How to Configure Nginx Proxy Cache

Last updated Monday, May 18, 2026 10:12 am Joshua James 8 min read

Nginx proxy cache stores selected upstream responses on disk so repeated requests can be served from Nginx instead of hitting the backend every time. It is useful for public pages, static API responses, expensive generated content, and reverse-proxied applications that can safely reuse the same response for more than one visitor.

Proxy cache for proxy_pass reverse proxy traffic is separate from FastCGI cache for PHP-FPM and separate from Nginx open file cache, which caches file metadata instead of upstream HTTP responses. If the reverse proxy itself is not working yet, configure the Nginx reverse proxy first, then add caching after the backend returns correct responses.

Understand Nginx Proxy Cache Directives

Proxy cache needs one shared cache zone and one or more locations that use that zone. These directives handle the core cache path, cache key, response lifetime, bypass logic, stale-response behavior, and lock behavior.

DirectiveContextPurpose
proxy_cache_pathhttpDefines the disk path, shared memory zone, cache size, directory layout, and inactive cleanup window.
proxy_cachehttp, server, locationEnables a named cache zone for proxied responses.
proxy_cache_keyhttp, server, locationControls which request parts make one cache entry unique.
proxy_cache_validhttp, server, locationSets cache lifetimes for selected response status codes.
proxy_cache_bypasshttp, server, locationSkips reading from cache when any condition evaluates to a non-empty, non-zero value.
proxy_no_cachehttp, server, locationPrevents saving the current response to cache when a condition matches.
proxy_cache_use_stalehttp, server, locationAllows Nginx to serve an expired cached response during selected upstream failures.
proxy_cache_lockhttp, server, locationLets one request populate a missing cache entry while similar requests wait, which reduces cache stampedes.

By default, Nginx proxy cache is most practical for public GET and HEAD requests. Treat authenticated pages, dashboards, carts, checkout flows, account pages, and personalized API responses as bypass targets unless you have a precise cache key and clear application-level rules.

Do not add proxy cache to a production virtual host without a rollback path. A bad cache key or missing bypass rule can serve one visitor’s private response to another visitor.

Prepare Nginx for Proxy Cache

Confirm that Nginx is installed and available:

nginx -v

The command should print a line that starts with nginx version: nginx/. If the shell reports command not found, install Nginx before continuing.

Proxy cache also needs a writable cache directory. The Nginx worker user differs by package family, so detect it in the same block that creates the directory:

NGINX_USER=$(sudo nginx -T 2>/dev/null | awk '$1 == "user" {gsub(/;$/, "", $2); print $2; exit}')
if [ -z "$NGINX_USER" ]; then
    NGINX_USER=$(nginx -V 2>&1 | sed -n 's/.*--user=\([^[:space:]]*\).*/\1/p')
fi
if [ -z "$NGINX_USER" ]; then
    NGINX_USER=www-data
fi
printf 'Nginx worker user: %s\n' "$NGINX_USER"
sudo mkdir -p /var/cache/nginx/proxy_cache
sudo chown -R "$NGINX_USER": /var/cache/nginx/proxy_cache
sudo -u "$NGINX_USER" test -w /var/cache/nginx/proxy_cache && printf 'cache directory writable\n'

Debian and Ubuntu distro packages commonly return www-data, while Fedora, RHEL, Rocky Linux, and nginx.org packages commonly use nginx. Example output from a Debian-family package may look like this:

Nginx worker user: www-data
cache directory writable

Add an Nginx Proxy Cache Zone

The cache zone belongs in the http context, not inside an individual server block. On many packaged installs, a file in /etc/nginx/conf.d/ is loaded from the main http block, which makes it a practical place for shared cache settings.

Check whether the active Nginx configuration includes a conf.d file glob:

sudo nginx -T 2>/dev/null | grep -E 'include[[:space:]]+.*conf\.d/\*\.conf;'

Relevant output on common packaged layouts includes this line in the http context:

include /etc/nginx/conf.d/*.conf;

If no matching include appears in the http context of the full dump, add the cache zone inside the existing http block instead of creating an unused file. When the include is active, create a shared proxy cache configuration file:

sudo nano /etc/nginx/conf.d/proxy-cache.conf

Add this cache zone definition:

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

This creates a cache named proxy_cache. The keys_zone=proxy_cache:100m value stores cache keys and metadata in shared memory, while max_size=1g limits the disk cache. The inactive=60m value removes entries that have not been requested for 60 minutes, even if their normal cache lifetime has not expired yet.

Test the configuration before enabling cache in any site:

sudo nginx -t

A successful syntax test prints these lines:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Enable Nginx Proxy Cache for a Reverse Proxy

Start with the locations that can safely cache public responses. The example assumes your backend already runs on 127.0.0.1:3000. Replace that upstream with the backend used by your site.

Add these map blocks in the http context. They create skip-cache variables for non-GET requests, authenticated requests, and common session cookies:

map $request_method $skip_cache_method {
    default 1;
    GET 0;
    HEAD 0;
}

map $http_authorization $skip_cache_auth {
    default 1;
    "" 0;
}

map $http_cookie $skip_cache_cookie {
    default 0;
    ~*(session|wordpress_logged_in|comment_author) 1;
}

Then configure the reverse-proxy location to use the cache zone:

upstream app_backend {
    server 127.0.0.1:3000;
}

server {
    listen 80;
    server_name example.com www.example.com;

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;

        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_set_header X-Forwarded-Proto $scheme;

        proxy_cache proxy_cache;
        proxy_cache_key "$scheme$host$request_uri";
        proxy_cache_valid 200 301 302 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_lock on;
        proxy_cache_bypass $skip_cache_method $skip_cache_auth $skip_cache_cookie;
        proxy_no_cache $skip_cache_method $skip_cache_auth $skip_cache_cookie;

        add_header X-Cache-Status $upstream_cache_status always;
    }
}

The $request_uri variable includes the path and query string, so /products?page=1 and /products?page=2 become separate cache entries. The $host variable keeps different hostnames from sharing the same cached response when one upstream serves more than one domain.

Test and reload Nginx after editing the server block:

sudo nginx -t && sudo systemctl reload nginx

Verify Nginx Proxy Cache Hits and Misses

The X-Cache-Status header makes cache behavior visible while you test. Send the same request twice through Nginx:

curl -sI -H "Host: example.com" http://127.0.0.1/ | grep -E '^(HTTP/|X-Cache-Status:)'
curl -sI -H "Host: example.com" http://127.0.0.1/ | grep -E '^(HTTP/|X-Cache-Status:)'

Relevant output should show a cache miss followed by a cache hit:

HTTP/1.1 200 OK
X-Cache-Status: MISS
HTTP/1.1 200 OK
X-Cache-Status: HIT

If the second request still shows MISS, do not keep raising cache sizes. Check the status code, upstream response headers, cache bypass variables, and active location first. The Nginx access and error logs can also show whether the request reached the location you edited.

Control What Nginx Proxy Cache Stores

A useful proxy cache is selective. Cache public responses, skip private responses, and keep short lifetimes for error-prone or frequently updated routes.

Cache Different Nginx Status Codes for Different Durations

Use proxy_cache_valid to set different lifetimes by status code:

proxy_cache_valid 200 302 10m;
proxy_cache_valid 301 1h;
proxy_cache_valid 404 1m;

Short 404 caching can reduce repeated misses for missing static assets. Keep it conservative on dynamic sites where a URL might appear shortly after deployment.

Bypass Nginx Proxy Cache for Private Requests

The proxy_cache_bypass directive skips reading from cache, and proxy_no_cache prevents saving the current response. Use both when a request is private:

proxy_cache_bypass $skip_cache_method $skip_cache_auth $skip_cache_cookie;
proxy_no_cache $skip_cache_method $skip_cache_auth $skip_cache_cookie;

This pattern keeps authenticated requests, non-GET methods, and session-cookie requests from using or populating the shared cache. Adjust the cookie names to match your application.

Disable Nginx Proxy Cache for Admin and Account Paths

Some paths should never use a shared proxy cache. Add a more specific location before the general cached location when your application has dashboards, login pages, carts, or checkout flows:

location ~* ^/(admin|login|account|checkout)(/|$) {
    proxy_cache off;
    proxy_pass http://app_backend;
    proxy_http_version 1.1;

    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_set_header X-Forwarded-Proto $scheme;
}

If a route keeps matching the cached location instead, review your Nginx location block priority. Regex and prefix locations can change which cache rules handle a request.

Cache Only a Public Nginx Path

For APIs and applications with mixed private and public routes, cache only a narrow path instead of the full site:

location /api/public/ {
    proxy_pass http://app_backend;
    proxy_http_version 1.1;

    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_set_header X-Forwarded-Proto $scheme;

    proxy_cache proxy_cache;
    proxy_cache_key "$scheme$host$request_uri";
    proxy_cache_valid 200 5m;
    proxy_cache_bypass $skip_cache_method $skip_cache_auth $skip_cache_cookie;
    proxy_no_cache $skip_cache_method $skip_cache_auth $skip_cache_cookie;

    add_header X-Cache-Status $upstream_cache_status always;
}

This approach is safer than trying to cache an entire application and carve out every private exception later.

Serve Stale Nginx Proxy Cache During Upstream Problems

Stale cache can keep public pages available during short backend failures. Add proxy_cache_use_stale inside the cached location when serving an expired response is safer than returning an upstream error:

proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;

The updating option lets Nginx serve a stale response while another request refreshes it. The proxy_cache_lock option reduces the chance that many simultaneous requests stampede the backend when a popular cache entry expires.

Use stale cache for public content only. Serving an expired account page, checkout response, or personalized API result can be worse than showing a temporary error.

Avoid Common Nginx Proxy Cache Mistakes

Most proxy cache problems come from scope, not syntax. Review these rules before you enable cache broadly.

  • Do not cache authenticated, session-based, cart, checkout, or account responses unless the cache key and bypass rules are application-specific and tested.
  • Include $host in the cache key when one upstream serves more than one hostname.
  • Keep $request_uri in the cache key when query strings change the response.
  • Do not use proxy_ignore_headers to override upstream Cache-Control or Set-Cookie behavior unless you have verified that the application response is safe to share.
  • Do not expose a public cache purge endpoint unless your Nginx build supports one and the endpoint is strongly protected.
  • Monitor disk usage. Cache growth is controlled by max_size, but a full filesystem can still break logging, uploads, package updates, and other services.

If the cached site starts showing wrong content, disable cache for that location first, reload Nginx, and then investigate the cache key and bypass conditions. Do not keep experimenting on live traffic while a shared cache may be serving private responses.

Troubleshoot Common Nginx Proxy Cache Problems

Use the cache header, active Nginx config, and error logs together. One signal alone can point in the wrong direction.

SymptomLikely CauseFirst Check
X-Cache-Status missingThe request is handled by a different location or server blockCheck server_name, location matching, and active config output
Always MISSResponse is not cacheable, status code is not listed, or cache key changes each requestInspect response headers and request URL
Always BYPASSA bypass variable is non-empty or non-zeroCheck method, authorization header, and cookies
Wrong user content appearsCache key or bypass rules are too broadDisable cache for private paths immediately
Permission errorsNginx worker cannot write to the cache pathCheck cache directory ownership and error logs
Cache fills diskmax_size is too high or cache path shares a small filesystemCheck disk usage and reduce cache size

Find the Active Nginx Proxy Cache Configuration

Dump the active configuration and search for cache directives:

sudo nginx -T 2>/dev/null | grep -E 'proxy_cache_path|levels=|keys_zone=|max_size=|inactive=|use_temp_path|proxy_cache proxy_cache|proxy_cache_key|X-Cache-Status'

Relevant output should include the cache zone and the location-level cache settings:

proxy_cache_path /var/cache/nginx/proxy_cache
    levels=1:2
    keys_zone=proxy_cache:100m
    max_size=1g
    inactive=60m
    use_temp_path=off;
        proxy_cache proxy_cache;
        proxy_cache_key "$scheme$host$request_uri";
        add_header X-Cache-Status $upstream_cache_status always;

If these lines are missing, the file may not be included from nginx.conf, the server block may not be enabled, or the request may be hitting another virtual host. Check the active server_name values and the default server before assuming the cache directives are loaded.

Fix Nginx Proxy Cache Permission Errors

Check recent cache-related errors:

sudo tail -n 100 /var/log/nginx/error.log | grep -i cache

Relevant error lines often mention permission or filesystem problems:

failed (13: Permission denied)

Re-detect the worker user, reapply ownership to the cache directory, then reload only after the syntax test passes:

NGINX_USER=$(sudo nginx -T 2>/dev/null | awk '$1 == "user" {gsub(/;$/, "", $2); print $2; exit}')
if [ -z "$NGINX_USER" ]; then
    NGINX_USER=$(nginx -V 2>&1 | sed -n 's/.*--user=\([^[:space:]]*\).*/\1/p')
fi
if [ -z "$NGINX_USER" ]; then
    NGINX_USER=www-data
fi
sudo chown -R "$NGINX_USER": /var/cache/nginx/proxy_cache
sudo nginx -t && sudo systemctl reload nginx

Fix Nginx Proxy Cache Always Showing MISS

If every request is a miss, compare the upstream response headers with your cache rules:

curl -sI -H "Host: example.com" http://127.0.0.1/ | grep -E '^(HTTP/|Cache-Control:|Set-Cookie:|X-Cache-Status:)'

Example output from a private response may show why Nginx is not storing the response:

HTTP/1.1 200 OK
Cache-Control: private, no-store
Set-Cookie: session=abc123; Path=/; HttpOnly
X-Cache-Status: MISS

Do not ignore those headers blindly. For private application responses, the correct fix is usually to narrow the cached path or add stronger bypass rules, not to force Nginx to cache everything.

Fix Nginx Proxy Cache Hiding Upstream Errors

Stale cache can make a broken backend look healthy to visitors. When you troubleshoot upstream errors, bypass the cache with a private header or temporary rule, then test the backend directly.

Send a request that bypasses cache through your authorization rule:

curl -sI -H "Host: example.com" -H "Authorization: Bearer test" http://127.0.0.1/ | grep -E '^(HTTP/|X-Cache-Status:)'

If the uncached path shows a backend problem, use the relevant troubleshooting path for the status code. For upstream connection failures, start with the Nginx 502 Bad Gateway troubleshooting workflow.

Remove or Roll Back Nginx Proxy Cache

To disable proxy cache, remove or comment out the proxy_cache, proxy_cache_key, proxy_cache_valid, proxy_cache_bypass, proxy_no_cache, and X-Cache-Status lines from the affected locations. Then remove the shared proxy_cache_path line if no other site uses that cache zone.

Test and reload after the rollback edit:

sudo nginx -t && sudo systemctl reload nginx

Deleting /var/cache/nginx/proxy_cache removes the cache directory created earlier. Disable the cache zone first and confirm no other virtual host uses that path.

sudo rm -rf /var/cache/nginx/proxy_cache
test ! -e /var/cache/nginx/proxy_cache && echo "proxy cache directory removed"

Expected output confirms the directory was removed:

proxy cache directory removed

Conclusion

Nginx proxy cache is configured with a shared cache zone, visible hit/miss headers, private-request bypass rules, and a rollback path. When routing or cache keys still behave unexpectedly, review Nginx proxy_pass trailing slash behavior, the Nginx try_files directive, and Nginx root vs alias path mapping before expanding cache coverage.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show more of our fresh Linux tutorials in Top Stories and From your sources when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<blockquote>quote</blockquote> quote block

Got a Question or Feedback?

We read and reply to every comment - let us know how we can help or improve this guide.

Let us know you are human: