In which I show how to optimize Wordpress on Apache to handle a reasonable continuous load on a tiny free tier virtual machines, with only standard Ubuntu packages. [6 minutes]
I recently managed to do all this myself, and am writing it up as I haven't found a blog post that covers everything required to get Wordpress, Apache, mod_rewrite, mod_fastcgi, php-fpm and php-apc to play nicely together. There's plenty out there for nginx though.
Brandon's Blog on FastCGI with PHP Opcode Cache captures basic principle and explains the benefits. Opcode caching however provides most of the performance, so most sites will do fine with mod_php (no need for mod_fastcgi + php-fpm).
On free tier VMs from cloud providers, a stock Wordpress/mod_php install runs out of memory and CPU resources at just one request per second! One user opening all the archive posts or a vulnerability scanner or crawler can DOS your site for everyone else.
Configure Permalinks using mod_rewrite, since they're pretty and getting mod_rewrite to play nice with FastCGI is part of the trick.
Extract /usr/share/doc/php-apc/apc.php.gz to /srv/wordpress/apc.php and visit /apc.php to see how the cache is doing. If fragmentation is substantial then shm_size is too low. Restrict access to apc.php.
apc.shm_size = 64M in /etc/php5/conf.d/apc.ini, because the default 32MB cache is too small for Wordpress (once you open Admin). Also set PHP memory_limit to 64M. Also set apc.slam_defense = 0 to prevent piles of "Potential cache slam averted for key" in Apache's error.log. The the slam_defense setting is deprecated by the default apc.write_lock anyway.
Install the trunk version (supports WP 3.1+) of APC Object Cache Backend simply by placing object-cache.php in the wp-content directory. Simply dropping the file in enables Wordpress to cache complex PHP objects between requests and is also a huge win for little effort.
WARNING: Brandon's benchmarks found reqs/second performance is not improved by switching from mod_php to FastCGI, most of the performance gain came from opcode caching. I now only recommend using FastCGI+FPM in case of (a) many concurrent requests and (b) serving a lot of static files (not just PHP content) because it conserves memory better.
If you really want FastCGI+FPM, uncomment the "multiverse" lines in /etc/apt/sources.list and run apt-get update to make libapache2-mod-fastcgi available. Run sudo apt-get install libapache2-mod-fastcgi php5-cgi php5-fpm, which also enables mod_fastcgi and starts the "php5-fpm" service. For a micro instance, set FPM max_children to 6, max_requests to 500. Immortal PHP processes been known to go crazy after a while - after a couple of days I found one hogging 100% CPU - so don't let them live forever.
Add the following lines inside your WordPress VirtualHost, adapting "/srv/wordpress" to your DocumentRoot. It pretends that there exists a PHP5 executable called "/php5.fcgi". mod_fastcgi intercepts calls to php5.fcgi and passes them to FPM instead. Also enable the "actions" module to support the "Action" line.
The solution is to add a RewriteCond %{REQUEST_URI} !^/php5.fcgi just before the final RewriteRule . index.php [L] to prevent /php5.fcgi (the handler) from being re-written to /index.php, which then needs a handler (/php5.fcgi), which is then rewritten to /index.php, ad infinitum. The complete rule block looks as follows:
Now you are ready to a2enmod fastcgi && a2dismod php5 && service apache2 restart.
Try a test query. If anything goes wrong, you can immediately revert to mod_php5 by running a2enmod php5 && a2dismod fastcgi && service apache2 restart.
P.S. If you're using Elastic Load Balancer, check out my post on getting Apache to log the correct client IP.
I recently managed to do all this myself, and am writing it up as I haven't found a blog post that covers everything required to get Wordpress, Apache, mod_rewrite, mod_fastcgi, php-fpm and php-apc to play nicely together. There's plenty out there for nginx though.
Brandon's Blog on FastCGI with PHP Opcode Cache captures basic principle and explains the benefits. Opcode caching however provides most of the performance, so most sites will do fine with mod_php (no need for mod_fastcgi + php-fpm).
On free tier VMs from cloud providers, a stock Wordpress/mod_php install runs out of memory and CPU resources at just one request per second! One user opening all the archive posts or a vulnerability scanner or crawler can DOS your site for everyone else.
Basic Apache Settings
If you are using mod_php on a micro, first:- Set MaxClients from 150 down to 50 (Apache 2.2), StartServers in later versions.
- Set MaxConnectionsPerChild to 1000
- Set KeepAliveTimeout to 5
- Set MaxKeepAliveRequests to 30 (about two-thirds of the MaxClients value)
Software used
Required
- apache2 - standard Apache 2.2 webserver on Ubuntu 11.10
- php-apc - Alternative PHP Cache, providing shared opcode cache
- APC Object Cache Backend - WP can use APC to retain objects between requests.
- WP Super Cache - cache pages to static HTML
Optional
- libapache2-mod-fastcgi - FastCGI module capable of talking to php-fpm.
- php5-fpm - PHP FastCGI Process Manager (FPM) helps by sharing opcode cache.
Basic Optimizations
First install Wordpress from scratch under Apache + mod_php to a DocumentRoot of /srv/wordpress. On Ubuntu, run sudo apt-get install libapache2-mod-php5 php5-mysql php5-gd to get dependencies.Configure Permalinks using mod_rewrite, since they're pretty and getting mod_rewrite to play nice with FastCGI is part of the trick.
WP Super Cache
Install WP Super Cache from the plugin manager, using the default "PHP" caching. WP Super Cache is a huge win for minimal effort, serving cached HTML to anonymous users without regenerating the page.Opcode and Object Caching
Install the APC opcode cache with sudo apt-get install php-apc. Run php -r 'phpinfo();'|grep apc to check that it's enabled. APC causes PHP sub-processes to inherit a warm opcode cache, greatly conserving CPU resources.Extract /usr/share/doc/php-apc/apc.php.gz to /srv/wordpress/apc.php and visit /apc.php to see how the cache is doing. If fragmentation is substantial then shm_size is too low. Restrict access to apc.php.
apc.shm_size = 64M in /etc/php5/conf.d/apc.ini, because the default 32MB cache is too small for Wordpress (once you open Admin). Also set PHP memory_limit to 64M. Also set apc.slam_defense = 0 to prevent piles of "Potential cache slam averted for key" in Apache's error.log. The the slam_defense setting is deprecated by the default apc.write_lock anyway.
Install the trunk version (supports WP 3.1+) of APC Object Cache Backend simply by placing object-cache.php in the wp-content directory. Simply dropping the file in enables Wordpress to cache complex PHP objects between requests and is also a huge win for little effort.
Advanced micro-optimization
Set up mod_fastcgi with FPM
FPM takes PHP out of Apache, cutting startup memory for new Apache workers from 10MB to 2.5MB per process.WARNING: Brandon's benchmarks found reqs/second performance is not improved by switching from mod_php to FastCGI, most of the performance gain came from opcode caching. I now only recommend using FastCGI+FPM in case of (a) many concurrent requests and (b) serving a lot of static files (not just PHP content) because it conserves memory better.
If you really want FastCGI+FPM, uncomment the "multiverse" lines in /etc/apt/sources.list and run apt-get update to make libapache2-mod-fastcgi available. Run sudo apt-get install libapache2-mod-fastcgi php5-cgi php5-fpm, which also enables mod_fastcgi and starts the "php5-fpm" service. For a micro instance, set FPM max_children to 6, max_requests to 500. Immortal PHP processes been known to go crazy after a while - after a couple of days I found one hogging 100% CPU - so don't let them live forever.
Add the following lines inside your WordPress VirtualHost, adapting "/srv/wordpress" to your DocumentRoot. It pretends that there exists a PHP5 executable called "/php5.fcgi". mod_fastcgi intercepts calls to php5.fcgi and passes them to FPM instead. Also enable the "actions" module to support the "Action" line.
<IfModule !mod_php5.c> <IfModule mod_fastcgi.c> Alias /php5.fcgi /srv/wordpress/php5.fcgi FastCgiExternalServer /srv/wordpress/php5.fcgi -host 127.0.0.1:9000 AddHandler php-fpm .php Action php-fpm /php5.fcgi </IfModule> </IfModule>
Make mod_fastcgi work with mod_rewrite
The final trick is avoiding infinite recursion between mod_rewrite and mod_fastcgi, which shows up as lots of these in error.log: the Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary.The solution is to add a RewriteCond %{REQUEST_URI} !^/php5.fcgi just before the final RewriteRule . index.php [L] to prevent /php5.fcgi (the handler) from being re-written to /index.php, which then needs a handler (/php5.fcgi), which is then rewritten to /index.php, ad infinitum. The complete rule block looks as follows:
RewriteEngine On RewriteBase / RewriteRule ^index\.php$ - [L] RewriteRule ^files/(.+) wp-includes/ms-files.php?file=$1 [L] RewriteCond %{REQUEST_FILENAME} -f [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^ - [L] RewriteCond %{REQUEST_URI} !^/php5.fcgi RewriteRule . index.php [L]
Now you are ready to a2enmod fastcgi && a2dismod php5 && service apache2 restart.
Try a test query. If anything goes wrong, you can immediately revert to mod_php5 by running a2enmod php5 && a2dismod fastcgi && service apache2 restart.
P.S. If you're using Elastic Load Balancer, check out my post on getting Apache to log the correct client IP.
Good call setting max_children to 6 and max_requests to 500 - helped me out a bunch. Thanks!
ReplyDeleteIf you want to squeeze a little more performance from this setup, configure php-fpm to listen on a unix socket rather than a tcp socket, this avoids the TCP handshake delays.
ReplyDelete