WordPress Optimization

From wiki.mikejung.biz
Jump to navigation Jump to search

How to Optimize Wordpress on a cPanel Server

Don't have the time to optimize WordPress? Don't really know what you're doing? Check out Liquid Web's new Managed WordPress offering It's really fast, trust me, I helped with the optimizations ;) If you want to do it yourself, I can respect that, please continue on and enjoy the rest of the page!

This page is meant to help improve Wordpress performance on a cPanel server. Because cPanel is somewhat limited in terms of PHP handlers and lack of native PHP-FPM support, it's hard to get the "best" performance, but with some small changes we can significantly improve WordPress Performance. This guide focuses on optimizing the following areas:

  • MySQL InnoDB Tweaks
  • Apache Event Configurations
  • Installing and using Memcached
  • Installing and using Varnish
  • Enabling PHP opcode caching
  • WordPress Page Caching configuration

I will still cover how to install and configure PHP-FPM on a cPanel server, but if you are feeling less adventurous you can stick with FCGI for the PHP handler. FCGI is not quite as fast as PHP-FPM, but it's close enough, at least imo.

WordPress Performance Tests

By using the guide below, you can significantly improve Wordpress performance. For my tests I used WordPress 4.0.1 and did not create any posts, or add any content to the blog. I did this to set a baseline for performance, once you start adding in content it's hard to really compare results.

I tested out performance on LiquidWeb's 1GB SSD VPS The VPS has 2 vCPUs and 1GB of RAM along with 50GB all SSD storage.

For the "Default" run I used a newly created cPanel server, installed WordPress and started to running tests. I used MySQL 5.6, Apache 2.4 Prefork with PHP 5.4 using SuPHP as the handler. I did not have an optimized .htaccess file in place, Varnish was not installed, Memcached was not installed, and w3totalcache was not used for this run. Obviously this is not how most people run WordPress, but I wanted to use the most basic configuration and no optimizations to show the "worst case" WordPress performance.

I used Apache Benchmark to test out the site. I ran this from a remote location, so this was NOT run on the same server that hosted the Wordpress site. This command will use 5 concurrent connections and will load the site 1000 times.

ab -c 5 -n 1000 http://mytestdomain.lol/

As you can see, with the default configuration I get around 13 Requests per second. The response time for 95% of requests was 456ms. This isn't amazing, but not terrible. The server was able to handle the load without issue, but view the next set of results to see what happens once we optimize the server a bit.

Server Software:        Apache/2.4.10
Document Path:          /
Document Length:        7418 bytes

Concurrency Level:      5
Time taken for tests:   76.810 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      7675000 bytes
HTML transferred:       7418000 bytes
Requests per second:    13.02 [#/sec] (mean)
Time per request:       384.050 [ms] (mean)
Time per request:       76.810 [ms] (mean, across all concurrent requests)
Transfer rate:          97.58 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1    2   2.1      1      20
Processing:   274  382  51.1    382     655
Waiting:      219  309  44.2    310     491
Total:        275  384  51.2    385     655

Percentage of the requests served within a certain time (ms)
  50%    385
  66%    411
  75%    425
  80%    433
  90%    447
  95%    456
  98%    468
  99%    478
 100%    655 (longest request)

Once we installed PHP-FPM, Varnish, Memcached, Apache Event, applied .htaccess, and installed w3totalcache we get much better results. With the configuration listed below my test site was able to process 805 Requests per second. The response time for 95% of requests was 11ms which is insane. As you can see if you modify the default cPanel settings you can get significant performance gains for a basic Wordpress site.

Server Software:        Apache/2.4.10
Document Path:          /
Document Length:        7753 bytes

Concurrency Level:      5
Time taken for tests:   1.242 seconds
Complete requests:      1000
Failed requests:        0
Total transferred:      8124694 bytes
HTML transferred:       7753000 bytes
Requests per second:    805.07 [#/sec] (mean)
Time per request:       6.211 [ms] (mean)
Time per request:       1.242 [ms] (mean, across all concurrent requests)
Transfer rate:          6387.65 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        2    3   1.1      3      11
Processing:     1    3   1.1      3      12
Waiting:        1    3   1.1      3      12
Total:          4    6   2.0      6      17

Percentage of the requests served within a certain time (ms)
  50%      6
  66%      6
  75%      7
  80%      7
  90%      9
  95%     10
  98%     13
  99%     14
 100%     17 (longest request)

MySQL Installation and Configuration Steps

MySQL 5.6 Configuration for Wordpress

Ensure that MySQL 5.6 is installed and that innodb_buffer_pool_size is set correctly. If you are using MySQL 5.1 or older you must upgrade to at least MySQL 5.5. If you don't upgrade to a newer version of MySQL performance is going to suck no matter what, using old software is a terrible idea.

Generally speaking you will want to give MySQL at least 128MB of RAM for innodb_buffer_pool, if you have a larger database then raise this to an appropriate limit. Keep in mind that you don't want to set innodb_buffer_pool to a value that is higher than the amount of RAM on the server, otherwise it could start to swap out pages and performance will drop quickly. If this is a brand new WordPress install then a buffer pool size of 128MB should be plenty. You will also want to increase the size of innodb log files. By default the value is 5MB, at least for older versions of MySQL and 48MB for MySQL 5.6 or newer. Since the VPS I am using has all SSD storage I will be raising the log size to 128MB. There are two log files, so the total size would be 256MB between both log files. If you raise this value too high and the server crashes it might take a minute or two for MySQL to replay back the logs to recover data, this used to be an issue with slower, spinning HDDs, however SSDs can replay log files much faster, so increasing the size will help with write performance.

Please note that you must move the old innodb logs out of the way, stop MySQL and then start it up again. If you do not do this then MySQL will fail to start and you will have downtime on your hands until you remove the old log files.

This MySQL configuration should work well for most servers with 1GB or 2GB of RAM.

cat /etc/my.cnf
innodb_buffer_pool_size = 128M
innodb_log_file_size = 128M
key_buffer_size = 64M

This MySQL configuration should work well for most servers with 4GB of RAM.

cat /etc/my.cnf
innodb_buffer_pool_size = 256M
innodb_log_file_size = 256M
key_buffer_size = 64M

This MySQL configuration should work well for most servers with 8GB of RAM. Again, this is assuming you have a relatively small WordPress site, if you have a large site with a database that is, say, 10GB in size then you would want to raise innodb_buffer_pool_size to at least 10GB. Obviously you will need more RAM if you are using an 8GB server, but to truly optimize WordPress for performance you will want to try and fit most of the actively used data into RAM.

cat /etc/my.cnf
innodb_buffer_pool_size = 512M
innodb_log_file_size = 256M
key_buffer_size = 64M

Apache 2.4 Event Configuration for Wordpress

Run EasyApache to Enable the Event MPM

You will want to run Easy Apache in a screen to make sure you have the EVENT MPM enabled, we will also make sure that mod_fastcgi is selected. I like to run EasyApache in a screen, if you don't do this and your ssh connection times out or something strange happens then you may need to start the process over again. In general it's best to run cPanel scripts in a screen.

screen -S EA

Make sure that you have selected the following options under the Apache exhaustive list:

  • Update Apache to the lastest 2.4.x release
  • Update PHP to 5.5 or newer
  • Event MPM (de select prefork or worker and only select Event)
  • mod_fastcgi

Once you have selected the latest versions for Apache and PHP, and selected the Event and mod_fastcgi options, go ahead and save / build the configuration. This can take around 10 minutes or so.

cPanel Tweak Settings Listen Port

In WHM Tweak Settings. Change the default Apache listen IP and port to and apply the changes before any cPanel accounts are created. Leave the SSL IP and port alone, apache will still be handling ssl traffic. If you already have live sites on your server then you will want to save this step for last. You need to have Varnish install and listening on port 80, using localhost 8080 as the backend. If you do not do this before this step then your sites will be down. You can find this setting in WHM under Tweak Settings > System. Configure the listen port as shown in the image below.

Smush-cPanel Tweak Settings Apache Listen Varnish.jpg

cPanel Apache Event Configuration

Because cPanel likes to override the main httpd.conf file, which is modified also by WHM global configuration, we will want to put an event ifmodule block in pre_virtualhost_global.conf, this will override any dumb settings cPanel tries to apply, keeping our event settings at the defaults, which should be fine for most websites. If you are expecting more than 400 concurrent requests then you can raise the "ThreadsPerChild" from 25 to 30, and continue to raise this in small steps until you are happy. Keep in mind that in order to reach this limit Apache would need 400 requests AT THE EXACT SAME TIME, it's able to serve lots of requests within a 1 second time span, so if you get 400 requests over the duration of 1 second, these settings would be more than enough to handle the traffic. If anything, PHP is going to be the bottleneck before Apache becomes one.

vim /usr/local/apache/conf/includes/pre_virtualhost_global.conf

KeepAlive On
KeepAliveTimeout 5
MaxKeepAliveRequests 100

<IfModule event.c>
ThreadsPerChild 25
ServerLimit 16
MaxRequestWorkers 400
StartServers 3
MaxConnectionsPerChild 0

Apache Event MPM and Version Info

Using Apache 2.4 EVENT, we are NOT using mod_php or SuPHP as the handler, we are using FastCGI to pass off PHP handling to PHP-FPM. At this point you will want to make sure that you are using Apache 2.4 with the EVENT MPM. Run httpd -V to find out what versions you are using.

httpd -V
Server version: Apache/2.4.10 (Unix)
Server built:   Nov 26 2014 14:49:27
Cpanel::Easy::Apache v3.26.10 rev9999
Server's Module Magic Number: 20120211:36
Server loaded:  APR 1.5.1, APR-UTIL 1.5.4
Compiled using: APR 1.5.1, APR-UTIL 1.5.4
Architecture:   64-bit
Server MPM:     event
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)
Server compiled with....
 -D APR_HAVE_IPV6 (IPv4-mapped addresses disabled)
 -D HTTPD_ROOT="/usr/local/apache"
 -D SUEXEC_BIN="/usr/local/apache/bin/suexec"
 -D DEFAULT_PIDLOG="logs/httpd.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="conf/mime.types"
 -D SERVER_CONFIG_FILE="conf/httpd.conf"

Varnish Installation and Configuration for Wordpress

Varnish 4.0 Installation Method for cPanel

This seems to be the simplest way to install Varnish. As of early 2015, the latest stable version of Varnish is 4.0.x. Run the commands below to install the Varnish repo, and jemalloc which is required for Varnish to run correctly. These repos and packages should not conflict with cPanel's repos. Once varnish is installed you will want to make sure that it's enable via chkconfig so that Varnish always starts up after the server is rebooted.

rpm --nosignature -i https://repo.varnish-cache.org/redhat/varnish-4.0.el6.rpm
wget https://dl.fedoraproject.org/pub/epel/6/x86_64/jemalloc-3.6.0-1.el6.x86_64.rpm
rpm -i jemalloc-3.6.0-1.el6.x86_64.rpm
yum install varnish
chkconfig varnish on

Configuration of Varnish via /etc/sysconfig/varnish

This Varnish configuration file is used to configure the varnish daemon. We will be changing the listening port to 80 and configuring the malloc storage size based on the size of the instance used. I set this to 128MB for the 1GB VPS, you may need to tune this if you run into memory issues. You don't really need to configure Varnish to cache all the things, most of the time you will have a few pages / images that are accessed very often and a ton of other pages that are accessed less frequently, Varnish does a pretty good job at making sure the most used files are cached, and it will clear out files that are never accessed. It's best to wait a day or two and view varnishstat to get an idea of what your "warm" cache looks like. If you are seeing a lot of misses or that Varnish is utilizing all 128Mb, you can then consider raising this value.

For this file, we really only need to worry about two items, the listening port and the storage size. The listen port should always be set to 80. The storage size should scale with the instance size (how much RAM your server has). I was able to hammer my server with 1000 requests @ 10 concurrent connections and my server didn't even break a sweat with only 1GB of RAM, so this configuration should be pretty good for most sites.

vim /etc/sysconfig/varnish 



This file defines what the backend servers are (in this case that is apache It also defines how varnish caches things, and handles them. We are basically just going to define the ACL for Purging so that W3Total cache can purge if needed.

Really, for the most part we should just be able to set it and forget it, but if you run into issues you will modify the VCL. By default Varnish is able to cache a lot with no special VCL in place. If you run into issues with SSL, or Sessions or whatever you might want to add in some VCL to handle this, otherwise the file below should be fine.

vim /etc/varnish/default.vcl

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

acl purge {

sub vcl_recv {
        if (req.method == "PURGE") {
                if (!client.ip ~ purge) {
                        return(synth(405,"Not allowed."));
                return (purge);

At this point you should be able to restart varnish and Apache. If you have not yet changed Apache's listening port in cPanel Tweak settings, you should do so now. I suggest stopping Apache, starting up Varnish on Port 80, then going to WHM Tweak settings and changing Apache listen to If you get any errors with Varnish or Apache about a port already being used, make sure you stop the services first, or else there will be a conflict between Apache and varnish as to what port should be used by what application.

Running these four commands should get you on track once you have updated Varnish to listen on port 80 (all interfaces) and Apache on port 8080 ( only). Leave Apache SSL port 443 alone, Varnish doesn't do SSL, so you will have to use Apache for SSL traffic.

service apache stop
service varnish stop
service varnish start
service apache start

cPanel PHP 5.5 Configuration for Wordpress

Other than making sure EasyApache is using at least PHP 5.5.x as the main version there is not a lot of configuration needed to optimize PHP. PHP 5.5 has a built in OPcache, previously this was known as the Zend Optimizer, but now that is open source and built into PHP. All you need to do is modify the global php.ini file and include the opcache.so module, which should already exist on the server. By using opcode caching, we allow already compiled PHP code to get cached in RAM. The next time the server needs to perform a cached PHP operation it can simply run it instead of having to interpret it and compile it again. This saves a ton of CPU usage and also reduces latency between requests, which means your site loads faster and uses less resources.

To use opcache correctly you must make sure that you are using either FCGI or PHP-FPM as the PHP Handlers. If you are using DSO or SuPHP then opcaching will not work and your server will be low and waste memory.

Zend OPcache Configuration

Assuming you are using PHP 5.5+ you should be able to easily enable PHP's built in OPcache by modifying /usr/local/lib/php.ini. Make sure that opcache.so is located in your extensions directory that is specified in the php.ini file. Once you have edited the file restart php-fpm.


PHP Version Info

As long as we are using PHP 5.5 and up, you should be good.

php -v
PHP 5.5.19 (cli) (built: Nov 26 2014 14:53:33) 
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
    with the ionCube PHP Loader v4.6.1, Copyright (c) 2002-2014, by ionCube Ltd.
    with Suhosin v0.9.36, Copyright (c) 2007-2014, by SektionEins GmbH

cPanel PHP Handler Info

Make sure you set the default PHP handler to FCGI if you don't want to install PHP-FPM. If you plan on using PHP-FPM then you can set the handler to "none" once you have PHP-FPM installed and running. For now you might as well set this to FCGI until you get PHP-FPM configured.

/usr/local/cpanel/bin/rebuild_phpconf --current
Available handlers: fcgi cgi none
PHP4 SAPI: none
PHP5 SAPI: fcgi
SUEXEC: enabled
RUID2: not installed

PHP-FPM Configuration on cPanel

Installation method for PHP-FPM

This script seems to work well for installing and configuring PHP-FPM on a cPanel account. The script will install PHP-FPM, generate per domain configuration files and modify httpd.conf to configure existing domains to use PHP-FPM. You will need to make sure you enable FCGI via Easy Apache and select the default PHP handler to be FCGI.

wget http://sysally.net/cphstack.sh
chmod a+x cphstack.sh
./cphstack.sh install
service php-fpm start

Note that sometimes the installer won't run even if you already selected mod_fastCGI via EasyApache. If you get an error when you try to run the installer, simply run Easy Apache again and make sure that you un select SuPHP or Prefork. If everything already looks fine then just have EA build the configuration again, once that is done you should be able to run the installer script.

You must start PHP-FPM once the installer finishes, by default PHP-FPM will not start up after the installer is done. If you don't do this then all your sites will be down.

PHP-FPM Config File: /usr/local/etc/php-fpm.conf

This is the global configuration file for PHP-FPM. There is not much to mess with here, at the end of the file there is an includes directory listed which houses the individual vhost configs. NOTE The memcache.so line was added by me to get the memcache module to work. Everything else is at the default, but keep in mind that the .conf files under the include directory will take precedence over this file.

pid = /var/run/php-fpm/php-fpm.pid
error_log = /var/log/php-fpm.log

user = nobody
group = nobody
listen =
pm = ondemand
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
php_admin_value[extension] = memcache.so

PHP-FPM Config File(s): /opt/xstack/cphstack/php-fpm.pool.d/*.conf

The per domain config files go in this directory. Each file is named after the domain, which is nice, for example, on my test server the only file under this directory was:

-rw-r--r-- 1 root root 18163 Nov 26 14:56 domain.conf

My config file has the following settings:

; Pool Definition ;

user = user
group = user
listen = /var/run/php-fpm/user.sock
listen.mode = 0666
pm = ondemand
pm.max_children = 15
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 256

Memcached Installation and Configuration for Wordpress on cPanel

Installing Memcached on a cPanel server is rather straightforward. You simply yum install memcached, and then modify memcached.conf as needed. I suggest starting off with 64MB cache size, then raise as needed.

yum install memcached

vim /etc/memcached.conf

##Modify the file to include these lines. You can raise -m from 64 to however much memory you want memcached to use, this configuration file is meant for a smaller server with 1GB or 2GB of RAM. ##
-m 64
-p 11211
-u nobody

Once you have finalized the Memcached configuration file, go ahead and start up memcached and use chkconfig to make sure that memcached starts up on reboot.

service memcached start
chkconfig memcached on

Now you need to install the memcache PHP module so that WordPress can use it with w3totalcache

pecl install memcache
echo "extension=memcache.so" >> /usr/local/lib/php.ini
service httpd restart

Run the command below to make sure that the memcache PHP module is loaded.

php -m | grep memcache

WordPress 4.1 cPanel Configuration

Nothing too special here, installed the latest version of Wordpress and installed w3totalcache/


This is how I enabled the plugin. No need to really worry about page cache since we are caching the hell out of everything already. If you prefer to have Page Cache enabled, feel free to do so, but for the sake of simplicity I disabled page caching for now until I am sure that everything is updating properly.

Page Cache: Disabled
Minify: Disabled
Database Cache: Enabled, uses Memcached
Object Cache: Enabled, uses Memcached
Browser Cache: Enabled
CDN: Disabled
Reverse Proxy: Enable Varnish Cache, server

cPanel and WHM 11.48 Configuration

Tweak Settings

Updating Use pigz from Off to On.

Updating Bandwidth limit check from On to Off

Updating Allow cPanel & WHM to determine the best value for your MySQL max_allowed_packet configuration? from On to Off

Updating Allow cPanel & WHM to determine the best value for your MySQL open_files_limit configuration? from On to Off.

Updating Critical load threshold from “10” to # of CPUs (autodetect).

Apache non-SSL IP/port

Optimized .htaccess for WordPress Caching

<ifmodule mod_deflate.c>
AddOutputFilterByType DEFLATE text/text text/plain text/xml text/css application/x-javascript application/javascript text/javascript

<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 5 seconds"
ExpiresByType text/css A31536000
ExpiresByType application/x-javascript A31536000
# Text
ExpiresByType text/css A31536000
ExpiresByType application/x-javascript A31536000
ExpiresByType text/html A3600
ExpiresByType text/richtext A3600
ExpiresByType text/plain A3600
ExpiresByType text/xml A3600

# Image
ExpiresByType image/gif A31536000
ExpiresByType image/x-icon A31536000
ExpiresByType image/jpeg A31536000
ExpiresByType image/png A31536000
ExpiresByType image/svg+xml A31536000

# Video
ExpiresByType video/asf A31536000
ExpiresByType video/avi A31536000
ExpiresByType video/quicktime A31536000
ExpiresByType video/mp4 A31536000
ExpiresByType video/mpeg A31536000

ExpiresByType application/pdf A31536000

# Flash
ExpiresByType application/x-shockwave-flash A31536000

# Font
ExpiresByType application/x-font-ttf A31536000
ExpiresByType application/vnd.ms-fontobject A31536000
ExpiresByType application/x-font-otf A31536000

# Audio
ExpiresByType audio/mpeg A31536000
ExpiresByType audio/ogg A31536000
ExpiresByType audio/wav A31536000
ExpiresByType audio/wma A31536000

# Zip/Tar
ExpiresByType application/x-tar A31536000
ExpiresByType application/x-gzip A31536000
ExpiresByType application/zip A31536000

Options -Indexes

how to modify the base URL for wordpress 4.0


1. Login to wp-admin

2. Settings > General

3. WordPress Address (URL) -- leave this alone, this is the base install directory, changing this will break things.

4. Site Address (URL) -- change this from domain.com/somedir to domain.com.

5. Copy index.php and .htaccess from the install directory to public_html

6. Edit require('./wp-blog-header.php'); to ('./installdir/wp-blog-header.php')

7. You may need to make other changes, especially with the theme.

how to disable all wordpress 4.1 plugins via MySQL(phpmyadmin)

In the table wp_options, under the option_name column (field) find the active_plugins row, change the option_value field to:


How to convert all MyISAM WordPress tables in a database to InnoDB

For optimal WordPress database performance you will want to make sure all WordPress tables are using the InnoDB storage engine. To check if you have any WordPress tables using the older, slower, MyISAM storage engine, run the command below

mysql -e "SELECT concat(TABLE_SCHEMA, '.', TABLE_NAME) FROM information_schema.tables WHERE engine = 'MyISAM'"


The command above displays ALL tables in the MyISAM format. You DO NOT want to convert any mysql. tables, if they are MyISAM, leave them alone. What you want to change are any WordPress tables that are MyISAM.

You need to know what your WordPress database is named, then look at the output of the command, if there are tables listed then we can convert to InnoDB.

Below is an example of what the output will look like. I've shortened the list to make it easier to focus on.

mysql -e "SELECT concat(TABLE_SCHEMA, '.', TABLE_NAME) FROM information_schema.tables WHERE engine = 'MyISAM'"
| concat(TABLE_SCHEMA, '.', TABLE_NAME)                          |
DONT CHANGE| information_schema.ROUTINES                                    |
DONT CHANGE| information_schema.TRIGGERS                                    |
DONT CHANGE| information_schema.VIEWS                                       |
CHANGE!| WORDPRESS10.wp_commentmeta                                     |
CHANGE!| WORDPRESS10.wp_comments                                        |

We only want to change / focus on tables from a Wordpress DB, not the information_schema, or mysql_ tables, if you change those to InnoDB you will break things.

Assuming you have some MyISAM tables for wordpress, you can run the command below to get a list of all the tables that need to be converted, this command then sends that output to a file called alter.sql. REPLACE $WORDPRESS_tp with the actual database name.

mysql -e "SELECT concat('ALTER TABLE ',TABLE_NAME,' ENGINE=InnoDB;')
FROM Information_schema.TABLES 

You should totally check the contents of "alter.sql" before running the command below, if all the tables you want to convert are listed, then running this command will convert the LIVE DATABASE tables from MyISAM to INNODB. I've done this live and didn't take down my site, but you should totally make a DB backup before doing this, and do this during a low traffic / maintenance period.

mysql -f $WORDPRESS_tp < alter.sql

Much of this information was obtained / learned via - https://rtcamp.com/tutorials/mysql/myisam-to-innodb/. For more information / ways to convert WordPress tables to InnoDB, please check out their blog!