Please wait...

How to make WordPress site faster

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

You must be logged in to post a comment.