Setting HTTP Cache Headers with PHP

If you are just taking modern browsers into consideration, you need to set only "Cache-Control" and "ETag" headers. "Expires" and "Last-Modified" headers belong to an older HTTP specification and you can leave them out.

This tutorial will discuss how you can set "Cache-Control" and "ETag" headers through PHP.

For an understanding about HTTP caching in general, you can refer HTTP Cache Headers Explained and Practical Examples of Cache Headers.

Setting "Cache-Control" Header

Setting the "Cache-Control" header is direct. Just use the required directives for "Cache-Control" and send the header through the header function.

header('Cache-Control: max-age=86400');

Please note that you must use this function before any output from the script is emitted.

Setting "ETag" Header

Since the ETag header should be unique for a unique content, it requires a little logic. This tutorial dicusses one method based on timestamps through which you can create ETags — however this is just for example and you can use your own methods to create ETags. ETags based on a hash of the content are also popular.

2 timestamps can be related with a PHP script :

  • Generally PHP scripts send out dynamic content - for example some content from the database. That content usually has a last modified timestamp, which is also stored in the database.
  • Another timestamp is associated with the code contained in the PHP script - basically the timestamp when the PHP file was last modified.

As an example, see the below 2 codes. The first one is the content of a PHP file at some time. The second one is the content of the same PHP file, but after some time (someone made a modification to the code).

<?php
$content = 'Hello World';
?>
<html>
<head>
	<script src="analytics.js"></script>
</head>
<body>
	<?= $content ?>
</body>
</html>
<?php
$content = 'Hello World';
?>
<html>
<head>
	<script src="analytics.js"></script>
	<script src="ads.js"></script>
</head>
<body>
	<?= $content ?>
</body>
</html>

The main content that the user sees is same for both, but the content of the PHP files are different (an extra script tag). So while creating ETags based on last modification timestamp, we will have to consider this also.

So we set the ETag as the concatatenation of the last modification timestamp of the main content (that user sees) AND the last modification timestamp of the PHP file (in the server filesystem). If any of these timestamps change, the ETag is changed.

Last modification timestamp of the PHP file in the server file system can be retrieved through the filemtime function.

PHP Codes to Set Cache Headers

<?php

// Get last modification time of the current PHP file
$file_last_mod_time = filemtime(__FILE__);

// Get last modification time of the main content (that user sees)
// Hardcoded just as an example
$content_last_mod_time = 1520949851;

// Combine both to generate a unique ETag for a unique content
// Specification says ETag should be specified within double quotes
$etag = '"' . $file_last_mod_time . '.' . $content_last_mod_time . '"';

// Set Cache-Control header
header('Cache-Control: max-age=86400');

// Set ETag header
header('ETag: ' . $etag);

// Check whether browser had sent a HTTP_IF_NONE_MATCH request header
if(isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
	// If HTTP_IF_NONE_MATCH is same as the generated ETag => content is the same as browser cache
	// So send a 304 Not Modified response header and exit
	if($_SERVER['HTTP_IF_NONE_MATCH'] == $etag) {
		header('HTTP/1.1 304 Not Modified', true, 304);
		exit();
	}
}

// Rest of the code in the PHP script

?>