David Moreau Simard

2 minute read

Using tengine (an awesome fork of Nginx, check it out), I found that it would cache HEAD requests even when I thought I explicitly told it not to.

Here’s the long story short.

What I expected to work

In the http section of /etc/nginx/nginx.conf:

proxy_cache_min_uses 3;
proxy_cache_methods  GET;
proxy_cache_valid    200 10m;
proxy_cache_valid    403 404 1m;
proxy_cache_path     /var/cache/nginx/website/ levels=1:2 keys_zone=website:16m
                     loader_threshold=300 loader_files=200
                     max_size=20g;

Reading the above, I would expect nginx to only cache GET requests, as per configured through proxy_cache_methods after the third hit.

However, I would still get cache hits after the third HEAD request as well.

Confirming I’m not crazy

I confirmed the issue by adding a custom header in my vhost, /etc/nginx/sites-enabled/website:

location / {
	proxy_cache website;
	proxy_pass http://website;
	proxy_redirect off;
	proxy_set_header Host $host;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	
	# Custom header to show whether we're hitting cache or not
	add_header X-Proxy-Cache $upstream_cache_status;
}

What do you know, after the third HEAD request, I’d get a X-Proxy-Cache: HIT back from the webserver.

“Syntax sugar”

Now, looking closely at the documentation for proxy_cache_methods, you see the following:

If the client request method is listed in this directive then the response will be cached. “GET” and “HEAD” methods are always added to the list, though it is recommended to specify them explicitly. See also the proxy_no_cache directive.

No way. You tell it to cache only GET requests and it’ll add HEAD back in there ? This was further confirmed in an older mailing list e-mail from a nginx developer:

GET/HEAD is syntax sugar, i.e. you can not disable GET/HEAD even if you set just proxy_cache_methods POST;

The solution

If you find a better way, do tell me because otherwise this is what I ended up with. It works, though !

location / {
	proxy_cache website;
	# I TOLD YOU NOT TO CACHE HEAD REQUESTS !@#$%
	if ($request_method = HEAD ) {
     		set $nocache 1;
   	}
		
	proxy_pass http://website;
	proxy_redirect off;
	proxy_set_header Host $host;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	
	# Custom header to show whether we're hitting cache or not
	add_header X-Proxy-Cache $upstream_cache_status;
}