Skip to main content

Caching in Nginx: proxy_cache and fastcgi_cache in Practice

Nginx · 29.09.2026

Why cache responses at the Nginx level

A cache at the Nginx level stores an already-built backend response and serves it directly on the next request, without touching PHP-FPM or the proxied application. For a high-traffic site this removes most of the load from CPU and the database: a blog post or product page that does not change every second is rendered once and then served from memory or disk.

There are two cache types with the same logic but different sources: proxy_cache caches responses received through proxy_pass (see the article on reverse proxy), while fastcgi_cache caches responses from PHP-FPM received through fastcgi_pass. Configuring both directives is almost identical.

Configuring the cache zone

The cache zone is declared once in the http block and sets where files are stored and how much memory is allocated for the key index.

proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=proxy_cache:10m max_size=1g inactive=60m use_temp_path=off;

keys_zone=proxy_cache:10m is the zone name and the amount of memory for metadata, roughly 8000 keys per 1 MB. max_size=1g caps the cache size on disk, and inactive=60m removes files that have not been accessed for 60 minutes, even if they are not formally expired yet.

Using proxy_cache in a location

Inside the server block the zone is attached with the proxy_cache directive, and the response lifetime with proxy_cache_valid.

location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_cache proxy_cache;
    proxy_cache_valid 200 10m;
    proxy_cache_valid 404 1m;
    add_header X-Cache-Status $upstream_cache_status;
}

The X-Cache-Status header is the fastest way to debug this: values HIT, MISS, EXPIRED, and BYPASS immediately show whether the response came from the cache. Without this header, you can only guess whether the cache is working from the response time, which is far less reliable.

fastcgi_cache for PHP sites

For WordPress, Bitrix, and other CMS platforms on PHP-FPM, the cache is configured the same way, only through fastcgi_cache and with a key that accounts for the method and cookies.

fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=fcgi_cache:10m max_size=1g inactive=60m;

location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    fastcgi_cache fcgi_cache;
    fastcgi_cache_key "$scheme$request_method$host$request_uri";
    fastcgi_cache_valid 200 10m;
    fastcgi_cache_bypass $cookie_PHPSESSID;
    fastcgi_no_cache $cookie_PHPSESSID;
}

For the setup of the Nginx and PHP-FPM link itself, see the article on Nginx and PHP-FPM. Caching pages for logged-in users is usually unwanted: fastcgi_cache_bypass and fastcgi_no_cache keyed on the session cookie exclude the account area from the shared cache.

Cache parameters: what each one controls

DirectivePurpose
proxy_cache_validlifetime of a response with a specific status code
proxy_cache_keyformula for the key used to look up a cache entry
proxy_cache_bypasscondition under which the cache is not read
proxy_no_cachecondition under which a response is not stored in the cache
proxy_cache_lockone request goes to the backend, the rest wait for its result

proxy_cache_lock on; matters at the start of cache warmup: without it, a hundred simultaneous requests to a page that is not cached yet send a hundred identical requests to the backend at once, which is called a cache stampede.

Summary: caching checklist for Nginx

  • A cache zone is created with proxy_cache_path or fastcgi_cache_path in the http block.
  • Lifetime is set separately for successful and for error responses.
  • Pages with session cookies and the account area are excluded via *_no_cache and *_bypass.
  • The X-Cache-Status header is added for debugging and visible in a curl response.
  • proxy_cache_lock is enabled to avoid simultaneous requests hitting a cold cache.
← Back to Knowledge Base Ask Support