# Caching in Nginx: proxy_cache and fastcgi_cache in Practice

> How to set up caching in Nginx with proxy_cache and fastcgi_cache: cache zone, key, lifetime, and purging the cache for specific pages.

## 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](https://www.zevshost.net/en/kb/nginx-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](https://www.zevshost.net/en/kb/nginx-php-fpm-svyazka/). 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

| Directive | Purpose |
| --- | --- |
| proxy\_cache\_valid | lifetime of a response with a specific status code |
| proxy\_cache\_key | formula for the key used to look up a cache entry |
| proxy\_cache\_bypass | condition under which the cache is not read |
| proxy\_no\_cache | condition under which a response is not stored in the cache |
| proxy\_cache\_lock | one 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.

---

Source: https://www.zevshost.net/en/kb/nginx-proxy-cache-keshirovanie/
