Speed up WordPress 1000x

Facebook's HHVM

Facebook’s HHVM logo

(HHVM or PHP7)+Nginx+FastCGI+Varnish

  • PHP 7.0.10 (Latest Stable)
  • Nginx 1.11.3 (Latest)
  • HHVM  3.14.4 (Latest)
  • Varnish Cache Proxy service 4.0.3 (Latest)

This was done on a centOS 7 instance.

First tune your OS

  1. Tuned optimizes the settings on the OS and is configured based on your needs. Sort of like a profile that changes the settings, to find them use the “tuned-adm list” command
 [root@softlayer ~]]# tuned-adm list
 Available profiles:
 - balanced
 - desktop
 - latency-performance
 - network-latency
 - network-throughput
 - powersave
 - throughput-performance
 - virtual-guest
 - virtual-host
 Current active profile: virtual-guest

You’d want to select the throughput-performance profile as such

 [root@softlayer ~]]# tuned-adm profile throughput-performance

This should lower slightly page load time a little bit. Then we update the server’s existing packages and start to dive in.

 [root@softlayer ~]]# sudo su-
 [root@softlayer ~]]# yum update -y

Temporarily disable selinux

[root@softlayer ~]]# setenforce 0
  1. Next since we’re going to be installing PHP7 and its packages we want to enable EPEL and Remi repos to get the files.
 [root@softlayer ~]]# yum install epel-release -y

Next we’ll enable Remi’s repos

 [root@softlayer ~]]# wget http://rpms.remirepo.net/enterprise/remi-release-7.rpm 
 [root@softlayer ~]]# sudo rpm -Uvh remi-release-7*.rpm

Remove old versions of PHP & Install PHP 7

 [root@softlayer ~]]# yum remove php* -y
 [root@softlayer ~]]# yum --enablerepo=remi-php70 install php70-php

Install PHP 7 dependencies

[root@softlayer ~]]# yum --enablerepo=remi-php70 install php70-php-pear php70-php-bcmath php70-php-pecl-jsond-devel php70-php-mysqlnd php70-php-gd php70-php-common php70-php-fpm php70-php-intl php70-php-cli php70-php php70-php-xml php70-php-opcache php70-php-pecl-apcu php70-php-pecl-jsond php70-php-pdo php70-php-gmp php70-php-process php70-php-pecl-imagick php70-php-devel php70-php-mbstring php70-php-mcrypt

Run the following to make sure all is jiffy

Check PHP 7 Version

[root@softlayer ~]]# php70 -v
PHP 7.0.10 (cli) (built: Aug 31 2016 17:03:50) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
with Zend OPcache v7.0.10, Copyright (c) 1999-2016, by Zend Technologies

Now the great thing about PHP 7 is that it comes with APCu and OpCache (which out of the box are great extensions for caching) you can verify their presence by running the following command.

[root@softlayer ~]# php70 -i | egrep 'apc|opcache'

MariaDB Installation (Great replacement for mySQL)

If you don’t have it already, install MariaDB 5.5 [ or latest version]

[root@softlayer ~]]# yum install mariadb-server wget unzip -y

Start its installation and set a password you’ll remember by running the following command

[root@softlayer ~]]# mysql_secure_installation

 

To slightly speed up how WordPress utilizes MariaDB, we’ll adjust it as follows to have it optimize its usage
Edit /etc/my.cnf.d/server.cnf

[mysqld]
 innodb_buffer_pool_size = 512M
 query_cache_size = 64M

Query_cache_size enables cache for repeated queries. Innodb_buffer_pool_size will allow us to increase the cache for InnoDB Data.

Next login to MariaDB

[root@softlayer ~]# mysql -uroot -p

Create user for your WordPress installation, keep a note of it somewhere. Run the following commands to create a new wordpress database, and create a new user and give them all privileges to that database.

 MariaDB [(none)] create database wordpress;
 MariaDB [(none)] grant all privileges on wordpress.* to wpuser@localhost identified by 'wppass';
 MariaDB [(none)] flush privileges;
 MariaDB [(none)] exit;

 

Now we’ll get to Installing HHVM, this is based on https://github.com/facebook/hhvm/wiki/Prebuilt-Packages-on-Centos-7.x however it has been updated to fix broken links

First update

[root@softlayer ~] yum update -y

Install HHVM Dependencies

[root@softlayer ~]]# yum install cpp gcc-c++ cmake git psmisc {binutils,boost,jemalloc,numactl}-devel \
 {ImageMagick,sqlite,tbb,bzip2,openldap,readline,elfutils-libelf,gmp,lz4,pcre}-devel \
 lib{xslt,event,yaml,vpx,png,zip,icu,mcrypt,memcached,cap,dwarf}-devel \
 {unixODBC,expat,mariadb}-devel lib{edit,curl,xml2,xslt}-devel \
 glog-devel oniguruma-devel ocaml gperf enca libjpeg-turbo-devel openssl-devel \
 mariadb mariadb-server make -y

 

Install HHVM

[root@softlayer ~]]# rpm -Uvh http://mirrors.linuxeye.com/hhvm-repo/7/x86_64/hhvm-3.14.4-1.el7.centos.x86_64.rpm

Check HHVM’s version to check it was installed correctly

[root@softlayer ~] hhvm --version
HipHop VM 3.14.4 (rel)
Compiler: tags/HHVM-3.14.4-0-gcee2aedf82cffa3f4959adf9e0409c9ec7b314cb
Repo schema: c6d3b7c365d7be3fe41c697542afe0a1b1802987

Copy HHVM’s systemd service into the directory that has a higher precedence for services; /etc/systemd/system/

[root@softlayer ~] cp -p /usr/lib/systemd/system/hhvm.service /etc/systemd/system/

In your file that has been copied over (/etc/systemd/system/hhvm.services), edit it and change the vServer.Port port to 9000 for and change daemon user from nginx to root

[Unit]
Description=HHVM HipHop Virtual Machine (FCGI)
[Service]
ExecStart=/usr/local/bin/hhvm --config /etc/hhvm/server.ini --user root --mode daemon -vServer.Type=fastcgi -vServer.Port=9000
[Install]
WantedBy=multi-user.target

Save the file and reload using the following command

[root@softlayer ~] systemctl daemon-reload

Now we’ll update HHVM’s port configuration as well so it also listens on port 9k; edit /etc/hhvm/server.ini

hhvm.server.port = 9000

Change the timezone to your server’s location, around line 11 you can find full timezone list here: http://php.net/manual/en/timezones.america.php

Save changes and exit the file

You can now disable PHP 7 and use HHVM in its place by doing the following

[root@softlayer ~] systemctl disable php70-php-fpm
[root@softlayer ~] systemctl stop php70-php-fpm
[root@softlayer ~] systemctl enable hhvm
[root@softlayer ~] systemctl start hhvm

 

Finally install NGINX & Varnish

Varnish will be outside and will receive the port 80 traffic and then send that to our NGINX webserver.  After NGINX processes it with HHVM handler, it’ll send it back out to Varnish who’ll cache the data and save it in RAM to serve it quickly. FOR NOW if possible, avoid Varnish and use an alternative or not use it altogether, because in a new post that will be added soon I’ll have an alternative to Varnish (it has shown to consume more memory and usage than nginx for sure).

Create a repo “/etc/yum.repos.d/nginx.repo” to install the latest nginx. Inside that file, add the following:

[nginx]
 name=nginx repo
 baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/
 gpgcheck=0
 enabled=1
[root@softlayer ~] yum install nginx -y

Check NGINX Version

[root@softlayer ~] nginx -v
 nginx version: nginx/1.11.3

Install Varnish

[root@softlayer ~]]# yum -y install varnish

Enable Varnish at boot

[root@softlayer ~]]# systemctl enable varnish

Start Varnish

[root@softlayer ~]]# systemctl start varnish

Now for Varnish Configuration
Edit /etc/varnish/varnish.params

Look for

 VARNISH_LISTEN_PORT=6081

Change to

VARNISH_LISTEN_PORT=80

The reason is that we want Varnish to receive the traffic on Port 80 and be the conduit to cache data to the customer.

Edit /etc/varnish/varnish.params
For Varnish Cache Storage on disk:
(This step is recommended if you don’t have enough RAM and have SSD drive with tons of space; use plenty for the storage file 4GB to 5 GB)
Change the following value as follows:

VARNISH_STORAGE="file,/var/lib/varnish/varnish_storage.bin,4G"

This will use that storage file size of 4 GB located at that location.

For Varnish Cache Storage on RAM:
Change the following value as follows:

VARNISH_STORAGE="malloc,512m"

Instead of 512M RAM you can use the largest free available RAM.

Now to update the port that Varnish connects to send and receive data to/from, you’ll want to edit the following file

/etc/varnish/default.vcl

Look for

backend default {
     .host = "127.0.0.1";
     .port = "8080";
 }

 

Change the port number to 8080. Next I recommend copying and updating the configuration for Varnish from Matt on https://github.com/mattiasgeniar/varnish-4.0-configuration-templates to update your Varnish config to work with WordPress. I will have update here regarding that but for now your best bet is to follow Matt’s Github directions.

If you already have apache, you can now formally switch to nginx webserver as follows, if you are not sure if httpd is running or not, it doesn’t hurt to run the following commands as well

[root@softlayer ~] systemctl disable httpd
[root@softlayer ~] systemctl stop httpd
[root@softlayer ~] systemctl enable nginx
[root@softlayer ~] systemctl start nginx

Now we’ll be making some file changes, in /etc/nginx/nginx.conf
The numbers are shown to show you the code’s location, they should not be added to the file.
Look for..

8.    access_log  /var/log/nginx/access.log  main;
9.    sendfile        on;
10.   #tcp_nopush     on;

 

Right after, change or add the following [remember again not to add the line numbers – they are there to guide you! ]

11.  keepalive_timeout  3;
12.  gzip  on;
13.  server_names_hash_bucket_size 128;
14.  fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=wpcache:30m max_size=512M inactive=600m;
15.  include /etc/nginx/conf.d/*.conf;
16.  }

 

Save changes, and exit the file.

Now this step is optional but I have seen performance gains as much as 10ms shaved off my site. It is to mount your fastcgi cache folder onto ram so that it loads cached files from ram rather than from disk. To do that, do the following and adjust size (for Megabytes e.g. 512M) as necessary

[root@softlayer ~] mount -t tmpfs -o size=1G tmpfs /var/cache/nginx/wordpress

To make sure that it loads every time you reboot add the following to /etc/fstab file

[root@softlayer ~] tmpfs /var/cache/nginx/wordpress tmpfs defaults,size=1G 0 0

Next we’ll create a new file (you don’t have to name it httpd.conf but whatever helps you identify your site config file), /etc/nginx/conf.d/httpd.conf

In this file add the codes listed below, replace server_name value with your website’s url. The listen port will be 8080 because instead of nginx, we want Varnish to listen on port 80. Varnish will set atop nginx and work with it and hhvm to deliver cached files to customer. Below be sure to update the root location with your website’s folder. Nginx will work with fastcgi on port 9000 to get files quickly handled by HHVM.

server {
         listen 8080;
         server_name yourwebsite.com;
         root  /var/www/yoursitefolder;
         index index.php index.html index.htm;            
         location / {
                 try_files $uri $uri/ /index.php?$args;
         }
         location ~* /\. {
                   deny all;
           }
           location ~ [^/]\.php(/|$) {
                   fastcgi_split_path_info ^(.+?\.php)(/.*)$;
                   if (!-f $document_root$fastcgi_script_name) {
                                   return 404;
                   }
                   fastcgi_pass 127.0.0.1:9000;
                   fastcgi_index index.php;
                   fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
                   include fastcgi_params;
                   include fastcgi_params;
                   set $do_not_cache 0;
                  if ($request_method = POST) {
                  set $do_not_cache 1;
                  }
                  if ($query_string != "") {
                  set $do_not_cache 1;
                  }
                  if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
                  set $do_not_cache 1;
                  }
                  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 $do_not_cache 1;
                  }
                  fastcgi_cache        wpcache;
                  fastcgi_cache_key    "$request_method:$scheme://$host$request_uri";
                  fastcgi_cache_valid  200 60m;
                  fastcgi_no_cache     $do_not_cache;
                  fastcgi_cache_bypass $do_not_cache;
                  add_header X-F-Cache $upstream_cache_status;
          }
  }

Save changes, and exit the file

Restart nginx

 [root@softlayer ~] systemctl restart nginx

Now before we get going, final notes.

Your server will have firewall-cmd installed by default with “public” zone so run the following to enable access to your website.

[root@softlayer ~]# firewall-cmd --permanent --zone=public --add-service=http
[root@softlayer ~]# firewall-cmd --permanent --zone=public --add-service=https
[root@softlayer ~]# firewall-cmd --permanent --zone=public --add-port=8080/tcp
[root@softlayer ~]# firewall-cmd --permanent --zone=public --add-port=9000/tcp
[root@softlayer ~]# firewall-cmd --reload

 

Now that we have access to your website you should begin to have a feel for fast speed on your website.

 

If your website still fails to load, run the following to adjust permissions to your website’s site folder

[root@softlayer ~] chgrp nginx /var/www/yoursitefolder
[root@softlayer ~] chmod -R g+w /var/www/yoursitefolder

 

 

If you ever become unsatisfied with HHVM and wish to use PHP 7 instead or visa versa, you can toggle back and forth using the simple commands below

[root@softlayer ~] systemctl disable hhvm
[root@softlayer ~] systemctl stop hhvm
[root@softlayer ~] systemctl enable php70-php-fpm
[root@softlayer ~] systemctl start php70-php-fpm

 

You should now install wordpress on your website (If you haven’t already!) and install the W3Total Cache plugin and configure Cloudflare. Cloudflare+W3Total Cache plugins are great companions. Cloudflare will act as an CDN and minify some of your theme files and will work with W3Total Cache to deliver files compressed.

https://support.cloudflare.com/hc/en-us/articles/200169756-Can-I-use-WordPress-caching-plugins-like-Super-Cache-or-W3-Total-Cache-W3TC-with-CloudFlare

https://support.cloudflare.com/hc/en-us/articles/200169546-What-fields-do-I-need-to-enter-in-W3TC-W3-Total-Cache-settings

 

To conduct simple tests using either hhvm or php7 you can run the following tests using either above

[root@softlayer ~]  ab -n 300 -c 30 http://yourwebsite.com

 

Heavy tests; 10000 is the number of requests and 100 is the concurrent connections

 [root@softlayer ~] ab -n 10000 -c 100 http://yourwebsite.com

That’s it!

Test Results
How can this article be complete without a thorough test?
From a remote server I ran to my website server

ab -n 10000 -c 100 http://stanleyss.com/
 This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
 Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
 Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking stanleyss.com (be patient)
 Completed 1000 requests
 Completed 2000 requests
 Completed 3000 requests
 Completed 4000 requests
 Completed 5000 requests
 Completed 6000 requests
 Completed 7000 requests
 Completed 8000 requests
 Completed 9000 requests
 Completed 10000 requests
 Finished 10000 requests

Server Software: cloudflare-nginx
 Server Hostname: stanleyss.com
 Server Port: 80

Document Path: /
 Document Length: 39981 bytes

Concurrency Level: 100
 Time taken for tests: 49.556 seconds
 Complete requests: 10000
 Failed requests: 2
 (Connect: 0, Receive: 0, Length: 2, Exceptions: 0)
 Write errors: 0
 Total transferred: 405208123 bytes
 HTML transferred: 399762132 bytes
 Requests per second: 201.79 [#/sec] (mean)
 Time per request: 495.558 [ms] (mean)
 Time per request: 4.956 [ms] (mean, across all concurrent requests)
 Transfer rate: 7985.16 [Kbytes/sec] received

Connection Times (ms)
 min mean[+/-sd] median max
 Connect: 0 102 294.5 12 4007
 Processing: 40 242 314.2 155 15017
 Waiting: 32 54 74.7 40 4050
 Total: 46 344 456.1 169 15017

Percentage of the requests served within a certain time (ms)
 50% 169
 66% 228
 75% 374
 80% 404
 90% 1018
 95% 1260
 98% 1518
 99% 1809
 100% 15017 (longest request)

From using firebug I was able to see that the site itself loaded quickly within 100ms tops but using Google ads and wp images caused delays that reached 400ms.

Huge thanks to the inspiration for this post:
https://en.kusanagi.tokyo/archives/category/simply-fast-wordpress/

Additionally you’ll be thrilled to learn that Softlayer, an IBM Cloud provider now provides easy access to roll out this same setup using an premade image, read about that here:

http://blog.softlayer.com/2016/speed-your-wordpress-softlayer

Leave a Reply

Your email address will not be published.