Back to Blog
DevOpsJune 29, 20264 min read

Nginx proxy_cache: A Practical Guide to Performance Tuning

Master nginx proxy_cache configuration to speed up your site. Learn how to handle static and dynamic content, set cache headers, and optimize performance.

nginxperformancedevopscachingweb-servers

Last month, our primary API server started buckling under the weight of a sudden traffic spike, with CPU usage hovering near 95% for hours. We realized our origin server was re-processing the same expensive database queries for every single request, so we pivoted to implementing an Nginx caching layer to shield the application.

Understanding nginx proxy_cache Architecture

The core of Nginx performance tuning lies in its ability to act as a high-performance buffer between the client and your backend. Instead of passing every request to PHP or Node.js, Nginx stores the response on disk or in memory and serves subsequent requests directly.

We initially tried caching everything by default, but that broke our user-specific dashboards. We quickly learned that you must distinguish between public assets and private user data. If you're building a system with dynamic blocks, consider how Dynamic Block Rendering: PHP, Performance, and Caching Strategies impacts your cache hit ratio before you start writing config files.

Setting Up Your nginx cache configuration

To get started, you need to define a proxy_cache_path in your http block. This tells Nginx where to store the cached files and how much memory to allocate for the cache keys.

NGINX
# Define the cache zone
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m use_temp_path=off;

server {
    location / {
        proxy_pass http://backend_upstream;
        proxy_cache my_cache;
        proxy_cache_valid 200 302 10m;
        proxy_cache_valid 404 1m;
        
        # Add a header to see if it's a hit or miss
        add_header X-Cache-Status $upstream_cache_status;
    }
}

The keys_zone=my_cache:10m is crucial. It allocates 10MB of shared memory for the cache keys—enough for about 80,000 keys—which is plenty for most medium-sized applications.

Handling Dynamic Content Caching

Caching dynamic content is risky. If you cache a personalized response, you'll leak data between users. We typically handle this by ignoring headers that usually trigger cache bypasses, like Set-Cookie.

HeaderBehaviorStrategy
Cache-Control: privateDo not cacheIgnore or strip header
Set-CookieUser-specificBypass cache for authenticated users
Vary: AuthorizationCache per userUse proxy_cache_key with user ID

When dealing with complex data dependencies, you might need more granular control than a simple timeout. If your application requires high data consistency, you should look into Cache Tagging and Invalidation: Mastering Data Consistency to ensure your users don't see stale information after an update.

Best Practices for Performance Tuning

  1. Use proxy_cache_use_stale: This is a lifesaver during backend outages. If your origin server crashes, Nginx can serve the last cached version of the page, keeping your site up while you fix the backend.
  2. Respect Cache-Control headers: Always honor the origin server's instructions. If your application sends Cache-Control: no-cache, Nginx should respect that.
  3. Monitor Cache Hits: Use the $upstream_cache_status variable in your logs. If your hit ratio is below 20%, you're likely caching the wrong things or your keys are too volatile.

Common Pitfalls

We once spent about two days debugging an issue where the cache wasn't updating. It turned out our backend was sending Vary: User-Agent, which created a unique cache key for every mobile device and browser version. This effectively nuked our cache efficiency. Always check your Vary headers before blaming Nginx.

If you're dealing with massive database contention, don't rely solely on Nginx. Often, the best approach is a layered strategy, such as implementing WordPress performance: Database proxy strategies for high concurrency to catch the load before it hits the database layer.

Frequently Asked Questions

How do I clear the Nginx cache manually?

Nginx doesn't have a native "purge" command in the open-source version. You can either delete the files in the cache directory using rm -rf /var/cache/nginx/* or use the ngx_cache_purge module if you need targeted invalidation.

Why is my cache not working for dynamic pages?

Check your proxy_ignore_headers directive. If your backend is sending Set-Cookie or Expires headers in the past, Nginx will refuse to cache the response by default.

Should I cache in memory or on disk?

Memory is faster but limited. Use proxy_cache_path for disk storage and proxy_temp_path on a fast SSD. If you have a small dataset, you can use tmpfs to keep the cache in RAM.

Caching is as much about invalidation as it is about performance. We're currently exploring how to automate cache purging via hooks, but for now, we rely on shorter TTLs for highly volatile data. Keep your configuration simple, monitor your hit rates, and always test your cache behavior in a staging environment before pushing to production.

Similar Posts