Improving nginx Proxy + Fastcgi Page Caching Skip Cache Reasons

Many nginx users nowadays are enjoying the benefits of server side caching with a reverse proxy cache or fastcgi cache (this is what Kinsta use). I have the pleasure of working with a lot of these server configurations and sometimes you get some undesired caching behavior that you need to debug in the nginx virtual host!

Most nginx caching configurations do not provide a header for the reason why the page cache was skipped. The ones that do only have a singular reason but sometimes there can be multiple triggers that prevent the specific page (URL or URI) from being cached. This tutorial will show you how to add multiple reasons for skipping the nginx page cache. You will need access to your nginx virtual host or have your hosting provider make these changes.

Here is a traditional nginx virtual host snippet with fastcgi caching enabled without any cache skip reason headers added.

# cache by default
set $skip_cache 0;

# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
	set $skip_cache 1;
}
if ($query_string != "") {
	set $skip_cache 1;
}
# Don't cache uris containing the following segments
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
	set $skip_cache 1;
}
# Don't use the cache for logged in users or recent commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
	set $skip_cache 1;
}

location ~ \.php$ {
	try_files $uri =404;
	# add cache status
	add_header WP-Bullet-Fastcgi-Cache $upstream_cache_status;
	include fastcgi_params;
	fastcgi_pass unix:/run/php/php7.2-fpm.sock;
	fastcgi_split_path_info ^(.+\.php)(.*)$;
	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
	fastcgi_cache_bypass $http_secret_header $skip_cache;
	fastcgi_no_cache $skip_cache;
	fastcgi_cache WORDPRESS;
	fastcgi_cache_valid 404      1m;
	fastcgi_cache_valid  60m;
}

Here is a traditional nginx virtual host snippet with fastcgi caching enabled and includes the skip cache reason which is really useful for debugging!

# cache by default
set $skip_cache 0;

# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
	set $skip_cache 1;
	set $skip_reason "POST";
}
if ($query_string != "") {
	set $skip_cache 1;
	set $skip_reason "QueryString";
}
# Don't cache uris containing the following segments
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
	set $skip_cache 1;
	set $skip_reason "URI";
}
# Don't use the cache for logged in users or recent commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
	set $skip_cache 1;
	set $skip_reason "Cookie";
}

location ~ \.php$ {
	try_files $uri =404;
	# add cache status
	add_header WP-Bullet-Fastcgi-Cache $upstream_cache_status;
	# add the cache skip reason if relevant
	add_header WP-Bullet-Skip $skip_reason;
	include fastcgi_params;
	fastcgi_pass unix:/run/php/php7.2-fpm.sock;
	fastcgi_split_path_info ^(.+\.php)(.*)$;
	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
	fastcgi_cache_bypass $http_secret_header $skip_cache;
	fastcgi_no_cache $skip_cache;
	fastcgi_cache WORDPRESS;
	fastcgi_cache_valid 404      1m;
	fastcgi_cache_valid  60m;
}

According to the configuration above, it is always the last skip reason that will go inside of the WP-Bullet-Skip header. In order to get multiple skip cache reasons we need to concatenate (join together) these variables. That is possible using this technique which looks like this "${skip_reason}-POST". This concatenates the variables together so we can see multiple reasons for the nginx cache being skipped!

# cache by default
set $skip_cache 0;
# set default empty skip reason
set $skip_reason "";

# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
	set $skip_cache 1;
	set $skip_reason "${skip_reason}-POST";
}
if ($query_string != "") {
	set $skip_cache 1;
	set $skip_reason "${skip_reason}-QueryString";
}
# Don't cache uris containing the following segments
if ($request_uri ~* "/wp-admin/|/xmlrpc.php|wp-.*.php|/feed/|index.php|sitemap(_index)?.xml") {
	set $skip_cache 1;
	set $skip_reason "${skip_reason}-URI";
}
# Don't use the cache for logged in users or recent commenters
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
	set $skip_cache 1;
	set $skip_reason "${skip_reason}-Cookie";
}

location ~ \.php$ {
	try_files $uri =404;
	# add cache status
	add_header WP-Bullet-Fastcgi-Cache $upstream_cache_status;
	# add the cache skip reason if relevant
	add_header WP-Bullet-Skip $skip_reason;
	include fastcgi_params;
	fastcgi_pass unix:/run/php/php7.2-fpm.sock;
	fastcgi_split_path_info ^(.+\.php)(.*)$;
	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
	fastcgi_cache_bypass $http_secret_header $skip_cache;
	fastcgi_no_cache $skip_cache;
	fastcgi_cache WORDPRESS;
	fastcgi_cache_valid 404      1m;
	fastcgi_cache_valid  60m;
}

As always make sure the nginx virtual host is valid

nginx -t

If you see everythig is OK then reload nginx

sudo service nginx reload

Sources

Merging Variables in nginx