WP-Super-Cache: bug fixing and PHP object destruction

If you use the WPTouch mobile plugin, or the preload function in my caching plugin, or noticed that annoying but random and (thankfully) rare “front page isn’t showing my front page” bug then you might like to try the development version of WP-Super-Cache located on this page.

Mobile plugins need to tell WP Super Cache what user agents they support. I documented the filters you can use (“cached_mobile_browsers” and others) to do this but I don’t think they’ve been used by any plugin. It’s not hard to do and I added code that checks for WPTouch so when you visit the WP Super Cache settings page it updates the mobile user agents. So far it works for me but please feel free to view this site on your mobile phone and tell me if it looks ok! I also added support for the theme switcher in WPTouch based on code posted on the wporg forum.

It appears that the “random post on the front page” problem is a side effect of how PHP works. WordPress incorrectly reported that the current page wasn’t a search page, even though it was. I put an extra bit of code in checking if $_GET is non empty and that fixed it, so far.

Just in case anyone else is interested, this is why is_search() fails randomly when run during PHP shutdown. When a PHP process shuts down it starts by killing off objects. Unfortunately this happens before PHP stops executing your code, something that changed after PHP4.

Anything that runs when the output buffer finishes or that is registered by register_shutdown_function() better not depend on objects or classes. That means no using $wpdb, the object cache may disappear or to be completely paranoid don’t expect $wp_query to be around either! The functions is_search(), is_feed() and other related WordPress functions depend on $wp_query so you should cache the values of those functions earlier in the process. I’m thinking of hooking into wp_head but that depends on the theme unfortunately. Not every theme uses that action.

Many years ago I changed the format of the cache “meta file” from an object to an array because of the way the PHP desctruction process works. More recently, but still two years ago I had to remove all calls to get_option() and update_option() in the output buffer handler because occasionally people saw the error, “Call to a member function get() on a non-object in cache.php” in their error log. The object cache object had been destroyed by PHP! That was a right pain to figure out as the code looked perfect yet didn’t work right some of the time.

To help figure out problems I’ve added a lot more debugging to the plugin too. If $wp_query disappears it’ll appear in your debug log, and preloading will generate a lot more messages now.

Next up is caching is_search(), is_feed(), is_single() etc. Where should those be cached? The “init” action is too early for is_search and probably others but I don’t want to depend on actions that may not be in a theme.

WP Super Cache 1.0

WP Super Cache is a fast page caching plugin for WordPress that can significantly speed up websites.

The first release of this plugin was in September 2009 so this has been a bit too long in the making. Back then the main caching plugin was WP Cache, which this plugin is based on, but now there are quite a few including W3 Total Cache, Hypercache, Quick Cache and many more.

Version 1.0 is an incremental upgrade from the previous release but it has a number of bugfixes and new features:

  1. The all new scheduler and admin bar link should please many people, especially those who want to clear the cache on their site at particular times. The “Delete Cache” admin bar link is something people have been asking about for as long as WordPress has had that bar!
  2. You can preload categories and taxonomies although this hasn’t really been tested as well as I’d like. It works for me but YMMV.
  3. There’s better support for mobile and https users now. Mobile support should scale a lot more than previously.
  4. The cache tester is fixed now and the plugin traps as many errors as possible. It’ll also spit out some helpful text if there is a problem. 99% of the time it’s because the server can’t request a page from itself.
  5. At the risk of annoying users who comment on your site you can make them appear to be anonymous users which will drastically help your server if they like commenting a lot. Unfortunately it stops the comment form populating with their details so it might be worth using an external comment system like Intense Debate or Disqus!
  6. The Advanced Settings page now lists a “do not cache page” secret key. Use this key to view any page of your site uncached.
  7. The cache file listing and delete links should work again now.
  8. And many many more bug fixes.

Once you upgrade go visit the settings page and check out what’s new there. The upgrade worked fine for me, but there’s no harm looking. Pay close attention to the new garbage collector. The scheduler is rather powerful and flexible so it’s worth setting up right.

If you have any problems please leave a comment here or use the support forum.

WP Super Cache Scheduler and Admin Bar Link

I made a few changes to WP Super Cache over the last week. The garbage collection user interface received an overhaul and it’s now possible to schedule the those jobs using a timer or a clock. It can also send you an email each time it runs!

Another major change was the addition of a “Delete Cache” link on the admin bar for logged in users. This may seem strange, but it will only show if you have disabled caching for known users. Unfortunately this is needed because of the security measures required. It only deletes the supercache file(s) for the current page, not any legacy files associated with it.

If you’re feeling adventurous go download the development version from this page and give it a whirl. I really need feedback that this works well so please leave a comment if you try it.

The version number won’t change and when the next release comes out you’ll still get the upgrade notification!

Bye Bye Digg, Hello Stephen Fry!

Last week The Hibbs Lupus Trust posted a tweet thanking various people after their site was mentioned in a tweet by Stephen Fry. Lupus is a horrible condition and the Trust aims to help raise awareness of it, support sufferers and help GPs diagnose it.

Lupus causes the body’s immune system to go into overdrive and starts to attack itself. It is believed that over 50,000 people throughout the UK suffer with Lupus of whom 90% are female. The symptoms are many and varied, and the condition often seems to mimic other diseases. This gives rise to difficulty in diagnosis and the condition can be overlooked for years, unless the GP or consultant is alert to the possibility of lupus.

The Trust’s website must have experienced a torrent of traffic because Mr Fry is followed by gazillions of people and the tweet was retweeted by over 100 of his followers. The website stayed up and I’m glad that WP Super Cache played a part in helping them.

I wanted to mark the occasion of a website getting a Frying and surviving so I changed the “Digg proof” message in the plugin to “Stephen Fry Proof”. Download the development version from this page if you want a look. The link in the admin page goes to the Trust’s tweet so I hope I can play a small part in spreading the word.

Of course, just because you’re using this plugin is no guarantee that your site will survive a mention by Mr Fry or any other celebrity on Twitter (especially if your hosting is simply too limited) but With WP Super Cache at least it has a fighting chance!

BONUS: In case you didn’t see it, here’s Stephen Fry ag caint as Gaeilge (talking in Irish) as he appeared on an Irish language tv programme a while back:

Preload categories and custom post content

The never ending journey to WP Super Cache 1.0 continues. Go grab the development version of the plugin on the download page as there have been a few changes since my last post (where I announced support for mixed https sites among other things).

Some of the changes:

  • The cache preloader will now preload tags and categories pages (and whatever other taxonomy you want by using the wp_cache_preload_taxonomies filter). It should also preload pages with custom post types, but I haven’t tested that yet as I don’t have any such pages here. I hope Adam does at least!
  • You should be able to delete files from the cache file listing again.
  • You can make “known users” anonymous so they’re served Supercache cache files. Logged in users will appear logged out and users who leave comments won’t have the comment form filled in however.
  • And quite a few more bugfixes.

Only thing is, I need your help. The development version is running on this multisite install of WordPress and it’s working fine but I really need feedback, especially before 1.0 comes out.

So, if you’re comfortable with WordPress please go visit the download page and install the development version. Thanks!

Speed up WordPress with Apache and Varnish

Varnish is an open source, state of the art web application accelerator.

What it does is make your existing site faster by caching requests so your web server doesn’t have to handle them. This helps because your web server may be a lumbering giant like Apache that is loaded up with extra functionality like PHP, the GD library, mod_rewrite and all the other tools you need to make your website. All these modules unfortunately make your general purpose web server slower and heavier so by avoiding it your site spits out pages much faster!

Varnish sits in front of your webserver. Most documentation I’ve read on the subject suggest having Apache listen on any port other than port 80 and then have Varnish listen on port 80 of the external IP address. There’s no need to do this as I configured Apache to listen on port 80 of the 127.0.0.1 or localhost address while Varnish sits on the external IP.

Installing Varnish

Setting up Varnish is fairly easy. I’m going to assume that you’re already using Apache and On a Debian based system just use this to install it (as root)

apt-get install varnish

Apache

You need to configure Apache first. It has to listen on port 80 of the localhost interface. Edit /etc/apache2/ports.conf and change the following settings:

NameVirtualHost 127.0.0.1:80
Listen 127.0.0.1:80

Normally Apache listens on port 80 of all interfaces so you’ll probably just have to add “127.0.0.1:” in front of the 80.

Varnish

By default Varnish won’t start. You need to edit /etc/default/varnish. Change the following options in that file:

START=yes

DAEMON_OPTS="-a EXTERNAL_IP_ADDRESS:80 \
             -T localhost:6082 \
             -f /etc/varnish/default.vcl \
             -S /etc/varnish/secret \
             -s file,/var/lib/varnish/$INSTANCE/varnish_storage.bin,1G"

Replace EXTERNAL_IP_ADDRESS with the IP of your external IP address.

Now edit /etc/varnish/default.vcl. The file should already exist but most of it is commented out. First of all change the “Backend default”:

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

This tells Varnish that Apache is listening on port 80 of the localhost interface.

I’m going to define several functions in the default.vcl now. Comments in the code should explain what most of it does.

# Called after a document has been successfully retrieved from the backend.
sub vcl_fetch {
    # Uncomment to make the default cache "time to live" is 5 minutes, handy 
    # but it may cache stale pages unless purged. (TODO)
    # By default Varnish will use the headers sent to it by Apache (the backend server)
    # to figure out the correct TTL. 
    # WP Super Cache sends a TTL of 3 seconds, set in wp-content/cache/.htaccess

    # set beresp.ttl   = 300s;

    # Strip cookies for static files and set a long cache expiry time.
    if (req.url ~ "\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$") {
            unset beresp.http.set-cookie;
            set beresp.ttl   = 24h;
    }

    # If WordPress cookies found then page is not cacheable
    if (req.http.Cookie ~"(wp-postpass|wordpress_logged_in|comment_author_)") {
        set beresp.cacheable = false;
    } else {
        set beresp.cacheable = true;
    }

    # Varnish determined the object was not cacheable
    if (!beresp.cacheable) {
        set beresp.http.X-Cacheable = "NO:Not Cacheable";
    } else if ( req.http.Cookie ~"(wp-postpass|wordpress_logged_in|comment_author_)" ) {
        # You don't wish to cache content for logged in users
        set beresp.http.X-Cacheable = "NO:Got Session";
        return(pass);
    }  else if ( beresp.http.Cache-Control ~ "private") {
        # You are respecting the Cache-Control=private header from the backend
        set beresp.http.X-Cacheable = "NO:Cache-Control=private";
        return(pass);
    } else if ( beresp.ttl < 1s ) {
        # You are extending the lifetime of the object artificially
        set beresp.ttl   = 300s;
        set beresp.grace = 300s;
        set beresp.http.X-Cacheable = "YES:Forced";
    } else {
        # Varnish determined the object was cacheable
        set beresp.http.X-Cacheable = "YES";
    }
    if (beresp.status == 404 || beresp.status >= 500) {
        set beresp.ttl = 0s;
    }

    # Deliver the content
    return(deliver);
}

sub vcl_hash {
    # Each cached page has to be identified by a key that unlocks it.
    # Add the browser cookie only if a WordPress cookie found.
    if ( req.http.Cookie ~"(wp-postpass|wordpress_logged_in|comment_author_)" ) {
        set req.hash += req.http.Cookie;
    }
}

# Deliver
sub vcl_deliver {
    # Uncomment these lines to remove these headers once you've finished setting up Varnish.
    #remove resp.http.X-Varnish;
    #remove resp.http.Via;
    #remove resp.http.Age;
    #remove resp.http.X-Powered-By;
}

# vcl_recv is called whenever a request is received
sub vcl_recv {
    # remove ?ver=xxxxx strings from urls so css and js files are cached.
    # Watch out when upgrading WordPress, need to restart Varnish or flush cache.
    set req.url = regsub(req.url, "\?ver=.*$", "");

    # Remove "replytocom" from requests to make caching better.
    set req.url = regsub(req.url, "\?replytocom=.*$", "");

    remove req.http.X-Forwarded-For;
    set    req.http.X-Forwarded-For = client.ip;

    # Exclude this site because it breaks if cached
    #if ( req.http.host == "example.com" ) {
    #    return( pass );
    #}

    # Serve objects up to 2 minutes past their expiry if the backend is slow to respond.
    set req.grace = 120s;
    # Strip cookies for static files:
    if (req.url ~ "\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$") {
        unset req.http.Cookie;
        return(lookup);
    }
    # Remove has_js and Google Analytics __* cookies.
    set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(__[a-z]+|has_js)=[^;]*", "");
    # Remove a ";" prefix, if present.
    set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");
    # Remove empty cookies.
    if (req.http.Cookie ~ "^\s*$") {
        unset req.http.Cookie;
    }
    if (req.request == "PURGE") {
        if (!client.ip ~ purge) {
                error 405 "Not allowed.";
        }
        purge("req.url ~ " req.url " && req.http.host == " req.http.host);
        error 200 "Purged.";
    }

    # Pass anything other than GET and HEAD directly.
    if (req.request != "GET" && req.request != "HEAD") {
        return( pass );
    }      /* We only deal with GET and HEAD by default */

    # remove cookies for comments cookie to make caching better.
    set req.http.cookie = regsub(req.http.cookie, "1231111111111111122222222333333=[^;]+(; )?", "");

    # never cache the admin pages, or the server-status page
    if (req.request == "GET" && (req.url ~ "(wp-admin|bb-admin|server-status)")) {
        return(pipe);
    }
    # don't cache authenticated sessions
    if (req.http.Cookie && req.http.Cookie ~ "(wordpress_|PHPSESSID)") {
        return(pass);
    }
    # don't cache ajax requests
    if(req.http.X-Requested-With == "XMLHttpRequest" || req.url ~ "nocache" || req.url ~ "(control.php|wp-comments-post.php|wp-login.php|bb-login.php|bb-reset-password.php|register.php)") {
        return (pass);
    }
    return( lookup );
}

Notes:

  1. Varnish caches Javascript and CSS files without the cache buster ?ver=xxxx parameter. Varnish doesn’t cache any url with a GET parameter so those files weren’t getting cached at all.
  2. The code removes the Cookies for Comments cookie after it checks for GET and HEAD requests. This improved caching significantly as web pages are not cached with and without that cookie. They are all cached without it. The cache hit/miss ratio went up significantly when I made these two changes.
  3. I have a private site on this server that requires login. I had to stop Varnish caching this site as the privacy plugin thought I wasn’t logged in. See the example.com code above.
  4. If pages were purged Varnish could store cached pages for much longer.

As I didn’t modify WordPress so it would issue PURGE commands there are probably issues with the cache keeping slightly stale pages cached but I haven’t seen it happen or receive complaints about that.

PHP

Since all requests to Apache come from the local server PHP will think that the remote host is the local server. By using an auto_prepend_file set in your php.ini or .htaccess file you can tell PHP what the real IP is with this code:

if ( isset( $_SERVER[ "HTTP_X_FORWARDED_FOR" ] ) ) {
        $_SERVER[ 'REMOTE_ADDR' ] = $_SERVER[ "HTTP_X_FORWARDED_FOR" ];
}

You’ll see a huge improvement if you use Apache, especially if you don’t use a full page caching plugin like WP Super Cache on your WordPress site.

To see exactly how well Varnish is working use varnishstat and watch the ratio of cache hit and miss requests. This will vary depending on your TTL and by how much time Varnish has had to populate the cache. You can also configure logging using varnishncsa as described on this page:

varnishncsa -a -w /var/log/varnish/access.log -D -P /var/run/varnishncsa.pid

Now use multitail to watch /var/log/varnish/access.log and your web server’s access log.

I used a number of sites for help when setting this up. Here are a few:

I have tried Nginx in the past but could not getting it working without causing huge CPU spikes as PHP went a little mad. In comparison, Varnish was simple to install and set up. Have you tried Varnish yet? How can I improve the code above?

Edit: It looks like someone else has done the hard work. I must give the WordPress Varnish plugin a go.

This plugin purges your varnish cache when content is added or edited. This includes when a new post is added, a post is updated or when a comment is posted to your blog.

Cookies for Comments 0.5.4

Cookies for Comments is a WordPress plugin that can be used alongside Akismet to significantly reduce or even kill completely comment spam.

Version 0.5.4 adds a rejections page in case human visitors trip over the cookie check. You can modify the message shown to visitors and it’s obviously only useful if you don’t use the cookie check in your .htaccess file.

It happens sometimes, usually when the visitor has cookies turned off, but very occasionally for some other reason. (That I don’t know of yet)

This plugin isn’t a replacement for Akismet as it is easily bypassed. Thankfully 99% of spam bots don’t bother checking so this plugin works quite well!

Get a sneak preview of WP Super Cache before 1.0!

WP Super Cache 1.0 will be out soon(ish) but I need testers to bang on the development version available on the download page.

This version should be significantly faster if you have a lot of mobile visitors, plus it adds support for https secure pages. The speed came about by saving those files as static files in the supercache directories rather than the old “legacy caching” of WP Cache. You’ll see files like index-mobile.html(.gz) and index-https.html(.gz) in those directories. Be aware that if you use a flat permalink structure the plugin opens the root supercache directory for reading to find the index* files. This may or may not be an issue with directories containing a very large number of directories (10,000+ for example).

HTTPS support is built into the mod_rewrite rules so those files are served by the web server directly, but PHP serves the mobile clients. Only now you’ll be able to cache many times more mobile pages than before. There’s negligible speed difference between PHP and mod_rewrite modes anyway in normal operation.

There’s also a handy little “donotcachepage” GET parameter now. Add that, giving it the value of a secret key from the Advanced settings page and the current page will not be cached in any way.

There is lots of new code in the plugin. It’s running fine here, and a few other people have had no problems but I need more feedback. If you have a development server please give this a go, and if it works ok I believe it’s good enough to go live, under supervision. Don’t go install it on a Friday afternoon…

With the upgrade to WordPress 3.2 I moved this site and my photoblog to a Multisite install so I’ve been looking at making Supercache work better in that environment too.

I also installed Varnish as a front end cache and woah, I’m impressed. Setup was fairly easy though it’s still not 100% right. When I make posts the following page is a blank page except for the blog title, and when I (as a logged in user) leave a comment I get a 500 error and wp-comments-post.php doesn’t redirect. I’m still trying to debug those issues. I mentioned this on Google Plus a few days ago but expect a follow up post on Varnish with docs in the near future.

Now all I need is traffic. I should blog more!

Howto: Build a contact form with a Polldaddy survey

I just added a contact form to the about page here using a Polldaddy survey. While it’s not as straight forward as installing a plugin to do the job, I think it’s worth doing because it touches on all aspects of Polldaddy survey creation. After you’ve created this contact form on your own blog you’ll know how to create a Polldaddy survey, a custom style sheet and how to change the language in the form too. It’s very flexible.

Here’s how I did it.

  1. Login to Polldaddy and on the dashboard create a new survey for your contact form.
  2. Give your form a descriptive name and select the custom stylesheet. You’ll have to create a new one. I use the WordPress 2010 theme so I based my stylesheet on the Plain White theme. Changes are minor, mainly to accommodate width and font size. Grab the css file here and copy it into your style.
  3. Now on to the questions. I created a simple Name, Email and Comment form.
  4. You need to tell the survey where to send responses. After saving, go to Reports->Data and scroll down to the Email Notifications where you can fill in your details. You can also subscribe to an rss feed or send responses to a HTTP URL.

  5. To embed the form in your website use the embed popup and choose “Website Inline”. The iframe code should be copied into the new page that will hold your contact form.

  6. You’ll have a form that looks like this.

  7. You’re not finished yet though. Submit the form and you’ll see the message, “Survey Completed”. That’s not exactly appropriate for a contact form is it? Go to the languages page and create a new Survey Pack. You can change just about every bit of text displayed in the form here. After you’ve saved the language pack go back to the edit survey page and select the correct language pack:
  8. The one final job to do is to adjust the iframe size. I made each field of the form mandatory but that raises errors when you submit an empty form. Those errors make the form longer than the default and the iframe is too small to hold it. I bumped the height to 900 pixels and no more ugly scrollbars! There’s more empty whitespace below the form but my contact form is at the end of the page so I don’t mind.

You could also use the Javascript embed method, but that loads the survey form in a css popup window. I prefer the iframe method.

As you can see, Polldaddy surveys are incredibly flexible and offer a lot of customization options. I work on Polldaddy code every day so of course I’ll say this but I’d have no hesitation in recommending the service to anyone needing polls, surveys, quizzes or ratings. Create a free account and give it a spin!