Now that’s a big claim but I can assure you its true for all three aspects. It doesn’t even require heavy customisation and the approach is based on standard plugins available on the WordPress plugin site. However like everything there’s a trade off with the approach and in this case its the loss of flexibility and dynamic behaviour. This isn’t an issue with static websites but if you’re running a blog then this solution isn’t for you (stuff like comments won’t work as this requires connectivity and feedback from WordPress). It’s up to you to decide whether my approach has merits for your use case. I offer no guarantees other than that I have applied the approach below to my own systems and for me it works. It’s very rough around the edges, I have been hacking some files and I haven’t rolled my changes into a nice shrink wrap form. Enough with the disclaimers let’s get going with an actual explanation of what I’m offering.
Security
WordPress suffers from the same problem that almost all Content Management Systems (CMS) suffer from, it has a unified code base for both content publication and content management. With WordPress (and similar systems) that share the same code base it is possible to hack the content management system through the content publication system. The content publication system is the aspect of the CMS that generates the pages if a visitor hits the site. The content publication system by its very nature is an open interface to the outside world and can therefor be hacked. By the fact that it shares code with the CMS system it is inevitable that also the CMS can be compromised in an attack on the content publication system. These hacks occur time and again and are endemic to the shared code approach so they will never go away. The only way of ensuring your CMS is not hacked through your content publication system is by separating the two. Now separation in a physical (code) sense is possible but requires a huge amount of effort and in effect means a different version of WordPress through a fork. This is not what I want to achieve, I have limited time and I can’t maintain my own version of WordPress and keep up with all the new functionality that the WordPress team cranks out all the time. Therefore I mean separation in a logical sense and this I achieve through the use of WP SuperCache. WP Super Cache turns your WordPress site/blog into a collection of static pages and it uses a .htaccess mod_rewrite approach to serve customers the static pages. It also has an option to serve page components like JS, CSS and images from a Content Delivery Network (CDN). My approach to separating the CMS from content publication is that I turn the WP Super Cache cache (pardon the pun) into its own virtual host in Apache and serve content in its static form from that Virtual Host. My visitors don’t need to access the WordPress installation to get to the content, the CMS and the content publication are logically separated. Now there’s a couple of tricks required for getting this up and running and I’ll explain these later in this post.
Speed
The approach of moving your page components into a CDN is well known and relatively straightforward to achieve with solutions like WP Super Cache or W3 Total Cache. Going one step further and moving your entire site, so including your html is a little less usual but that is what I have achieved. My test site (not this one) based on the standard twentyten theme now loads in 1.223 seconds of which 0.252 seconds is spent on the DNS lookups. The html and all other page components are served through Amazon Cloudfront using Origin Pull (but any other CDN can do the same, there is no Cloudfront specific trickery involved).
How it works
There’s a couple of code changes involved and some Apache and DNS configuration changes. What do you need:
- LAMP platform and WordPress. I used the most recent version of WordPress (3.1.2) at the time of writing. Hosting is done on Amazon EC2 with a CentOS 5.6 based system
- WP Super Cache plugin installed
- A CDN, I used Amazon Cloudfront
- Access to DNS for setting CNAME records
I’m assuming you have a functioning LAMP server. The following steps need to be executed:
- Create a virtual host in Apache for the WordPress site
- Install WordPress and WP Super Cache plugin
- Configure the WP Super Cache plugin
- Code hacks to the WP Super Cache plugin
- Set up your CDN
- Configure your DNS
- Test
We’re going to put the WordPress site in a directory called “wordpress” located in /var/www/html (CentOS/Fedora default) and create a special virtual host called cms.example.com:
<VirtualHost *:80> ServerName cms.example.com ServerAdmin admin@example.com DocumentRoot /var/www/html/wordpress LogLevel info ErrorLog logs/error_log TransferLog logs/access_log </VirtualHost>
Install WordPress in the /var/www/html/wordpress directory and configure it with the cms.example.com home/site url. Check that the installation completed sucessfully and you can access the admin interface at http://cms.example.com/wp-admin/. Install the WP Super Cache plugin as explained by the documentation.
Configure the WP Super Cache plugin as follows:
- Advanced settings:
- Cache hits to this website for quick access
- Use PHP to serve cache files
- 304 Not Modified browser caching. Indicate when a page has not been modified since last requested
- Cache rebuild. Serve a supercache file to anonymous users while a new file is being generated
- CDN settings:
- Enable CDN Support
- Off-site URL: http://cdn.example.com (where example.com is your own domain)
- Preload settings:
- Preload mode (garbage collection only on legacy cache files)
Create a new directory in your webroot, e.g. “cache”:
mkdir /var/www/html/cache
Set this up as a new virtual host in Apache, let’s call this new site cache.example.com:
<VirtualHost *:80> ServerName cache.example.com ServerAdmin admin@example.com DocumentRoot /var/www/html/cache/supercache/cms.example.com ErrorLog logs/error_log TransferLog logs/access_log </VirtualHost>
Restart Apache to get the new Virtual Hosts activated. Copy over the wp-content/themes/[theme-name] folder to your cache directory (/var/www/html/cache/supercache/cms.example.com) but only where it concerns css, js and images. You don’t need to copy over the php files as only the web page resources are required. The same applies for the wp-includes directory if your theme uses javascript files in the js subdirectory. Check if the pages come up ok if you access http://cache.example.com. If they do you’re fine, if not troubleshoot what the issue is, e.g. look at the Apache logs/error_log file.
After this we need to do some small code wrangling, it’s going to be ugly but small and we need the absolute path of the directory that we just created. Navigate to the plugin directory of your WordPress installation and enter the wp-super-cache directory. Open file “wp-cache-phase1.php” and at the top of the file just after the include( WPCACHEHOME . ‘wp-cache-base.php’); instruction add:
include( WPCACHEHOME . 'wp-cache-base.php'); $cache_path = "/var/www/html/cache/";
Save the file and open file “wp-cache-phase2.php”. At the top of the file, just after <?php add:
$cache_path = "/var/www/html/cache/";
In the same file look for function function wp_cache_get_ob(&$buffer) and in this function look for this sequence (around line 504):
} else {
$buffer = apply_filters( ‘wpsupercache_buffer’, $buffer );
// Append WP Super Cache or Live page comment tag
wp_cache_append_tag($buffer);
[/bash]
After this sequence add:
$buffer = str_replace("http://cms.example.com", "http://www.example.com", $buffer);
Reason for this is that WP Super Cache will generate pages based on its own site/home url (cms.example.com) and we need to replace this url with the actual site url (www.example.com). Hence the clumsy find and replace whilst the pages are generated by the Preload section of the WP Super Cache plugin. I’m sure it can be done nicer but I’m just proving a concept, not winning prices for clean code.
Set up your CDN so that it has two Distribution Points / Pull Zones or whatever you CDN provider calls them. One should be listening to www.example.com and have cache.example.com as its origin server and the other should be listening to cdn.example.com and also have cache.example.com as its origin server. Note the CNAME records the CDN generates for you, let’s assume the following:
- xyz.cloudfront.net –> www.example.com
- abc.cloudfront.net –> cdn.example.com
Go to your DNS setup and set up the following changes:
- Have the www subdomain (I’m assuming you already have this set up otherwise create a www CNAME record) refer to xyz.cloudfront.net
- Create a CNAME record for cdn.example.com and have this point at abc.cloudfront.net
Apply the DNS changes and wait for the changes to propagate. If you can do a successful dig on www.example.com and cdn.example.com and you get to see something like this you should be ok:
www.example.com. 3044 IN CNAME xyz.cloudfront.net. xyz.cloudfront.net. 60 IN CNAME xyz.ams1.cloudfront.net. xyz.ams1.cloudfront.net. 60 IN A 216.137.59.28 xyz.ams1.cloudfront.net. 60 IN A 216.137.59.54 xyz.ams1.cloudfront.net. 60 IN A 216.137.59.64 xyz.ams1.cloudfront.net. 60 IN A 216.137.59.115 xyz.ams1.cloudfront.net. 60 IN A 216.137.59.207 xyz.ams1.cloudfront.net. 60 IN A 216.137.59.216 xyz.ams1.cloudfront.net. 60 IN A 216.137.59.220 xyz.ams1.cloudfront.net. 60 IN A 216.137.59.254
Access your site at http://www.example.com/ and see if its working. If so start doing your performance tests and do some investigations with HTTP analysis tooling like HTTP Fox.
After you’ve established everything works fine you can make cms.example.com only accessible to yourself or your content editors, there is no real time dependency on WordPress anymore and the installation can be purely used for content management activities.