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.
| Directive | Context | Purpose |
|---|---|---|
proxy_cache_path | http | Defines the disk path, shared memory zone, cache size, directory layout, and inactive cleanup window. |
proxy_cache | http, server, location | Enables a named cache zone for proxied responses. |
proxy_cache_key | http, server, location | Controls which request parts make one cache entry unique. |
proxy_cache_valid | http, server, location | Sets cache lifetimes for selected response status codes. |
proxy_cache_bypass | http, server, location | Skips reading from cache when any condition evaluates to a non-empty, non-zero value. |
proxy_no_cache | http, server, location | Prevents saving the current response to cache when a condition matches. |
proxy_cache_use_stale | http, server, location | Allows Nginx to serve an expired cached response during selected upstream failures. |
proxy_cache_lock | http, server, location | Lets 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
$hostin the cache key when one upstream serves more than one hostname. - Keep
$request_uriin the cache key when query strings change the response. - Do not use
proxy_ignore_headersto override upstreamCache-ControlorSet-Cookiebehavior 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.
| Symptom | Likely Cause | First Check |
|---|---|---|
X-Cache-Status missing | The request is handled by a different location or server block | Check server_name, location matching, and active config output |
Always MISS | Response is not cacheable, status code is not listed, or cache key changes each request | Inspect response headers and request URL |
Always BYPASS | A bypass variable is non-empty or non-zero | Check method, authorization header, and cookies |
| Wrong user content appears | Cache key or bypass rules are too broad | Disable cache for private paths immediately |
| Permission errors | Nginx worker cannot write to the cache path | Check cache directory ownership and error logs |
| Cache fills disk | max_size is too high or cache path shares a small filesystem | Check 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_cacheremoves 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.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><blockquote>quote</blockquote>