Empty page returned from Nginx with fastcgi caching (HEAD vs GET)

Written by - 2 comments

Published on - Listed in Linux Nginx


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:

Cached php page empty in browser

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:

  • Removed the cache (rm -rf /var/www/cache/*)
  • Launched the curl command with HEAD request method (curl -I) from above - X-Cache was a MISS
  • Launched the same curl command again - X-Cache was a HIT
  • Opened the same URL in browser: phpinfo was shown correctly

Solved!


Add a comment

Show form to leave a comment

Comments (newest first)

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.


RSS feed

Blog Tags:

  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