Configure Nginx as a caching reverse proxy

Set up a bounded response cache, exclude personalized requests, inspect cache status, and manage expiry without confusing bypass with purge.

Best practices
Illustration of server operations and request logs

Define a cache path and shared index in http, then enable that zone in the proxied location. Start with content that is safe to share. A cached personalized response is a correctness problem even when it improves the hit rate.

Configure a public-content cache

This example keeps requests carrying cookies or Authorization out of both cache lookup and storage:

http {
    proxy_cache_path /var/cache/nginx/public
        levels=1:2
        keys_zone=public_cache:10m
        max_size=10g
        inactive=60m
        use_temp_path=off;

    map $http_cookie $has_cookie {
        "" 0;
        default 1;
    }
    map $http_authorization $has_auth {
        "" 0;
        default 1;
    }

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://127.0.0.1:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_cache public_cache;
            proxy_cache_key "$scheme$host$request_uri";
            proxy_cache_valid 200 302 10m;
            proxy_cache_valid 404 1m;
            proxy_cache_bypass $has_cookie $has_auth;
            proxy_no_cache $has_cookie $has_auth;
            proxy_cache_lock on;
            add_header X-Cache-Status $upstream_cache_status always;
        }
    }
}

Adapt the handler and cacheable statuses to the application. This is conservative: even a non-personalizing cookie bypasses the cache. If response selection depends on other inputs, account for them in cache variation or bypass rules before enabling caching.

Understand the settings

levels=1:2 spreads cache files across two directory levels. keys_zone stores the in-memory index, not all response bodies. max_size controls cache-manager cleanup rather than reserving disk or imposing an instantaneous hard ceiling. inactive=60m removes entries not accessed for that period, regardless of whether they remain fresh.

The explicit key includes the public host because the backend receives that host and may vary its response accordingly. The default key uses $proxy_host, not automatically the client-facing $host. Never assume those are interchangeable in a multi-tenant setup.

Upstream Cache-Control, Expires, and X-Accel-Expires can affect caching. Set-Cookie responses are not normally cached. Do not disable those protections merely to obtain more HITs.

Verify cache behavior

Request an eligible URL twice and inspect X-Cache-Status and logs. A MISS followed by HIT is useful evidence for that request, not a guarantee that every response is cacheable. Test cookies, Authorization, different hosts, query parameters, and error responses separately.

proxy_cache_bypass controls reading from cache; proxy_no_cache controls storing the fetched response. A response excluded from storage by its headers does not necessarily show BYPASS. Check both request conditions and upstream headers when investigating persistent misses.

Use stale content only where acceptable

proxy_cache_use_stale error timeout http_500 http_502;

This can serve an existing stale response during selected failures. Decide whether stale content is acceptable for the endpoint; it is a poor default for sensitive or rapidly changing state. proxy_cache_lock on reduces simultaneous population of the same new key, subject to its configured timeouts.

Plan invalidation and capacity

Use versioned asset URLs, a cache-key version, or a supported restricted purge interface. NGINX Plus and third-party purge modules have different syntax. Reloading Nginx is not a purge, and deleting a live cache directory is neither atomic nor free of load consequences.

Ensure the worker can use the cache and temporary directories and that the filesystem has enough free space. Monitor origin traffic after invalidation because a cold cache can produce a sudden load increase.

FAQ

Why is every request BYPASS?

Inspect the evaluated bypass variables. Set-Cookie and private cache-control responses concern storage eligibility and are not equivalent to a bypass-condition match.

Are POST requests cached?

By default, only GET and HEAD are cacheable. Adding POST requires a deliberate cache-key and response-semantics design, including request-body differences; simply adding the method can serve the wrong response.

How much faster will caching be?

Measure your workload. An eligible HIT avoids that origin fetch, but the effect on end-to-end latency and resource use depends on hit rate, response size, and the rest of the request path.

Reference: Nginx proxy cache directives.

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting