Part 7 of 7 in "Self-Hosting WordPress"

  1. Self-Hosting WordPress – Ubuntu
  2. Self-Hosting WordPress – Nginx
  3. Self-Hosting WordPress – MariaDB
  4. Self-Hosting WordPress – PHP
  5. Self-Hosting WordPress – SSL
  6. Self-Hosting WordPress – Installation
  7. Self-Hosting WordPress – Performance

Optimising WordPress is a fairly straight forward process, and in doing so it’s possible to squeeze a serious amount of speed and power out of a cheaper server.

Go ahead and ssh in!

WP Cron

Cron is a system that runs programs (or commands) at scheduled times. WP Cron is the WordPress implementation that mimics this. Due to the necessity for WordPress to be compatible on various systems it’s not, by default, done “natively”. Instead WordPress triggers the appropriate hooks the next time the website is accessed. It’s possible to fake this with tools like Pingdom which hits your site every minute to report downtime, but we’ll go ahead and create a full cron system.

The first step is to disable WP Cron, and it’s crazy simple.

nano /var/www/html/wp-config.php

Scroll down and insert this line (near the bottom), feel free to add that other command if you are missing it.

define('FS_METHOD', 'direct');
define('DISABLE_WP_CRON', true);

/* That's all, stop editing! Happy blogging. */

Now we can edit the crontab, Linux’s built in cron system

crontab -e

Go ahead and append the following to the end.

# Run WP Cron
*/5 * * * * php -q /var/www/html/wp-cron.php >> /var/www/logs/cron_wp.log

If you’re not sure on the cron syntax it’s worth getting up to speed, then we just redirect the output to a log. All in all we’re running the wp-cron.php script manually every 5 minutes.

Save and exit.

crontab: installing new crontab

Nginx Page Cache

Nginx ships with page caching support, a few configuration changes can get it rolling for us. By caching each page Nginx can serve the HTML without even hitting WordPress. This means we don’t need to hit the database, WordPress or even PHP.

sudo nano /etc/nginx/sites-available/default

Insert a couple of lines which describe how Nginx will handle caching. The documentation has in-depth descriptions about each setting.

fastcgi_cache_path /var/www/cache levels=1:2 keys_zone=your_domain:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

server {
    listen 80 default_server;
    ...

Make sure to replace your_domain with your actual domain name, example: eliottrobson.me. This allows you to setup different domain caching as required in the future.

While we’re here, I’m going to redirect some more logs to that log folder I enjoy using. You can skip this. Following that we setup a variable to track which requests should avoid the cache including any wp-admin page, logged in users etc… And then we turn on gzip, just to compress the data we’re sending!

server_name your_domain_name;

access_log /var/www/logs/access.log;
error_log /var/www/logs/error.log;

set $skip_cache 0;

# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
    set $skip_cache 1;
}
if ($query_string != "") {
    set $skip_cache 1;
}

# Don’t cache requests containing the following segments
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
    set $skip_cache 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 $skip_cache 1;
}

gzip_comp_level 4;
gzip_types
    text/plain
    text/css
    text/js
    text/xml
    text/javascript
    application/javascript
    application/x-javascript
    application/json
    application/xml
    application/rss+xml
    image/svg+xml;
gzip_vary on;

So here we setup some tracking as to wether we should should skip the cache and hit WordPress. It should be pretty logical, feel free to add your own checks if appropriate.

Now, go ahead and merge the following (changes highlighted) in order to enable it for php requests.

location ~ \.php$ {
    include snippets/security-headers.conf;
    include snippets/fastcgi-php.conf;

    fastcgi_pass unix:/run/php/php7.0-fpm.sock;

    fastcgi_cache_bypass $skip_cache;
    fastcgi_no_cache $skip_cache;
    fastcgi_cache your_domain;
    fastcgi_cache_valid 60m;

    add_header X-Proxy-Cache $upstream_cache_status;
}

Make sure to change your_domain to the same value you set earlier.

Adding the header is optional, but it helps with debugging without revealing any sensitive information.

There are 3 values possible:

  • HIT – Page served from the cache
  • MISS – Page not cached, refresh will produce HIT or BYPAS
  • BYPASS – Page cached but skipped

Save the file, and restart Nginx.

sudo nginx -t
sudo service nginx restart

If you check your website, you should see the header being applied. 

However, when we post new content, the site will be out of date. Luckily there’s a great WordPress plugin that can fix it for us.

Nginx Cache

Activate this and then set the cache location in the settings. You’ll be able to manually purge the cache from this screen too.

This will purge our cache whenever the content changes ensuring it’s more or less up to date. Alternatively you could implement a cron job to do it if you were so inclined.

Redis Object Cache

Page caching gets us close to having a fast site, but actually we can do better. Object caching allows us to cache the response to popular database queries or expensive objects so we don’t need to fetch or compute them again. Redis is a key value store, critically acclaimed for its performance.

sudo apt-get install redis-server
sudo apt-get install php-redis

As always, that’s it. But we’ll tweak the configuration a little. As we only have a 512mb server, it’s worth setting some memory limits to avoid destroying our server.

sudo nano /etc/redis/redis.conf

Find the line…

# maxmemory <bytes>

Let’s enable the mode and set it to 64mb.

maxmemory 64mb

And then we can restart the services for this change to take effect.

sudo service redis-server restart
sudo service php7.1-fpm restart

There’s another WordPress plugin by the same author which provides the integration we’re looking for.

Redis Object Cache

Enabling the cache in the plugin settings is the last thing you’ll need to do!

PHP OpCache

So far, we’ve enabled object caching to reduce database queries and page caching to reduce PHP hits. If you visit your site you’ll already be excited by the speed. But we can even do one better. A huge part of the PHP execution time is reading and compiling each file, it does this for every request. By setting up OpCaching we can actually cache the compiled versions and read straight from memory.

OpCache comes as part of PHP7, so configuring it is all we need to do.

As part of the configuration there’s a “max files” setting. In order to ensure all our PHP files are cached we can run a quick search and see what our current count is.

find /var/www/html/ -iname *.php|wc -l
713

This is on a very bare bones install with no plugins. My production environment has over 3000 for reference. Consider a value at least as large as this (plus some more for maintainability).

sudo nano /etc/php/7.0/fpm/php.ini

The OpCache section is quite near the bottom.

There’s a few important settings we need to change.

opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=64
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=4096
opcache.revalidate_freq=15
opcache.fast_shutdown=1

Additionally, in a production environment the advice is to set validate_timestamps off. This means that the php won’t update unless the service or server is restarted but gives enhanced performance. My advice is to leave this on to save the hassle of updates.

sudo service php7.0-fpm restart

Continue reading "Self-Hosting WordPress"

You've reached the end of this series, good job!
There are currently no comments.