Nginx - create a cache based on upstream response time

The problem : be able to cache a backend response if it took more than 5 seconds. If not, don’t cache it!
A good challenge from @florentsolt. He solves the problem with a nodejs reverse proxy behind nginx, but I prefer a pure nginx implementation ;)

I needed that kind of configuration as a temporary workaround due to a proprietary backend which responds slowly to certain request, and quickly to anothers.
So instead of let the client wait 10 seconds or more, it’s better to render a cached response for next clients for 60 seconds, even if it’s not the more up to date data.

Finding no informations on the subject, here’s my solution (maybe not the better, but it worked for my needs). If you have a better solution (even with another software), you can add a comment :).

I use the “map” directive to set a variable and pass it via the X-Accel-Expires header (from man page : Parameters of caching can also be set directly in the response header. This has higher priority than setting of caching time using the directive. The “X-Accel-Expires” header field sets caching time of a response in seconds. ).

I use two vhosts for this trick, because we need to calculate the response time before send the response to the client.

proxy_cache_path /home/tmpcache/mycache levels=1:2 keys_zone=example:10m;

# If request_time > 3sec -> set a 60sec cache
map $request_time $longtime {
	"~^([3-9]|[1-9][0-9]+)\." 60;
	default 0;    
}

server {
	listen 127.0.0.1:8888;
	root /var/www/;
	server_name example.com; 
	location / { 
		proxy_redirect          off;
		proxy_set_header        Host $host;
		proxy_pass              http://mybackend;
		add_header X-Accel-Expires $longtime;
	}
}

server {
	listen 8080;
	root /var/www/;
	server_name example.com; 
	location / { 
		proxy_redirect          off;
		proxy_set_header        Host $host;
		proxy_pass              http://127.0.0.1:8888;
		proxy_cache             example;
		proxy_cache_valid       200 0m;
		add_header X-Cached $upstream_cache_status;
		add_header X-Time $request_time;
	}
}

A request to http://example.com:8080 will now force nginx to cache the response if it take more than 3 seconds via X-Accel-Expires: 60.
If request take less than 3 sec, a X-Accel-Expires: 0 is sent, which totally disable caching.

We can see it with curl :

# curl -I "http://example.com:8080/mylongrequest" 
HTTP/1.1 200 OK
Server: nginx/1.6.0
Date: Tue, 30 Sep 2014 16:43:35 GMT
Content-Type: text/xml
Connection: keep-alive
X-Cached: MISS
X-Time: 11.862

It took 11.8 sec ! On the next call :

# curl -I "http://example.com:8080/mylongrequest" 
HTTP/1.1 200 OK
Server: nginx/1.6.0
Date: Tue, 30 Sep 2014 16:45:05 GMT
Content-Type: text/xml
Connection: keep-alive
X-Cached: HIT
X-Time: 0.000

We hit the cache \o/