Improve site performance - Adding Expires Headers


I’m using YSlow on Firebug, it suggests Adding Expires Headers, what do you think of adding the following to .htaccess?

<IfModule mod_expires.c> ExpiresActive On ExpiresByType text/html M3600 ExpiresByType text/css M3600 ExpiresByType application/javascript M3600 ExpiresByType image/bmp M3600 ExpiresByType image/gif M3600 ExpiresByType image/x-icon M3600 ExpiresByType image/jpeg M3600 ExpiresByType image/png M3600 </IfModule>

I find this whole subject a bit confusing, I’ve just read this which suggested writing it a bit differently

ExpiresActive on <FilesMatch "\.(gif|jpe?g|png|css|js)$"> ExpiresDefault "access plus 2 weeks" </FilesMatch>

and this page it is very interesting but I’m not exactly sure what I should be doing! It also talks about entity tags (ETags), which I graded poorly on with YSlow.

So if you can advise me about ETags too please?

Three other things that YSLow reported, can you help improve any of them?

  • Grade F on Use cookie-free domains
  • Grade F on Use a Content Delivery Network (CDN) [EDIT I’ve been recommended Amazon Cloud]
  • Grade D on Minify JavaScript and CSS

There are 4 components that can be minified


I don’t understand the last one because the .htaccess includes:

AddOutputFilterByType DEFLATE text/html text/plain text/xml text/javascript text/css application/javascript

And why only 4 components? I guess the others must be minified already?

Thank you for reading :slight_smile: Finally, If I do need to tweak the .htaccess file does it matter the order that things are entered?


I’m expiring my images the way it’s done in your first example, but I’m using a much longer retention period. “M3600” means to expire the object from your cache 3600 seconds after it was last modified. One hour (3600 seconds) will in fact cut down on your server load. But given that these files rarely if ever change, there is no need to expire it so frequently. I use three months from access for images:

ExpiresByType image/gif A7776000
ExpiresByType image/png A7776000
ExpiresByType image/jpg A7776000
ExpiresByType image/jpeg A7776000


thank you.

Yes I’ll increase the time for images, I’ll try this:

<IfModule mod_expires.c> ExpiresActive On ExpiresDefault A300 ExpiresByType image/x-icon A7776000 ExpiresByType image/bmp A7776000 ExpiresByType image/gif A7776000 ExpiresByType image/png A7776000 ExpiresByType image/jpg A7776000 ExpiresByType image/jpeg A7776000 ExpiresByType application/x-shockwave-flash A604800 ExpiresByType video/x-flv A604800 ExpiresByType application/pdf A3600 ExpiresByType application/x-javascript A3600 ExpiresByType text/css A3600 ExpiresByType text/plain A300 ExpiresByType text/html A300 </IfModule>

btw application/x-javascript or application/javascript or both?
and image/ico or image/x-icon or both?

Do you only apply expires headers to images?

I notice you are using Atime (access) instead of Mtime (modified), does it make much difference?

ETags, I read this page which suggests to turn off ETags:

Header unset ETag FileETag None
but also says [quote]Please don’t turn off ETags and Last-Modified headers for your .html files, leave one of them ON. (I use Last-Modified for .html).[/quote] and then doesn’t explain how to do that! Do you know how?

this page also suggests to turn ETags off with FileETag None but makes no mention of .html files

Finally do I need to be concerned about mod_headers?

the article I referred to earlier goes on to suggest this:

[code]# Turn on Expires and set default expires to 3 days
ExpiresActive On
ExpiresDefault A259200

Set up caching on media files for 1 month

<FilesMatch “.(ico|gif|jpg|jpeg|png|flv|pdf|swf|mov|mp3|wmv|ppt)$”>
ExpiresDefault A2419200
Header append Cache-Control “public”

Set up 2 Hour caching on commonly updated files

<FilesMatch “.(xml|txt|html|js|css)$”>
ExpiresDefault A7200
Header append Cache-Control “private, must-revalidate”

Force no caching for dynamic files

<FilesMatch “.(php|cgi|pl|htm)$”>
ExpiresDefault A0
Header set Cache-Control "no-store, no-cache, must-revalidate, max-age=0"
Header set Pragma “no-cache”

Thank you for feedback, maybe none of this makes too much odds, the danger of a little knowledge is that I always want to know more!


From the Apache mod_expires page:

“Note that if you use a modification date based setting, the Expires header will not be added to content that does not come from a file on disk. This is due to the fact that there is no modification time for such content.”

Given that images should be static, and that their their modified time would thus not change, I would think that specifying Mtime would result in the image effectively NEVER being cached beyond the period specified in the expires directive.

OTOH, I would use Mtime for other asset types, such as the /x- files, to ensure that new versions were picked up immediately.

I’ve only applied expires directives to images as yet because that’s all it took me to improve my YSlow grade. So I can’t speak with any knowledge on caching javascript or any of the /x- types.

ETags are not “low hanging fruit” from a price (effort) to performance ratio perspective. It’s pretty far down my list.

And keep in mind that the Yahoo list addresses little to nothing about application performance. Almost every dynamic site suffers more from a poorly performing database strategy than from CPU or bandwidth issues.


Thank you again.

Here’s what I can report after playing around with .htaccess and YSLow.

I found that adding an expires header for images only, meant my css and js were still flagged. So I included css and js on shorter times. According to YSLow the minimum [quote]expiration time must be at least 48 hours in the future[/quote] in fact I set it slightly over 48 hours ExpiresByType text/css A172900I am still getting a few elements coming up with not having expiration dates far enough in the future, one culprit is google analytics! This is set only 24 hours in the future. Another is which has no expires. The final few files that still get flagged up are on my home page where I have an image slideshow that uses timthumb.php
The expires date on this seems to be the present. But this is dynamic content not static - the images change depending on new blog entries. so perhaps a different approach is required?

[quote] * For static components: implement “Never expire” policy by setting far future Expires header
* For dynamic components: use an appropriate Cache-Control header to help the browser with conditional requests[/quote]

So perhaps I should add something like:

# Force no caching for dynamic files <FilesMatch "\.(php|cgi|pl|htm)$"> ExpiresDefault A0 Header set Cache-Control "no-store, no-cache, must-revalidate, max-age=0" Header set Pragma "no-cache" </FilesMatch>

The biggest improvement I found (a jump from Grade F to A in YSlow) came from adding the ETags code. Re my previous concern about turning off ETags AND Last-Modified headers, well as I’m only turning off ETags it’s fine. I used this code:Header unset ETag FileETag None

I also found that the combination of text/javascript and application/javascript picked up the most .js files, having application/x-javascript instead of application/javascript missed a few. For instance .min.js files.

Re my earlier comment about getting a Grade D on Minify JavaScript and CSS whilst having AddOutputFilterByType DEFLATE. I was confusing myself - of course I have a Grade A on gzip compression, but a Grade D on Minify because I must Minify the four files it is picking up - DEFLATE is not Minify, doh!

Finally I feel pretty happy with everything, I guess I have one thought which is bothering me; why use mime types? I mean why not use <FilesMatch "\.(ico|gif|jpg|jpeg|png)$"> ExpiresDefault A2592000 instead of writing ExpiresByType image/gif A2592000 ExpiresByType image/png A2592000 ExpiresByType image/jpg A2592000 ExpiresByType image/jpeg A2592000 ExpiresByType image/x-icon A2592000 Surely this would be simpler than debating whether it’s text/ or application/ or “x-” or whatever, using the file extensions seems a lot easier. Is there a reason it is done one way or the other?


I can’t help on timthumb.php. I suspect that the images are in fact cached, but YSlow might be getting confused by the php script in the image src attribute. Check the ‘Net’ -> ‘Images’ section of Firebug to confirm that you’re getting 304s on these images instead of 200s. If the latter, check with the timthumb.php authors.

# Force no caching for dynamic files
<FilesMatch "\.(php|cgi|pl|htm)$">
  ExpiresDefault A0
  Header set Cache-Control "no-store, no-cache, must-revalidate, max-age=0"
  Header set Pragma "no-cache"

php, cgi, and pl are server side scripts which can not get cached by the browser. The htm(l) output of those scripts is presumably dynamically generated and could change at any given moment. As such, I would leave out that whole block.

IIRC, the various javascript mime types are specified due to differences among browsers. Unless you’ve confirmed that your solution works across all expected browsers, I would use all three.

Finally, if the FilesMatch directive works, go for it. I can’t see that being anything other than a stylistic preference


interesting, thank you.

Firstly I got rid of that mod_headers code, thank you for the explanation. Then I cleared my browser’s cache and loaded the page, I got 200s on all images, when I refreshed I got 304s. The Cache-Control is reported as max-age=0. So I presume it’s working ok and YSlow is simply getting confused?

One thing I noticed which I don’t understand (actually there are many I noticed which I don’t understand!) is using Page Speed one of the things that comes up is Specify image dimensions for the timthumb.php images. This is odd I think because looking in NET > Images > Params it shows correct values for h and w. Why do you think it is asking to specify image dimensions?

I will have a little play around with the FilesMatch directive later, I’m interested to see how it works.

Thank you again.


That is my guess. 304s are what you’re getting, and what you want.

[quote]Why do you think it is asking to specify image dimensions?

Those are HTTP GET parameters to the timthumb.php script. Changing them changes the size of the images returned by the script. They are probably also setting width and height dimensions on the returned HTML IMG tags, which would force the browser to display the images at those dimensions, regardless of the actual size of the images


thank you again.

[quote=“coolgeek, post:8, topic:53528”]Those are HTTP GET parameters to the timthumb.php script. Changing them changes the size of the images returned by the script. They are probably also setting width and height dimensions on the returned HTML IMG tags, which would force the browser to display the images at those dimensions, regardless of the actual size of the images

therefore Page Speed flagging up “specify image dimensions” is nothing to be concerned with? because timthumb.php is creating an image at a certain dimension by referencing the source image file (of a different dimension) on the server?

Thank you


It means that there is nothing further that you can do to stop YSlow from complaining about this particular issue short of a) no longer using the script, or b) modifying the script’s behavior yourself.

Note that not even Yahoo gets a perfect score. What you have done so far is probably good enough, at least until your traffic gets over 100K hits per day.


Thank you Sir, I will be in touch once I reach 100K per day :wink:


Just some food for thought – W3 Total Cache for WordPress handles all of this sort of thing (Etags, expires headers, CDNs etc) and is much easier than faffing about with htaccess and Apache directives (although easiness is a relative thing here – it’s still not for the faint-hearted).

With a little work it should lift pretty much any WP installation into the 90s in Yslow and PageSpeed.


For what it’s worth, even if you don’t have a lot of traffic, setting up appropriate Expires headers can make pages load much faster on your site. This forum got much faster when I set up all the CSS and images with appropriate expiries.


hi, thank you for your thoughts. I did try W3 Total Cache and WP-Minify but didn’t really get either working.

My website has a large footer that normally loads after the main body, which is what I want. For some reason although W3TC improved the overall loading time of the page, it made everything load simultaneously, so the main body took ages to load!

with WP-Minify because it combines and minifies it didn’t help. Basically I don’t fully understand why but as soon as even two javascripts were combined some element on the page would stop working. I have some javascript in the header needed to register events and the rest in the footer. Combining even two of them didn’t work. I’m using jQuery plugins - cufon fonts,, and on the front page.

So in fact I installed minify and minified the four files getting picked up by YSlow, without combining them. The one thing I haven’t done is to specify my temp directory for best performance. [quote]Otherwise Minify will have to load extra code to guess $min_cachePath[/quote] because I didn’t know where to specify.

Now my front page with the s3slider which uses the timthumb.php gets a 75 and other pages get 85. fyi here’s a timthumb.php demo page

The only things I mark badly on are the expires headers for timthumb.php but we’ve covered that; Make fewer HTTP requests, can’t be helped I think because, as mentioned, I’ve tried to combine javascript files but it doesn’t work for me; Put Javascript at the bottom, I’ve put as many as I can at the bottom; and finally a CDN, I’ve looked at Amazon Cloud but I don’t think it’s something (a cost) I need until I get a lot more hits per day.

I think I’ve managed to achieve everything that W3TC might have done if I’d got it to work how I wanted, except for the CDN. All I’ve had to do is tweak my .htaccess file and install minify, I kind of prefer this to installing a plugin, it feels cleaner somehow. Can you foresee any problems with doing it this way? perhaps when upgrading WordPress?

Thank you for your time


I can’t stop tweaking!

re using FilesMatch or ExpiresByType, this page suggests using both.

re TimThumb, I updated to the latest version 1.15 from 1.09, much better than old version as it uses jpeg compression instead of png, files are nearly 1/10 the size!

I also found this page Fine tuning TimThumb though it refers to a version from the start of the year it is still interesting. Suggested [quote]Set client side caching

let’s find approximately a line 317 (with header Cache-Control) and set it to

header(“Cache-Control: max-age=315360000,public”);
Then for the next line (maybe 318, with Expires header) increase cache timeout to 315360000:

header("Expires: " . gmdate(“D, d M Y H:i:s”, time() + 315360000) . “GMT”);[/quote]

One thing I notice is that TimThumb images now return a 200 Status Code and not a 304. This is noticeable not just on my site but also the developer’s site. He has created a WordPress theme optimized for TimThumb, looking at the theme demo in FireBug the status codes are 200 for the images even after refreshes. Is this something to be concerned about?

Thank you


FYI new timthumb.php version. The project author Ben Gillbanks has updated timthumb.php code for improved caching (Expires Headers) and correct status codes (304), amongst other improvements. Download/copy latest version from

This thread should be renamed to include “updating timthumb.php” as it did rather digress towards that subject!



Can anyone help me set timthumb to expire in 31 days rather than 10?


define (‘CACHE_SIZE’, 1000); // number of files to store before clearing cache
define (‘CACHE_CLEAR’, 20); // maximum number of files to delete on each cache clear
define (‘CACHE_USE’, TRUE); // use the cache files? (mostly for testing)
define (‘VERSION’, ‘1.25’); // version number (to force a cache refresh)
define (‘DIRECTORY_CACHE’, ‘./cache’); // cache directory
define (‘MAX_WIDTH’, 1500); // maximum image width
define (‘MAX_HEIGHT’, 1500); // maximum image height
define (‘ALLOW_EXTERNAL’, FALSE); // allow external website (override security precaution - not advised!)
define (‘MEMORY_LIMIT’, ‘30M’); // set PHP memory limit
define (‘MAX_FILE_SIZE’, 1500000); // file size limit to prevent possible DOS attacks (roughly 1.5 megabytes)
define (‘CURL_TIMEOUT’, 10); // timeout duration. Tweak as you require (lower = better)

// external domains that are allowed to be displayed on your website
$allowedSites = array (’,’,’,’,’,’,’,’,

// --------------------

// sort out image source
$src = get_request (‘src’, ‘’);
if ($src == ‘’ || strlen ($src) <= 3) {
display_error (‘no image specified’);

// clean params before use
$src = clean_source ($src);

// get mime type of src
$mime_type = mime_type ($src);

// used for external websites only
$external_data_string = ‘’;

// generic file handle for reading and writing to files
$fh = ‘’;

// check to see if this image is in the cache already
// if already cached then display the image and die
check_cache ($mime_type);

// cache doesn’t exist and then process everything
// check to see if GD function exist
if (!function_exists (‘imagecreatetruecolor’)) {
display_error (‘GD Library Error: imagecreatetruecolor does not exist - please contact your webhost and ask them to install the GD library’);

if (function_exists (‘imagefilter’) && defined (‘IMG_FILTER_NEGATE’)) {
$imageFilters = array (
1 => array (IMG_FILTER_NEGATE, 0),
2 => array (IMG_FILTER_GRAYSCALE, 0),
3 => array (IMG_FILTER_BRIGHTNESS, 1),
4 => array (IMG_FILTER_CONTRAST, 1),
5 => array (IMG_FILTER_COLORIZE, 4),
6 => array (IMG_FILTER_EDGEDETECT, 0),
7 => array (IMG_FILTER_EMBOSS, 0),
10 => array (IMG_FILTER_MEAN_REMOVAL, 0),
11 => array (IMG_FILTER_SMOOTH, 0),

// get standard input properties
$new_width = (int) abs (get_request (‘w’, 0));
$new_height = (int) abs (get_request (‘h’, 0));
$zoom_crop = (int) get_request (‘zc’, 1);
$quality = (int) abs (get_request (‘q’, 90));
$align = get_request (‘a’, ‘c’);
$filters = get_request (‘f’, ‘’);
$sharpen = (bool) get_request (‘s’, 0);

// set default width and height if neither are set already
if ($new_width == 0 && $new_height == 0) {
$new_width = 100;
$new_height = 100;

// ensure size limits can not be abused
$new_width = min ($new_width, MAX_WIDTH);
$new_height = min ($new_height, MAX_HEIGHT);

// set memory limit to be able to have enough space to resize larger images
ini_set (‘memory_limit’, MEMORY_LIMIT);

if (file_exists ($src)) {

// open the existing image
$image = open_image ($mime_type, $src);
if ($image === false) {
    display_error ('Unable to open image : ' . $src);

// Get original width and height
$width = imagesx ($image);
$height = imagesy ($image);
$origin_x = 0;
$origin_y = 0;

// generate new w/h if not provided
if ($new_width && !$new_height) {
    $new_height = floor ($height * ($new_width / $width));
} else if ($new_height && !$new_width) {
    $new_width = floor ($width * ($new_height / $height));

// scale down and add borders
if ($zoom_crop == 3) {

	$final_height = $height * ($new_width / $width);

	if ($final_height > $new_height) {
		$new_width = $width * ($new_height / $height);
	} else {
		$new_height = $final_height;


// create a new true color image
$canvas = imagecreatetruecolor ($new_width, $new_height);
imagealphablending ($canvas, false);

// Create a new transparent color for image
$color = imagecolorallocatealpha ($canvas, 0, 0, 0, 127);

// Completely fill the background of the new image with allocated color.
imagefill ($canvas, 0, 0, $color);

// scale down and add borders
if ($zoom_crop == 2) {

	$final_height = $height * ($new_width / $width);
	if ($final_height > $new_height) {
		$origin_x = $new_width / 2;
		$new_width = $width * ($new_height / $height);
		$origin_x = round ($origin_x - ($new_width / 2));

	} else {

		$origin_y = $new_height / 2;
		$new_height = $final_height;
		$origin_y = round ($origin_y - ($new_height / 2));



// Restore transparency blending
imagesavealpha ($canvas, true);

if ($zoom_crop > 0) {

	$src_x = $src_y = 0;
	$src_w = $width;
	$src_h = $height;

	$cmp_x = $width / $new_width;
	$cmp_y = $height / $new_height;

	// calculate x or y coordinate and width or height of source
	if ($cmp_x > $cmp_y) {

		$src_w = round ($width / $cmp_x * $cmp_y);
		$src_x = round (($width - ($width / $cmp_x * $cmp_y)) / 2);

	} else if ($cmp_y > $cmp_x) {

		$src_h = round ($height / $cmp_y * $cmp_x);
		$src_y = round (($height - ($height / $cmp_y * $cmp_x)) / 2);


	// positional cropping!
	switch ($align) {
		case 't':
		case 'tl':
		case 'lt':
		case 'tr':
		case 'rt':
			$src_y = 0;

		case 'b':
		case 'bl':
		case 'lb':
		case 'br':
		case 'rb':
			$src_y = $height - $src_h;

		case 'l':
		case 'tl':
		case 'lt':
		case 'bl':
		case 'lb':
			$src_x = 0;

		case 'r':
		case 'tr':
		case 'rt':
		case 'br':
		case 'rb':
			$src_x = $width - $new_width;
			$src_x = $width - $src_w;


	imagecopyresampled ($canvas, $image, $origin_x, $origin_y, $src_x, $src_y, $new_width, $new_height, $src_w, $src_h);

} else {

    // copy and resize part of an image with resampling
    imagecopyresampled ($canvas, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);


if ($filters != '' && function_exists ('imagefilter') && defined ('IMG_FILTER_NEGATE')) {
    // apply filters to image
    $filterList = explode ('|', $filters);
    foreach ($filterList as $fl) {

        $filterSettings = explode (',', $fl);
        if (isset ($imageFilters[$filterSettings[0]])) {

            for ($i = 0; $i < 4; $i ++) {
                if (!isset ($filterSettings[$i])) {
					$filterSettings[$i] = null;
                } else {
					$filterSettings[$i] = (int) $filterSettings[$i];

            switch ($imageFilters[$filterSettings[0]][1]) {

                case 1:

                    imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1]);

                case 2:

                    imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2]);

                case 3:

                    imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3]);

                case 4:

                    imagefilter ($canvas, $imageFilters[$filterSettings[0]][0], $filterSettings[1], $filterSettings[2], $filterSettings[3], $filterSettings[4]);


                    imagefilter ($canvas, $imageFilters[$filterSettings[0]][0]);


// sharpen image
if ($sharpen && function_exists ('imageconvolution')) {

	$sharpenMatrix = array (
		array (-1,-1,-1),
		array (-1,16,-1),
		array (-1,-1,-1),

	$divisor = 8;
	$offset = 0;

	imageconvolution ($canvas, $sharpenMatrix, $divisor, $offset);


// output image to browser based on mime type
show_image ($mime_type, $canvas);

// remove image from memory
imagedestroy ($canvas);

// if not in cache then clear some space and generate a new file
clean_cache ();

die ();

} else {

if (strlen ($src)) {
    display_error ('image ' . $src . ' not found');
} else {
    display_error ('no source specified');



  • @global $quality

  • @param $mime_type

  • @param $image_resized
    function show_image ($mime_type, $image_resized) {

    global $quality;

    $cache_file = get_cache_file ($mime_type);

    if (strpos ($mime_type, ‘jpeg’) > 1) {
    imagejpeg ($image_resized, $cache_file, $quality);
    } else {
    imagepng ($image_resized, $cache_file, floor ($quality * 0.09));

    show_cache_file ($mime_type);



  • @param $property

  • @param $default

  • @return
    function get_request ($property, $default = 0) {

    if (isset ($_GET[$property])) {
    return $_GET[$property];
    } else {
    return $default;



  • @param $mime_type

  • @param $src

  • @return
    function open_image ($mime_type, $src) {

    if (strpos ($mime_type, ‘jpeg’) !== false) {
    $image = imagecreatefromjpeg ($src);
    } elseif (strpos ($mime_type, ‘png’) !== false) {
    $image = imagecreatefrompng ($src);
    } elseif (strpos ($mime_type, ‘gif’) !== false) {
    $image = imagecreatefromgif ($src);

    return $image;



  • clean out old files from the cache

  • you can change the number of files to store and to delete per loop in the defines at the top of the code

  • @return
    function clean_cache () {

    // add an escape
    // Reduces the amount of cache clearing to save some processor speed
    if (rand (1, 50) > 10) {
    return true;

    flush ();

    $files = glob (DIRECTORY_CACHE . ‘/*’, GLOB_BRACE);

    if (count ($files) > CACHE_SIZE) {

     $yesterday = time () - (24 * 60 * 60);
     usort ($files, 'filemtime_compare');
     $i = 0;
     foreach ($files as $file) {
     	$i ++;
     	if ($i >= CACHE_CLEAR) {
     	if (@filemtime ($file) > $yesterday) {
     	if (file_exists ($file)) {
     		unlink ($file);




  • compare the file time of two files

  • @param $a

  • @param $b

  • @return
    function filemtime_compare ($a, $b) {

    $break = explode (’/’, $_SERVER[‘SCRIPT_FILENAME’]);
    $filename = $break[count ($break) - 1];
    $filepath = str_replace ($filename, ‘’, $_SERVER[‘SCRIPT_FILENAME’]);

    $file_a = realpath ($filepath . $a);
    $file_b = realpath ($filepath . $b);

    return filemtime ($file_a) - filemtime ($file_b);



  • determine the file mime type

  • @param $file

  • @return
    function mime_type ($file) {

    $file_infos = getimagesize ($file);
    $mime_type = $file_infos[‘mime’];

    // no mime type
    if (empty ($mime_type)) {
    display_error (‘no mime type specified’);

    // use mime_type to determine mime type
    if (!preg_match ("/jpg|jpeg|gif|png/i", $mime_type)) {
    display_error ('Invalid src mime type: ’ . $mime_type);

    return strtolower ($mime_type);



  • @param $mime_type
    function check_cache ($mime_type) {

    if (CACHE_USE) {

     if (!show_cache_file ($mime_type)) {
     	// make sure cache dir exists
     	if (!file_exists (DIRECTORY_CACHE)) {
     		// give 777 permissions so that developer can overwrite
     		// files created by web server user
     		mkdir (DIRECTORY_CACHE);
     		chmod (DIRECTORY_CACHE, 0777);




  • @param $mime_type

  • @return
    function show_cache_file ($mime_type) {

    // use browser cache if available to speed up page load
    if (isset ($_SERVER[‘HTTP_IF_MODIFIED_SINCE’])) {
    if (strtotime ($_SERVER[‘HTTP_IF_MODIFIED_SINCE’]) < strtotime(‘now’)) {
    header (‘HTTP/1.1 304 Not Modified’);
    die ();

    $cache_file = get_cache_file ($mime_type);

    if (file_exists ($cache_file)) {

     // change the modified headers
     $gmdate_expires = gmdate ('D, d M Y H:i:s', strtotime ('now +31 days')) . ' GMT';
     $gmdate_modified = gmdate ('D, d M Y H:i:s') . ' GMT';
     // send content headers then display image
     header ('Content-Type: ' . $mime_type);
     header ('Accept-Ranges: bytes');
     header ('Last-Modified: ' . $gmdate_modified);
     header ('Content-Length: ' . filesize ($cache_file));
     header ('Cache-Control: max-age=864000, must-revalidate');
     header ('Expires: ' . $gmdate_expires);
     if (!@readfile ($cache_file)) {
     	$content = file_get_contents ($cache_file);
     	if ($content != FALSE) {
     		echo $content;
     	} else {
     		display_error ('cache file could not be loaded');
     die ();


    return FALSE;



  • @staticvar string $cache_file

  • @param $mime_type

  • @return string
    function get_cache_file ($mime_type) {

    static $cache_file;
    global $src;

    $file_type = ‘.png’;

    if (strpos ($mime_type, ‘jpeg’) > 1) {
    $file_type = ‘.jpg’;

    if (!$cache_file) {
    // filemtime is used to make sure updated files get recached
    $cache_file = DIRECTORY_CACHE . ‘/’ . md5 ($_SERVER [‘QUERY_STRING’] . VERSION . filemtime ($src)) . $file_type;

    return $cache_file;



  • @param $url
  • @return
    function validate_url ($url) {
    $pattern = '/^(([\w]+:)?//)?(([\d\w]|%[a-fA-f\d]{2,2})+(:([\d\w]|%[a-fA-f\d]{2,2})+)?@)?([\d\w][-\d\w]{0,253}[\d\w].)+[\w]{2,4}(:[\d]+)?(/([-+_~.\d\w]|%[a-fA-f\d]{2,2})
    return preg_match ($pattern, $url);


  • @global array $allowedSites

  • @param string $src

  • @return string
    function check_external ($src) {

    global $allowedSites;

    // work out file details
    $fileDetails = pathinfo ($src);
    $filename = ‘external_’ . md5 ($src);
    $local_filepath = DIRECTORY_CACHE . ‘/’ . $filename . ‘.’ . strtolower ($fileDetails[‘extension’]);

    // only do this stuff the file doesn’t already exist
    if (!file_exists ($local_filepath)) {

     if (strpos (strtolower ($src), 'http://') !== false || strpos (strtolower ($src), 'https://') !== false) {
     	if (!validate_url ($src)) {
     		display_error ('invalid url');
     	$url_info = parse_url ($src);
     	// convert youtube video urls
     	// need to tidy up the code
     	if ($url_info['host'] == '' || $url_info['host'] == '') {
     		parse_str ($url_info['query']);
     		if (isset ($v)) {
     			$src = '' . $v . '/0.jpg';
     			$url_info['host'] = '';
     	// check allowed sites (if required)
     	if (ALLOW_EXTERNAL) {
     		$isAllowedSite = true;
     	} else {
     		$isAllowedSite = false;
     		foreach ($allowedSites as $site) {
     			if (strpos (strtolower ($url_info['host']), $site) !== false) {
     				$isAllowedSite = true;
     	// if allowed
     	if ($isAllowedSite) {
     		if (function_exists ('curl_init')) {
     			global $fh;
     			$fh = fopen ($local_filepath, 'w');
     			$ch = curl_init ($src);
     			curl_setopt ($ch, CURLOPT_TIMEOUT, CURL_TIMEOUT);
     			curl_setopt ($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.5) Gecko/20041107 Firefox/1.0');
     			curl_setopt ($ch, CURLOPT_URL, $src);
     			curl_setopt ($ch, CURLOPT_RETURNTRANSFER, TRUE);
     			curl_setopt ($ch, CURLOPT_HEADER, 0);
     			curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
     			curl_setopt ($ch, CURLOPT_FILE, $fh);
     			curl_setopt ($ch, CURLOPT_WRITEFUNCTION, 'curl_write');
     			// error so die
     			if (curl_exec ($ch) === FALSE) {
     				unlink ($local_filepath);
     				touch ($local_filepath);
     				display_error ('error reading file ' . $src . ' from remote host: ' . curl_error ($ch));
     			curl_close ($ch);
     			fclose ($fh);
             } else {
     			if (!$img = file_get_contents ($src)) {
     				display_error ('remote file for ' . $src . ' can not be accessed. It is likely that the file permissions are restricted');
     			if (file_put_contents ($local_filepath, $img) == FALSE) {
     				display_error ('error writing temporary file');
     		if (!file_exists ($local_filepath)) {
     			display_error ('local file for ' . $src . ' can not be created');
     		$src = $local_filepath;
     	} else {
     		display_error ('remote host "' . $url_info['host'] . '" not allowed');

    } else {

     $src = $local_filepath;


    return $src;



  • callback for curl command to receive external images

  • limit the amount of data downloaded from external servers

  • @global $data_string

  • @param $handle

  • @param $data

  • @return
    function curl_write ($handle, $data) {

    global $external_data_string, $fh;

    fwrite ($fh, $data);
    $external_data_string .= $data;

    if (strlen ($external_data_string) > MAX_FILE_SIZE) {
    return 0;
    } else {
    return strlen ($data);



  • tidy up the image source url

  • @param $src

  • @return string
    function clean_source ($src) {

    $host = str_replace (‘www.’, ‘’, $_SERVER[‘HTTP_HOST’]);
    $regex = “/^(http(s|)://)(www.|)” . $host . “//i”;

    $src = preg_replace ($regex, ‘’, $src);
    $src = strip_tags ($src);
    $src = check_external ($src);

    // remove slash from start of string
    if (strpos ($src, ‘/’) === 0) {
    $src = substr ($src, -(strlen ($src) - 1));

    // don’t allow users the ability to use ‘…/’
    // in order to gain access to files below document root
    $src = preg_replace ("/…+//", “”, $src);

    // get path to image on file system
    $src = get_document_root ($src) . ‘/’ . $src;

    if (!is_file ($src)) {
    display_error (‘source is not a valid file’);

    if (filesize ($src) > MAX_FILE_SIZE) {
    display_error (‘source file is too big (filesize > MAX_FILE_SIZE)’);

    if (filesize ($src) <= 0) {
    display_error (‘source file <= 0 bytes. Possible external file download error (file is too large)’);

    return realpath ($src);



  • @param $src

  • @return string
    function get_document_root ($src) {

    // check for unix servers
    if (file_exists ($_SERVER[‘DOCUMENT_ROOT’] . ‘/’ . $src)) {
    return $_SERVER[‘DOCUMENT_ROOT’];

    // check from script filename (to get all directories to timthumb location)
    $parts = array_diff (explode (’/’, $_SERVER[‘SCRIPT_FILENAME’]), explode (’/’, $_SERVER[‘DOCUMENT_ROOT’]));

    $path = $_SERVER[‘DOCUMENT_ROOT’];

    foreach ($parts as $part) {
    $path .= ‘/’ . $part;
    if (file_exists ($path . ‘/’ . $src)) {
    return $path;

    // special check for microsoft servers
    if (!isset ($_SERVER[‘DOCUMENT_ROOT’])) {
    $path = str_replace ("/", “\”, $_SERVER[‘ORIG_PATH_INFO’]);
    $path = str_replace ($path, ‘’, $_SERVER[‘SCRIPT_FILENAME’]);

     if (file_exists ($path . '/' . $src)) {
         return $path;


    display_error (‘file not found’);



  • generic error message

  • @param $errorString
    function display_error ($errorString = ‘’) {

    header (‘HTTP/1.1 400 Bad Request’);
    echo ‘

    ’ . htmlentities ($errorString);
    echo '
    Query String : ’ . htmlentities ($_SERVER[‘QUERY_STRING’]);
    echo '
    TimThumb version : ’ . VERSION . ‘
    die ();


[code]RewriteCond %{HTTP_HOST} ^$
RewriteRule ^/?$ “” [R=301,L]
RewriteCond %{HTTP_HOST} ^$ [OR]
RewriteCond %{HTTP_HOST} ^$
RewriteRule ^wiki/$ “” [R=301,L]
RewriteCond %{HTTP_HOST} ^$ [OR]
RewriteCond %{HTTP_HOST} ^$
RewriteRule ^forums/$ “” [R=301,L]

suPHP_ConfigPath /home/danny/public_html/wiki

order allow,deny
deny from all

BEGIN W3TC Browser Cache

AddType text/css .css AddType application/x-javascript .js AddType text/html .html .htm AddType text/richtext .rtf .rtx AddType image/svg+xml .svg .svgz AddType text/plain .txt AddType text/xsd .xsd AddType text/xsl .xsl AddType text/xml .xml AddType video/asf .asf .asx .wax .wmv .wmx AddType video/avi .avi AddType image/bmp .bmp AddType application/java .class AddType video/divx .divx AddType application/msword .doc .docx AddType application/x-msdownload .exe AddType image/gif .gif AddType application/x-gzip .gz .gzip AddType image/x-icon .ico AddType image/jpeg .jpg .jpeg .jpe AddType application/ .mdb AddType audio/midi .mid .midi AddType video/quicktime .mov .qt AddType audio/mpeg .mp3 .m4a AddType video/mp4 .mp4 .m4v AddType video/mpeg .mpeg .mpg .mpe AddType application/ .mpp AddType application/vnd.oasis.opendocument.database .odb AddType application/vnd.oasis.opendocument.chart .odc AddType application/vnd.oasis.opendocument.formula .odf AddType application/ .odg AddType application/vnd.oasis.opendocument.presentation .odp AddType application/vnd.oasis.opendocument.spreadsheet .ods AddType application/vnd.oasis.opendocument.text .odt AddType audio/ogg .ogg AddType application/pdf .pdf AddType image/png .png AddType application/ .pot .pps .ppt .pptx AddType audio/x-realaudio .ra .ram AddType application/x-shockwave-flash .swf AddType application/x-tar .tar AddType image/tiff .tif .tiff AddType audio/wav .wav AddType audio/wma .wma AddType application/ .wri AddType application/ .xla .xls .xlsx .xlt .xlw AddType application/zip .zip ExpiresActive On ExpiresByType text/css A31536000 ExpiresByType application/x-javascript A31536000 ExpiresByType text/html A3600 ExpiresByType text/richtext A3600 ExpiresByType image/svg+xml A3600 ExpiresByType text/plain A3600 ExpiresByType text/xsd A3600 ExpiresByType text/xsl A3600 ExpiresByType text/xml A3600 ExpiresByType video/asf A31536000 ExpiresByType video/avi A31536000 ExpiresByType image/bmp A31536000 ExpiresByType application/java A31536000 ExpiresByType video/divx A31536000 ExpiresByType application/msword A31536000 ExpiresByType application/x-msdownload A31536000 ExpiresByType image/gif A31536000 ExpiresByType application/x-gzip A31536000 ExpiresByType image/x-icon A31536000 ExpiresByType image/jpeg A31536000 ExpiresByType application/ A31536000 ExpiresByType audio/midi A31536000 ExpiresByType video/quicktime A31536000 ExpiresByType audio/mpeg A31536000 ExpiresByType video/mp4 A31536000 ExpiresByType video/mpeg A31536000 ExpiresByType application/ A31536000 ExpiresByType application/vnd.oasis.opendocument.database A31536000 ExpiresByType application/vnd.oasis.opendocument.chart A31536000 ExpiresByType application/vnd.oasis.opendocument.formula A31536000 ExpiresByType application/ A31536000 ExpiresByType application/vnd.oasis.opendocument.presentation A31536000 ExpiresByType application/vnd.oasis.opendocument.spreadsheet A31536000 ExpiresByType application/vnd.oasis.opendocument.text A31536000 ExpiresByType audio/ogg A31536000 ExpiresByType application/pdf A31536000 ExpiresByType image/png A31536000 ExpiresByType application/ A31536000 ExpiresByType audio/x-realaudio A31536000 ExpiresByType application/x-shockwave-flash A31536000 ExpiresByType application/x-tar A31536000 ExpiresByType image/tiff A31536000 ExpiresByType audio/wav A31536000 ExpiresByType audio/wma A31536000 ExpiresByType application/ A31536000 ExpiresByType application/ A31536000 ExpiresByType application/zip A31536000 Header set Pragma "public" Header set Cache-Control "public, must-revalidate, proxy-revalidate" FileETag None Header set X-Powered-By "W3 Total Cache/" Header set Pragma "public" Header set Cache-Control "public, must-revalidate, proxy-revalidate" FileETag None Header set X-Powered-By "W3 Total Cache/" Header set Pragma "public" Header set Cache-Control "public, must-revalidate, proxy-revalidate" FileETag None Header set X-Powered-By "W3 Total Cache/" # END W3TC Browser Cache

BEGIN W3TC Page Cache

RewriteEngine On RewriteBase / RewriteCond %{HTTP_USER_AGENT} (2\.0\ mmp|240x320|alcatel|amoi|asus|au\-mic|audiovox|avantgo|benq|bird|blackberry|blazer|cdm|cellphone|danger|ddipocket|docomo|dopod|elaine/3\.0|ericsson|eudoraweb|fly|haier|hiptop|hp\.ipaq|htc|huawei|i\-mobile|iemobile|j\-phone|kddi|konka|kwc|kyocera/wx310k|lenovo|lg|lg/u990|lge\ vx|midp|midp\-2\.0|mmef20|mmp|mobilephone|mot\-v|motorola|netfront|newgen|newt|nintendo\ ds|nintendo\ wii|nitro|nokia|novarra|o2|openweb|opera\ mobi|opera\.mobi|palm|panasonic|pantech|pdxgw|pg|philips|phone|playstation\ portable|portalmmm|ppc|proxinet|psp|pt|qtek|sagem|samsung|sanyo|sch|sec|sendo|sgh|sharp|sharp\-tq\-gx10|small|smartphone|softbank|sonyericsson|sph|symbian|symbian\ os|symbianos|toshiba|treo|ts21i\-10|up\.browser|up\.link|uts|vertu|vodafone|wap|willcome|windows\ ce|windows\.ce|winwap|xda|zte) [NC] RewriteRule .* - [E=W3TC_UA:_low] RewriteCond %{HTTP_USER_AGENT} (acer\ s100|android|archos5|blackberry9500|blackberry9530|blackberry9550|cupcake|docomo\ ht\-03a|dream|htc\ hero|htc\ magic|htc_dream|htc_magic|incognito|ipad|iphone|ipod|lg\-gw620|liquid\ build|maemo|mot\-mb200|mot\-mb300|nexus\ one|opera\ mini|samsung\-s8000|series60.*webkit|series60/5\.0|sonyericssone10|sonyericssonu20|sonyericssonx10|t\-mobile\ mytouch\ 3g|t\-mobile\ opal|tattoo|webmate|webos) [NC] RewriteRule .* - [E=W3TC_UA:_high] RewriteCond %{HTTPS} =on RewriteRule .* - [E=W3TC_SSL:_ssl] RewriteCond %{SERVER_PORT} =443 RewriteRule .* - [E=W3TC_SSL:_ssl] RewriteCond %{REQUEST_METHOD} !=POST RewriteCond %{QUERY_STRING} ="" RewriteCond %{REQUEST_URI} \/$ RewriteCond %{REQUEST_URI} !(\/wp-admin\/|\/xmlrpc.php|\/wp-(app|cron|login|register|mail)\.php|wp-.*\.php|index\.php) [NC,OR] RewriteCond %{REQUEST_URI} (wp-comments-popup\.php|wp-links-opml\.php|wp-locations\.php) [NC] RewriteCond %{HTTP_COOKIE} !(comment_author|wp-postpass|wordpress_\[a-f0-9\]\+|wordpress_logged_in) [NC] RewriteCond %{HTTP_USER_AGENT} !(bot|ia_archive|slurp|crawl|spider|2\.0\ MMP|240x320|ASUS|AU-MIC|Alcatel|Amoi|Android|Audiovox|AvantGo|BenQ|Bird|BlackBerry|Blazer|CDM|Cellphone|DDIPOCKET|Danger|DoCoMo|Elaine/3\.0|Ericsson|EudoraWeb|Fly|HP\.iPAQ|Haier|Huawei|IEMobile|J-PHONE|KDDI|KONKA|KWC|KYOCERA/WX310K|LG|LG/U990|Lenovo|MIDP-2\.0|MMEF20|MOT-V|MobilePhone|Motorola|NEWGEN|NetFront|Newt|Nintendo\ Wii|Nitro|Nokia|Novarra|O2|Opera\ Mini|Opera\.Mobi|PANTECH|PDXGW|PG|PPC|PT|Palm|Panasonic|Philips|Playstation\ Portable|ProxiNet|Proxinet|Qtek|SCH|SEC|SGH|SHARP-TQ-GX10|SIE|SPH|Sagem|Samsung|Sanyo|Sendo|Sharp|Small|Smartphone|SoftBank|SonyEricsson|Symbian|Symbian\ OS|SymbianOS|TS21i-10|Toshiba|Treo|UP\.Browser|UP\.Link|UTS|Vertu|WILLCOME|WinWAP|Windows\ CE|Windows\.CE|Xda|ZTE|dopod|hiptop|htc|i-mobile|iPhone|iPod|nokia|portalmmm|vodafone) [NC] RewriteCond "/home/danny/public_html/wp-content/w3tc/pgcache/$1/_index%{ENV:W3TC_UA}%{ENV:W3TC_SSL}.html%{ENV:W3TC_ENC}" -f RewriteRule (.*) "/wp-content/w3tc/pgcache/$1/_index%{ENV:W3TC_UA}%{ENV:W3TC_SSL}.html%{ENV:W3TC_ENC}" [L] # END W3TC Page Cache

BEGIN WordPress

WPhtC: Disable ServerSignature on generated error pages

ServerSignature Off

WPhtC: Disable directory browsing

Options All -Indexes

WPhtC: Protect WP-config.php

order allow,deny deny from all

WPhtC: Setting mod_gzip

mod_gzip_on Yes mod_gzip_dechunk Yes mod_gzip_item_include file \.(html?|txt|css|js|php|pl)$ mod_gzip_item_include handler ^cgi-script$ mod_gzip_item_include mime ^text/.* mod_gzip_item_include mime ^application/x-javascript.* mod_gzip_item_exclude mime ^image/.* mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*

WPhtC: Setting mod_deflate

AddOutputFilterByType DEFLATE text/html text/plain text/xml application/xml application/xhtml+xml text/javascript text/css application/x-javascript BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4.0[678] no-gzip BrowserMatch bMSIE !no-gzip !gzip-only-text/html Header append Vary User-Agent env=!dont-vary

RewriteEngine on

Unless you have set a different RewriteBase preceding this point,

you may delete or comment-out the following RewriteBase directive:

RewriteBase /

if this request is for “/” or has already been rewritten to WP

RewriteCond $1 ^(index.php)?$ [OR]

or if request is for image, css, or js file

RewriteCond $1 .(gif|jpg|jpeg|png|css|js|ico)$ [NC,OR]

or if URL resolves to existing file

RewriteCond %{REQUEST_FILENAME} -f [OR]

or if URL resolves to existing directory

RewriteCond %{REQUEST_FILENAME} -d

then skip the rewrite to WP

RewriteRule ^(.*)$ - [S=1]

else rewrite the request to WP

RewriteRule . /index.php [L]

END WordPress[/code]