A couple of months ago I had amazing results from http://wordpress.org/extend/plugins/web-optimizer/ It worked with Hybrid News, WPtouch and Mobile edition all alongside each other. Other users have reported disasters of varying proportions so definitely try this on a sandpit site first and back up along the way. I did.
I have since gone off web-optimizer – it got too clever and too hard to keep it reliable & it kept trying to bookmark my pages in Firefox. Too hard. I am presently using http://wordpress.org/extend/plugins/wp-minify/ and Super-cache. I set the cache expiry for both to a couple of days. Once I stop tinkering (breaking things) I may set the times even longer. It’s not a busy site. I find Super-cache makes for faster page loads as long as it isn’t building the cache for the first viewing.
Dancing with the Devil – .htaccess
Something that has made a huge, I said H U G E, difference in page-loading times is setting long client-side expiry on everything I can and pre-Gzipping as much content as possible. These are set in .htaccess so you will need access to that. Be CAREFUL – even the slightest typo in .htaccess will take your whole site off the air – instantly! Always keep a backup / original working (!) .htaccess ready to roll-back to in a moment’s notice… if you’re crazy enough to experiment with it on a live site. Who me?
A good link to .htaccess info: http://www.samaxes.com/2009/01/more-on-compressing-and-caching-your-site-with-htaccess/
You will notice in the Hybrid etc. themes there are *.gz copies of CSS files etc. If you can serve these then that will cut a lot of overhead. Here’s how to do that (in .htaccess) (lines starting with # are comments)
RewriteEngine on #Check to see if browser can accept gzip files. If so and we have it - serve it!
ReWriteCond %{HTTP:accept-encoding} gzip
RewriteCond %{HTTP_USER_AGENT} !Safari #make sure there's no trailing .gz on the url
ReWriteCond %{REQUEST_FILENAME} !^.+\.gz$ #check to see if a .gz version of the file exists.
RewriteCond %{REQUEST_FILENAME}.gz -f #All conditions met so add .gz to URL filename (invisibly)
RewriteRule ^(.+) $1.gz [QSA,L] .
I put this rule after the Super-cache rules but before the rules that get the server to gzip something that isn’t already g-zipped. They get gzipped per-request so that can get expensive.
Until I get around to splitting this up a bit and making it more readable, I’ll make it a little less readable by posting most of my .htaccess file here. Warning – this works for me. Your mileage may vary.
RewriteEngine on
#Check to see if browser can accept gzip files. If so and we have it - serve it!
ReWriteCond %{HTTP:accept-encoding} gzip
RewriteCond %{HTTP_USER_AGENT} !Safari
#make sure there's no trailing .gz on the url
ReWriteCond %{REQUEST_FILENAME} !^.+\.gz$
#check to see if a .gz version of the file exists.
RewriteCond %{REQUEST_FILENAME}.gz -f
#All conditions met so add .gz to URL filename (invisibly)
RewriteRule ^(.+) $1.gz [QSA,L]
# If they accept gzip and there isn't one - make one
<IfModule mod_gzip.c>
mod_gzip_on Yes
mod_gzip_dechunk Yes
mod_gzip_keep_workfiles No
mod_gzip_can_negotiate Yes
mod_gzip_add_header_count Yes
mod_gzip_send_vary Yes
mod_gzip_command_version '/mod_gzip_status'
mod_gzip_min_http 1000
mod_gzip_minimum_file_size 300
mod_gzip_maximum_file_size 512000
mod_gzip_maximum_inmem_size 60000
mod_gzip_handle_methods GET POST
mod_gzip_temp_dir /tmp
mod_gzip_item_include file \.html$
mod_gzip_item_include file \.php$
mod_gzip_item_include file \.pl$
mod_gzip_item_include file \.rb$
mod_gzip_item_include file \.py$
mod_gzip_item_include file \.cgi$
mod_gzip_item_include file \.css$
mod_gzip_item_include file \.js$
mod_gzip_item_include mime ^application/javascript$
mod_gzip_item_include mime ^application/x-javascript$
mod_gzip_item_include mime ^text/.*
mod_gzip_item_include mime ^httpd/unix-directory$
mod_gzip_item_include handler ^cgi-script$
mod_gzip_item_include handler ^server-status$
mod_gzip_item_include handler ^server-info$
mod_gzip_item_include handler ^application/x-httpd-php
mod_gzip_item_exclude mime ^image/.*
# BEGIN Expire headers
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 7200 seconds"
ExpiresByType image/x-icon "access plus 2592000 seconds"
ExpiresByType image/jpeg "access plus 2592000 seconds"
ExpiresByType image/png "access plus 2592000 seconds"
ExpiresByType image/gif "access plus 2592000 seconds"
ExpiresByType application/x-shockwave-flash "access plus 2592000 seconds"
ExpiresByType text/css "access plus 2592000 seconds"
ExpiresByType text/javascript "access plus 2592000 seconds"
ExpiresByType application/x-javascript "access plus 2592000 seconds"
ExpiresByType text/html "access plus 7200 seconds"
ExpiresByType application/xhtml+xml "access plus 7200 seconds"
</IfModule>
# END Expire headers
# BEGIN Cache-Control Headers
<IfModule mod_headers.c> <FilesMatch "\\.(ico|jpe?g|png|gif|swf|gz)$">
Header set Cache-Control "max-age=2592000, public"
</FilesMatch>
<FilesMatch "\\.(css)$">
Header set Cache-Control "max-age=2592000, public"
</FilesMatch>
<FilesMatch "\\.(js)$">
Header set Cache-Control "max-age=2592000, private"
</FilesMatch>
<filesMatch "\\.(html|htm)$">
Header set Cache-Control "max-age=7200, public"
</filesMatch>
# Disable caching for scripts and other dynamic files
<FilesMatch "\.(pl|php|cgi|spl|scgi|fcgi)$">
Header unset Cache-Control
</FilesMatch>
</IfModule>
# END Cache-Control Headers
# Set the default handler.
DirectoryIndex index.php
# protect the htaccess file
<files .htaccess>
order allow,deny
deny from all
</files>
# disable the server signature
ServerSignature Off
# protect wpconfig.php
<files wp-config.php>
order allow,deny
deny from all
</files>
# disable directory browsing
Options All -Indexes
## protect from spam comments
RewriteEngine On
RewriteCond %{REQUEST_METHOD} POST
RewriteCond %{REQUEST_URI} .wp-comments-post\.php*
RewriteCond %{HTTP_REFERER} !.*cadbloke.com.* [OR]
RewriteCond %{HTTP_USER_AGENT} ^$
# Add known spammer ip ranges here
RewriteCond %{REMOTE_ADDR} ^66\.36\.\251.$ [OR]
# end known spammers range.
RewriteRule (.*) ^http://%{REMOTE_ADDR}/$ [R=301,L]
# for Windows Live Writer access
<Files xmlrpc.php>
SecFilterInheritance Off
</Files>
Good luck with that.
The astute observers amongst you will note that I added an IP address range selector to the spam protection block just above. That IP range has been spamming this blog so my apologies if you’re in it – get yourself to a reputable ISP. It’s your fault.
Here are some more resources for .htaccess. Remember – tread very carefully.
http://perishablepress.com/press/2006/01/10/stupid-htaccess-tricks/
http://zemalf.com/1076/blog-htaccess-rules/
… and more than you’ll ever want / need to know about .htaccess, right from the source …
http://www.askapache.com/htaccess/htaccess.html
To get an idea of what I’m talking about, check my site (http://www.CADbloke.com/) in Yslow (Firefox’s Firebug plugin). Under the “components” tab note that most (hopefully all) of it is gzipped and expires in at least a few days time. Also have a look under the “statistics” tab & note how many requests are needed for a primed cache. If you have a look at the Net tab you can see what is actually being loaded. Try it by loading the page normally once of twice, then Ctrl-F5 to clear the cache and reload it. See how much was cached? That’s what speeds page loads up and takes a huge load off your server.
Mostly unrelated: Irfanview is a free image viewer that has a save-for-web plugin which is very effective and eminently tweakable.
[update: March 24, 2010] Here are a couple more links to some WordPress speed-up tips.
http://wpwebhost.com/optimizing-wordpress-blog-for-speed/ (lots of other resources linked-to from here)
http://www.askapache.com/wordpress/fastest-caching-plugins.html
http://www.tripwiremagazine.com/2009/11/optimize-wordpress-for-professional-performance.html
Something else I want to look at is conditional loading of plugins, JavaScript & CSS as detailed in these posts
http://w-shadow.com/blog/2009/02/22/make-your-plugin-faster-with-conditional-tags/
http://yoast.com/conditional-thickbox-loading/
http://justintadlock.com/archives/2009/08/06/how-to-disable-scripts-and-styles#comment-145538
This would create a few more variations of minified CSS & JavaScript to be cached by local clients but would also take a load off visitors who never visit pages with a contact form or haven’t left a comment or whatever else an unused plug in is waiting to not-do. I’m still thinking about how worthwhile that might be. Perhaps I’m better off controlling this by user-agent IDs to lighten the load for mobile browsers. I don’t want to create too many variations of the locally-cached CSS & JavaScript since that would actually increase download times and use more bandwidth. Oh, the dilemma.
[EDIT] Another CSS speed-up is to optimise your style.css. Wherever you see an @import statement (usually at the top of the file), go find the CSS file being imported and pate the whole file in place of the @import statement. I left the @import statement in place, commented out. I also minified the CSS before pasting it so it is all on the same line. That makes it easier to replace when you update a theme later. This is especially so in the case of child themes when it imports a lot of CSS from the base theme – you will have to go and re-paste the @imported CSS every time you upgrade the base theme, hopefully not too often.
oh, btw – my site is a heavily butchered version of Hybrid News. Any feedback is more than welcome.
Hope this helps.
Ewen

