
How to make WordPress site faster
Our client have a WordPress website with more than 600 SQL queries on first page and load time about 15 seconds and he gave us the task. We should do all we can make website load faster and will not fall when simultaneously have more than 100 active users.
After we had checked server configuration we suspect disk speed and made write and read disk test on VPS server use this command:
dd if=/dev/zero of=tempfile count=1000 bs=1000M conv=fdatasync
The result showed us very slow disk operations. It was about 90-110 MB/s. Having regard to other server parameters and average users visit in day was made decision to buy other VPS server. By our suggestions owner bought the Linode 4GB. Disk test was agreeably surprised us on new server, average result is: 750 MB/s.
There is skipped easy run and installation procedure on Linode.
As result, we have VPS server with Ubuntu 14 LTS with installed PHP5.5 and PHP5-FPM, compiled the Nginx with PagePurge module, PHP-APCU and MariaDB. In such configuration, first page load time was about 8-10 seconds without any tuning and cache.
Hereafter I will write how to create FCGI cache, configure Opcache and APCU modules, and which WordPress modules should be added.
OPcache improves PHP performance by storing precompiled script bytecode in shared memory, thereby removing the need for PHP to load and parse scripts on each request.
APCU – as W3TC WordPress plugin does not work with OPcache, however can work with ACPU and cache database. I am not sure and did not made any test however I thinks that ACPU is not necessary module accordingly W3TC too. Nevertheless, developers ask to install APCU and W3TC.
PHP5-FPM – A simple and robust FastCGI Process Manager for PHP. Well-proven and really fast server. Sometime in the future need to productivity test PHP5-FPM vs HHVM
MariaDB – executes queries faster than MySQL. Have you question why MariaDB, please look this slide show.
Nginx can be deployed to serve dynamic HTTP content on the network using FastCGI, SCGI handlers for scripts, WSGI application servers or Phusion Passenger module, and it can serve as a software load balancer
PagePurge – nginx module which adds ability to purge content from FastCGI, proxy, SCGI and uWSGI caches.
NGINX configurations based on nginx-boilerplate I will describe only different parts of configuration.
NGINX configurations is based on nginx-boilerplate I will describe only different parts of configuration.
[su_spoiler title=”site.com.conf”]
server { include nginx-bp/ports/http_default.conf; server_name www.site.com site.com; root /var/www/site.com; access_log /var/www/logs/site.com-access.log main buffer=32k; #buffering doesn't work with variables error_log /var/www/logs/site.com-error.log warn; # error_log doesn't support variables
include nginx-bp/enable/nocache.conf; include nginx-bp/enable/errors.conf; include nginx-bp/limits/methods.conf; include nginx-bp/locations/default-wp.conf; include nginx-bp/locations/system.conf; include nginx-bp/locations/favicon.conf; include nginx-bp/locations/favicon_apple.conf; include nginx-bp/locations/static.conf; include nginx-bp/locations/php-wp.conf; include nginx-bp/locations/purge.conf; include nginx-bp/locations/errors.conf; include conf.d/wordpress.conf; }
[/su_spoiler]
[su_spoiler title=”default-wp.conf”]
location / { try_files $uri $uri/ /index.php?$args; } [/su_spoiler]
[su_spoiler title=”php-wp.conf”]
location ~ ^.+.php(?:/.*)?$ { include nginx-bp/enable/php-wp.conf; }
[/su_spoiler]
[su_spoiler title=”nocache.conf”]
#Cache everything by default set $nocache 0; #Don't cache POST requests if ($request_method = POST) { set $nocache 1; } #Don't cache if the URL contains a query string if ($query_string != "") { set $nocache 1; } #Don't cache the following URLs if ($request_uri ~* "(login.php|purgecache.php)") { set $nocache 1; } #Don't cache if there is a cookie called PHPSESSID if ($http_cookie = "PHPSESSID") { set $nocache 1; }
[/su_spoiler]
[su_spoiler title=”php-wp.conf”]
try_files $uri $uri/ /index.php$is_args$args; fastcgi_split_path_info ^(.+.php)(.*)$; fastcgi_pass php; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SERVER_NAME $http_host; include fastcgi_params; fastcgi_ignore_client_abort off; fastcgi_connect_timeout 60; fastcgi_send_timeout 180; fastcgi_read_timeout 180; fastcgi_buffer_size 128k; fastcgi_buffers 4 256k; fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; fastcgi_next_upstream error timeout; fastcgi_keep_conn on; fastcgi_hide_header "X-Powered-By"; fastcgi_cache phpCache; limit_req zone=reqPerSec20 burst=150 nodelay;
[/su_spoiler]
[su_spoiler title=”cache.conf”]
fastcgi_cache_min_uses 1; # Remember to replace $cookie_PHPSESSID with a proper variable name, if you # use custom session name: session.name/session_name() fastcgi_cache_key $request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri|$cookie_PHPSESSID; fastcgi_cache_lock on; fastcgi_cache_lock_timeout 5s; fastcgi_cache_methods GET HEAD; map $http_x_requested_with $nocache { default 0; XMLHttpRequest 1; } fastcgi_pass_header "X-Accel-Expires"; fastcgi_pass_header "X-Accel-Redirect"; fastcgi_cache_bypass $nocache; fastcgi_no_cache $nocache; fastcgi_cache_use_stale error timeout invalid_header http_500; fastcgi_cache_valid 200 301 302 304 24h; fastcgi_cache_valid 404 24h; # fastcgi_ignore_headers below may cause caching of authorized user content # Comment it out to cache only anonymous generated pages # https://github.com/Umkus/nginx-boilerplate/issues/39 fastcgi_ignore_headers "Cache-Control" "Expires"; if_modified_since before; # Use "off" for local development. open_file_cache max=300; open_file_cache_errors on; # Fastcgi cache zones below # At some point you'd probably want to change these paths to their own # directory, for example to /var/cache/nginx/ fastcgi_cache_path /var/cache/nginx/phpCache levels=1:1 keys_zone=phpCache:64m max_size=256m inactive=7d;
[/su_spoiler]
[su_spoiler title=”wordpress.conf”]
set $nocache 0; # POST requests and urls with a query string should always go to PHP if ($request_method = POST) { set $nocache 1; } if ($query_string != "") { set $nocache 1; } # Don't cache uris containing the following segments if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)") { set $nocache 1; } # Don't use the cache for logged in users or recent commenters if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") { set $nocache 1; } # Deliver 404 instead of 403 "Forbidden" error_page 403 = 404; # Do not allow access to files giving away your WordPress version location ~ /(.|wp-config.php|readme.html|licence.txt) { return 404; } # Add trailing slash to */wp-admin requests. rewrite /wp-admin$ $scheme://$host$uri/ permanent; # Deny access to any files with a .php extension in the uploads directory # Works in sub-directory installs and also in multisite network location ~* /(?:uploads|files)/.*.php$ { deny all; } # Make sure files with the following extensions do not get loaded by nginx because nginx would display the source code, and these files can contain PASSWORDS! location ~* .(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(.php)?|xtmpl)$|^(..*|Entries.*|Repository|Root|Tag|Template)$|.php_ { return 444; } #nocgi location ~* .(pl|cgi|py|sh|lua)$ { return 444; } #disallow location ~* (roundcube|webdav|smtp|http:|soap|w00tw00t) { return 444; } # Rewrite for versioned CSS+JS via filemtime location ~* ^.+.(css|js)$ { rewrite ^(.+).(d+).(css|js)$ $1.$3 last; expires 31536000s; access_log off; log_not_found off; add_header Pragma public; add_header Cache-Control "max-age=31536000, public"; } # Yoast WordPress SEO rewrite ^/sitemap_index.xml$ /index.php?sitemap=1 last; rewrite ^/([^/]+?)-sitemap([0-9]+)?.xml$ /index.php?sitemap=$1&sitemap_n=$2 last;
[/su_spoiler_section]
[su_spoiler title=”purge.conf”]
location ~ /purge(/.*) { allow 127.0.0.1; allow 0.0.0.0; #replace by IP your server deny all; fastcgi_cache_purge phpCache $request_method|$http_if_modified_since|$http_if_none_match|$host|$request_uri|$cookie_PHPSESSID; }
[/su_spoiler]
PHP5-FPM has three configuration files. It is php.ini, php-fpm.conf and ./pool.d/www.conf. In that PHP5-FPM installed locally we used UNIX socket for connect. Just comment one and uncomment other string.
; listen = 127.0.0.1:9000 listen = /var/run/php-fpm.socket
You need to configure value pm.max_children, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers, pm.process_idle_timeout in www.conf file. There is not a recipe; these values depend of free memory, quantity of user visit, PHP scripts, etc… Apache HTTP server benchmarking tool can help you to build initial configuration.
Here are our configuration files for PHP5 cache modules.
[su_spoiler title=”opcache.ini”]
; configuration for php ZendOpcache module ; priority=05 zend_extension=opcache.so opcache.enable=1 opcache.memory_consumption=24 opcache.max_accelerated_files=2000 opcache.max_wasted_percentage=5 opcache.revalidate_freq=120 opcache.consistency_checks=1 opcache.enable_cli=1 opcache.interned_strings_buffer=8 opcache.fast_shutdown=1 opcache.save_comments=1 opcache.enable_file_override=1 opcache.validate_timestamps=1
[/su_spoiler]
[su_spoiler title=”apcu.ini”]
extension=apcu.so apc.mmap_file_mask=/tmp/apc.XXXXXX apc.enabled=1 apc.shm_size=8M apc.ttl=7200 apc.enable_cli=1 apc.gc_ttl=3600 apc.entries_hint=4096 apc.slam_defense=1 apc.serializer=igbinary [/su_spoiler]
Here comes NGINX Manager WordPress plugin that add necessary headers X-Accel-Expires for Nginx.
What we are reach? First page load time is still about 5 seconds, however when this page is in cache load time is about 2-3 seconds. Website does not fall and might serve more than 500 user according to AB test. Web site launch soon then we will have real information.
I think my article is incomplete so do not hesitate ask me. I will try answer.
Leave comments