NGINX Caching
and Best Practices

How can we serve 10,000 users per second without our server even breaking a sweat?

Learning Objectives

By the end of this lesson, students will be able to:

  • Configure NGINX's core caching directives.
  • Explain the request flow for a cache HIT, MISS, and BYPASS.
  • Implement secure caching for both public and private content.
  • Use advanced techniques like cache locking to improve performance.
  • Debug the cache using the X-Cache-Status header.

Lesson Roadmap

  1. Caching Fundamentals
  2. Core Configuration
  3. Controlling the Cache
  4. Security & Private Content
  5. Advanced Performance
  6. Review & Takeaways

Concept: What is NGINX Caching?

NGINX caching is a process where NGINX, acting as a reverse proxy, stores a copy of a response from a backend server. For subsequent identical requests, NGINX serves the stored copy directly, drastically reducing latency and server load.

NGINX Cache Request Flow Diagram NGINX Cache Request Flow Client Browser NGINX Server Backend Server Cache? Cache Storage 1. Request 2. MISS: Forward 3. Backend Response 4. Store 5. Serve to Client 2. HIT: Serve from Cache

Concept: The `proxy_cache_path` Directive

This is the foundational directive. Defined in the `http` block, it configures the on-disk storage path for cached files and, crucially, creates a shared memory zone to hold cache keys and metadata. Its parameters control the cache's size, structure, and behavior.

Anatomy of the proxy_cache_path Directive Anatomy of `proxy_cache_path` proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m; /var/cache/nginx Local filesystem path to store the cached file content. levels=1:2 Creates a tiered directory structure to avoid performance issues. keys_zone=my_cache:10m Shared memory zone (10MB) to store cache keys and metadata. CRITICAL for performance. max_size=10g Sets the upper limit for on-disk cache size (10 gigabytes). inactive=60m Removes items from cache if they haven't been accessed for 60 minutes, regardless of their expiration time (`proxy_cache_valid`).

Concept: Controlling Cache Behavior

You need fine-grained control over caching. Directives like proxy_cache enable it for a location, proxy_cache_valid sets default lifetimes, and proxy_cache_bypass provides a way to conditionally ignore the cache, for example, for logged-in users.

NGINX Cache Decision Flowchart Cache Decision Logic Request Arrives Bypass Condition Met? (e.g., login cookie present) Yes Fetch from Backend BYPASS No Is Item in Cache? No Fetch from Backend MISS Store in Cache & Serve Yes Is Item Expired? Yes Fetch from Backend EXPIRED Update Cache & Serve No Serve from Cache HIT

Concept: Security & Private Content

Caching private data is dangerous. By default, NGINX uses the URL as the cache key, which can leak one user's data to another. To cache authenticated content safely, you must use proxy_cache_key to include a user-specific identifier, like a session cookie.

Secure vs. Insecure Cache Key Caching Private Content: The Cache Key is Everything Insecure: Default Cache Key 👤 User A GET /dashboard Cache Storage key A: "/dashboard" 👤 User B GET /dashboard HIT! Serves A's Data DATA LEAK! Secure: Custom Cache Key 👤 User A (sess=123) GET /dashboard Cache Storage key: "/dashboard123" key: "/dashboard456" 👤 User B (sess=456) GET /dashboard Separate cache entries SECURE

Concept: Performance & `proxy_cache_lock`

The "thundering herd" problem occurs when a popular, expired item is requested by many users at once, overwhelming your backend. proxy_cache_lock solves this by allowing only the first request to go to the backend, while others wait for the cache to be filled.

Thundering Herd vs. Cache Locking Solving the "Thundering Herd" Problem Without `proxy_cache_lock` 👤 👤 👤 👤 Backend Server(Overwhelmed) All requests hit the backend! With `proxy_cache_lock on` 👤 👤 👤 👤 NGINX LOCK Cache Backend Server(Healthy) 1. First request passes 2. Others wait 3. Cache is populated 4. All served from cache

Concept: High Availability & `proxy_cache_use_stale`

What if your backend server fails? Instead of showing an error, NGINX can serve an expired ("stale") version of the content from its cache. This directive dramatically improves user experience and site resilience during outages or high load.

proxy_cache_use_stale State Diagram `proxy_cache_use_stale` for High Availability Request Arrives Backend OK? Fetch Fresh. Backend Error/Timeout? Serve from Cache (STALE) Serve 502/504 Error Backend is UP Serve Fresh Content (HIT/MISS/EXPIRED) Backend is DOWN Check for stale copy Stale copy exists No stale copy

Check Your Understanding

True or False?

  • The `proxy_cache_path` directive belongs inside a `server` block.   ➔ False. It must be in the `http` block.
  • `X-Cache-Status: HIT` means the response came from the backend server.   ➔ False. HIT means it came directly from the NGINX cache.
  • To cache content securely for each user, you should use `proxy_cache_key` with a session cookie.   ➔ True. This creates a unique cache entry per user.

Common Misconceptions

  • Error: Forgetting to set permissions (`chown www-data`) on the cache directory.
    Correction: NGINX cannot write to the cache without correct ownership, causing all requests to MISS.
  • Error: Thinking `max_size` is the only limit.
    Correction: An undersized `keys_zone` will cause items to be removed long before the disk is full.
  • Error: Assuming the cache is automatically purged on content update.
    Correction: Cache purging must be explicitly configured or handled by setting short cache validity times.

Summary & Key Takeaways

  • NGINX caching is a powerful reverse-proxy feature that significantly boosts performance and reduces backend load.
  • Configuration is key: start with `proxy_cache_path` in `http`, then enable with `proxy_cache` in `location`.
  • Security is paramount. Never cache private data without a user-specific `proxy_cache_key`.
  • Advanced directives like `proxy_cache_lock` and `proxy_cache_use_stale` provide resilience and prevent server overload.

Exit Ticket

Describe a scenario where `proxy_cache_bypass` would be more appropriate than using a custom `proxy_cache_key`.