Make Your WordPress At Least 3x Faster By Migrating To Nginx + PHP-FPM

Published on Author gryzli

If you want to make your WordPress at least 3x faster , then you should definitely read below….

Yesterday I woke up, and my first thought was – I need to make my WordPress blog faster !

 

Caring about performance !

That’s a great idea and most of the time, there is bunch of things you can do about this, all you need is a little bit enthusiasm, not so geeky knowledge.

 

My current running environment

It’s worth being said, what was my “starting point” before trying to do whatever optimizations I fell necessary, and here it was:

  • Centos 6.7
  • Php 5.3.x by the Centos repo
  • Apache 2.x by the Centos repo
  • MySQL 5.1.x

 

My past curve of WordPress speed optimizations

1. First I installed WordPress by the laziest way human beings know and using the laziest (still slowest) technologies – Apache + mod_php + php 5.3.x

2. Then I saw my WP site was tremendously slow and I tought to myself, lets get it little bit faster, what I’ve done was easy and simple:

  • I switched my PHP setup to mod_fcgid (instead the slow mod_php)
  • I installed WordPress caching module (WP Super Cache), which caused some serious impact on my load times

 

3. At a certain point in time, I again felt like being no enough fast

  • I setup static content compression by using Apache mod_deflate + mod_rewrite rules
    # Enable gzip
    <ifmodule mod_deflate.c>
            AddOutputFilterByType DEFLATE text/text text/html text/plain text/xml text/css application/x-javascript application/javascript text/javascript
    </ifmodule>

     

  • I installed PHP caching module (APC), which had its good for me
    The APC configuration was as follows

    extension="apc.so"
    
    apc.enabled = 1
    apc.shm_segments = 1
    apc.shm_size = 512M
    apc.optimization = 0
    apc.num_files_hint = 10000
    apc.user_entries_hint = 10000
    apc.ttl = 0
    apc.user_ttl = 0
    apc.gc_ttl = 600
    apc.cache_by_default = 1
    apc.filters = "apc\.php$"
    apc.slam_defense = 0
    apc.use_request_time = 1
    apc.mmap_file_mask = /tmp/apc.XXXXXX 
    apc.file_update_protection = 2
    apc.enable_cli = 1 
    apc.max_file_size = 20M
    apc.stat = 0
    apc.write_lock = 1
    apc.report_autofilter = 0
    apc.include_once_override = 1
    apc.rfc1867 = 0
    apc.rfc1867_prefix = "upload_"
    apc.rfc1867_name = "APC_UPLOAD_PROGRESS"
    apc.rfc1867_freq = 0
    apc.localcache = 1
    apc.localcache.size = 512
    apc.coredump_unmap = 0
    apc.stat_ctime = 0

     

  • Tuned my mysql configuration a little bit

At that point I think, I already doubled my performance grade, and my blog was loading 2 to 3 times faster.

Then I wanted some more speed on my WordPress, it still wasn’t fast enough

That was the time i woke up with the idea of switching to Nginx. I’ve already migtrated hundred of sites to Nginx+PHP-FPm and I already understand the huge benefits you get by using php-fpm.

 

Why use Nginx ?

Well some people tend that Nginx serves static content faster than Apache. I’m not quite sure, how much is the truth inside this, but for sure there is something real about it.

Despite the static content serving rumors, that wasn’t the reason I needed it, the real reason was:

Nginx has native support for executing php scripts under PHP-FPM and that’s great !

The configuration is so much straight forward, that I was tempted to switch to it.

 

How I Installed and Configured my Nginx ?

First I thought about installing Nginx from my Centos Repo, but that wasn’t the greatest idea, because it was too old. When it comes to software, most of the times the newer is the better (or at least we all hope for this).

So I decided to install the latest Nginx version, which also happened to be a damn easy task.

Just install Nginx repo, based on your Linux distro

Current Nginx Repo Lists

 

After successfully installing Nginx 1.8.x it came the time for configuring it.

 

Here are my current configs:

/etc/nginx/nginx.conf

user  apache;
worker_processes  4;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;

    keepalive_timeout  65;
    include /etc/nginx/conf.d/*.conf;
}

You may notice the following directives:

  • user apache; – I wanted to reuse my Apache permissions
  • worker_processes 4; – This is the same as the number of virtual CPU’s I have

 

/etc/nginx/conf.d/gryzli.info.conf

server {
    listen       192.168.0.1:80;
    server_name  gryzli.info;

    access_log  /var/log/nginx/ssl_gryzli.info.log main;
    error_log   /var/log/nginx/ssl_gryzli.info.error_log;

     root   /var/www/html/wordpress;
    location / {
        root   /var/www/html/wordpress;
        index  index.php index.html index.htm;
        try_files $uri $uri/ /index.php?$args;

    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php$ {
        root           /var/www/html/wordpress;
        fastcgi_pass   unix:/tmp/php5.6-fpm.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }

   # Include wordpress settings
   include /etc/nginx/custom_conf/wordpress.conf;


# THat redirects to SSL
#    rewrite     ^   https://$server_name$request_uri? permanent;
}

server {
    listen       192.168.0.1:443 ssl;

    root   /var/www/html/wordpress;
    server_name  gryzli.info;

    access_log  /var/log/nginx/ssl_gryzli.info.log main;
    error_log   /var/log/nginx/ssl_gryzli.info.error_log;

    location / {
        root   /var/www/html/wordpress;
        index  index.php index.html index.htm;
        try_files $uri $uri/ /index.php?$args;

    }

        ssl_certificate      /etc/httpd/conf.d/ssl/gryzli.info.bundle;
        ssl_certificate_key  /etc/httpd/conf.d/ssl/gryzli.info.key;

        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  5m;

        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers   on;

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php$ {
        root           /var/www/html/wordpress;
        fastcgi_pass   unix:/tmp/php5.6-fpm.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }

   # Include wordpress settings
   include /etc/nginx/custom_conf/wordpress.conf;
}

 

And the wordpress related configuration options:

/etc/nginx/custom_conf/wordpress.conf

# Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac).
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~ /\. {
        deny all;
}

# Deny access to any files with a .php extension in the uploads directory
# Works in sub-directory installs and also in multisite network
# Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban)
location ~* /(?:uploads|files)/.*\.php$ {
        deny all;
}

# WordPress single site rules.
# Designed to be included in any server {} block.


# Add trailing slash to */wp-admin requests.
rewrite /wp-admin$ $scheme://$host$uri/ permanent;

# Directives to send expires headers and turn off 404 error logging.
location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|mp4|ttf|rss|atom|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
#               access_log off; 
#       log_not_found off; 
        expires max;
}

 

 

Installing and configuring PHP 5.6.x as FPM

As with Nginx my thought about PHP was the same – the newer the better.  And again, there are rumors about php 5.6 being considerably faster than its predecessors. Still no tests by myself to acknowledge this, but lets trust the internet guys…

 

In the matter of fact, running php as FPM is the thing which makes the most of the speed increase magic. Why ? It’s simple, when you run PHP-FPM, you have the following BIG PLUSES:

  • PHP FPM threads are sharing their memory, which gives them the chance to use the same cache container (when you use opcode caching). Also after the child processes respawn, the cache base stays tight with the parent process, so the cache is not flushed.
  • If you use static created childs, you will benefit one more thing, skip the “event driven fork” part, which slows your request processing

 

This time I decided to compile my own copy of PHP 5.6, in order to include as minimal as possible modules and stuff.

  • Download the latest php from php.net
  • tar xvzf php-5.6.*.tar.gz
  • cd php-5.6*
  •  Execute configure
    ./configure --prefix=/usr/local/php5.6 \
            --enable-fpm \
            --with-openssl \
            --with-zlib \
            --with-curl \
            --with-mhash \
            --with-mysql \
            --with-mysqli \
            --enable-opcache \
            --with-pdo-mysql \
            --with-xmlrpc \
            --enable-zip \
            --enable-mbstring \

     

  • Then install it
    make -j4 
    make install

     

  • Finally copy the init script
    cp -a ./sapi/fpm/init.d.php-fpm /etc/init.d/php5.6-fpm
    

     

  • Add the service and make it autoload at boot time
    chkconfig add php5.6-fpm
    chkconfig php5.6-fpm on

We already have php-fpm installed in /usr/local/php5.6.

The last thing is to tune a little bit the php-fpm configuration file.

vim /usr/local/php5.6/etc/php-fpm.conf

[www]

user = apache
group = apache

listen = /tmp/php5.6-fpm.sock

listen.owner = apache
listen.group = apache
listen.mode = 0660

pm = static
pm.max_children = 20
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 10
pm.max_requests = 500
 
access.log = /var/log/php-fpm/$pool.access.log

access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"

 

The cherry of the pie – Installing opcache for php 5.6

There is one last thing – installing opcache for php5.6.  Opcache installation is pretty straight forward:

  • Install opcache from pecl (with pecl install opcache) or by manually compiling it
  • Modify php.ini config to enable opcache
    [opcache]
    zend_extension=opcache.so
    opcache.memory_consumption=128
    opcache.interned_strings_buffer=8
    opcache.max_accelerated_files=4000
    opcache.revalidate_freq=60
    opcache.fast_shutdown=1
    opcache.enable_cli=1
    

     

Voalla ! Now we have WordPress at least 3x times faster …

That was all of it in summary:

  1. Installed Nginx and configured it to serve my blog
  2. Installed PHP5.6 as FPM and make it statically pre-spawn its childs
  3. Added PHP 5.6 init script and make it autoload at system startup
  4. Configured Nginx to communicate with PHP-FPM through unix socket
  5. Installed Zend Opcache for doing the opcode cache part

And here we are, what I’ve achieved by this setup was:

3x faster wordpress by the Apache Bench (ab) judgement.

 

Before I was doing about 8-9 requests per second, now I’m able to make about 30 requests per second.

I have already done some example optimizations for Magento sites, you can also check it here:

Optimize your server for best Magento performance