A while ago I wrote about Enable caching in Nginx Reverse Proxy (and solve cache MISS only). The article back then was focusing on caching on a reverse proxy setup, meaning it was a proxy_cache. But Nginx also has the possibility to cache FastCGI backends.
For a customer I wanted to enable this kind of caching (fastcgi_cache) for the PHP backend using PHP-FPM. The setup is pretty much the same as proxy_cache - just call it fastcgi_cache instead ;-)
In Nginx's http section:
##
# Caching settings
##
fastcgi_cache_path /var/www/cache levels=1:2 keys_zone=cachecool:100m max_size=1000m inactive=60m;
fastcgi_cache_key "$scheme$host$request_uri";
And in the vhost config file within the .php file extension location:
location ~ \.php$ {
try_files $uri =404;
default_type text/html; charset utf-8;
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_pass unix:/run/php5-fpm.sock;
fastcgi_next_upstream error timeout invalid_header http_500 http_503;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_param HTTP_X_REAL_IP $remote_addr;
# FastCGI Caching
fastcgi_cache cachecool;
fastcgi_cache_valid 200 60m;
fastcgi_cache_methods GET;
add_header X-Cache $upstream_cache_status;
}
Then reload Nginx.
So far so good, caching seems to work which I was able to test with curl:
$ curl -I https://app.example.com/phpinfo.php -v
* Hostname was NOT found in DNS cache
* Trying 1.2.3.4...
* Connected to app.example.com (1.2.3.4) port 443 (#0)
[...ssl stuff...]
> HEAD /phpinfo.php HTTP/1.1
> User-Agent: curl/7.35.0
> Host: app.example.com
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
* Server nginx/1.10.3 is not blacklisted
< Server: nginx/1.10.3
Server: nginx/1.10.3
< Date: Fri, 21 Apr 2017 06:16:56 GMT
Date: Fri, 21 Apr 2017 06:16:56 GMT
< Content-Type: text/html; charset=UTF-8
Content-Type: text/html; charset=UTF-8
< Connection: keep-alive
Connection: keep-alive
< Vary: Accept-Encoding
Vary: Accept-Encoding
< X-Cache: MISS
X-Cache: MISS
Second curl request:
$ curl -I https://app.example.com/phpinfo.php -v
* Hostname was NOT found in DNS cache
* Trying 1.2.3.4...
* Connected to app.example.com (1.2.3.4) port 443 (#0)
[...ssl stuff...]
> HEAD /phpinfo.php HTTP/1.1
> User-Agent: curl/7.35.0
> Host: app.example.com
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
* Server nginx/1.10.3 is not blacklisted
< Server: nginx/1.10.3
Server: nginx/1.10.3
< Date: Fri, 21 Apr 2017 06:16:57 GMT
Date: Fri, 21 Apr 2017 06:16:57 GMT
< Content-Type: text/html; charset=UTF-8
Content-Type: text/html; charset=UTF-8
< Connection: keep-alive
Connection: keep-alive
< Vary: Accept-Encoding
Vary: Accept-Encoding
< X-Cache: HIT
X-Cache: HIT
The X-Cache header confirmed me: I hit the cache. So it's working.
But when I wanted to check the same URL in the browser, I got slapped in the face:
The page is empty?!
I manually removed the Nginx cache...
$ rm -rf /var/www/cache/*
... and then refreshed the browser. phpinfo.php was shown correctly now!
What's the difference between my curl and my browser request? Note the -I parameter after the curl command. This uses the HEAD request method. HEAD only returns the HTTP headers, no body. Accessing a website in the browser (usually) uses the GET method. So here we have the difference and also the explanation why the body was empty, resulting in an empty page.
So how do I solve this? The solution (found on ServerFault.com) is actually pretty self-explanatory: The caching key also needs to contain the request method. Remember the setup in Nginx's http section?
fastcgi_cache_key "$scheme$host$request_uri";
Here I only set the $scheme (https), the $host (app.example.com) and the $request_uri (/phpinfo.php). The caching mechanism doesn't differ between a GET, a HEAD or even another request method like POST. So let's add this:
fastcgi_cache_key "$scheme$request_method$host$request_uri";
After a Nginx reload, I tried to reproduce the empty cache problem:
Solved!
LAlf from wrote on Jan 18th, 2023:
Thank you, man!
Joe from wrote on May 1st, 2019:
Hey, just wanted to thank you, this very issue I spent trying to figure out a week. Your page saved me. Thanks, such a tiny detail to miss.
AWS Android Ansible Apache Apple Atlassian BSD Backup Bash Bluecoat CMS Chef Cloud Coding Consul Containers CouchDB DB DNS Database Databases Docker ELK Elasticsearch Filebeat FreeBSD Galera Git GlusterFS Grafana Graphics HAProxy HTML Hacks Hardware Icinga Influx Internet Java KVM Kibana Kodi Kubernetes LVM LXC Linux Logstash Mac Macintosh Mail MariaDB Minio MongoDB Monitoring Multimedia MySQL NFS Nagios Network Nginx OSSEC OTRS Office OpenSearch PGSQL PHP Perl Personal PostgreSQL Postgres PowerDNS Proxmox Proxy Python Rancher Rant Redis Roundcube SSL Samba Seafile Security Shell SmartOS Solaris Surveillance Systemd TLS Tomcat Ubuntu Unix VMWare VMware Varnish Virtualization Windows Wireless Wordpress Wyse ZFS Zoneminder