Web Server Caching: Complete Setup Guide

· Mei-Lin Wu · 8 min read

Intermediate

Web server caching stores frequently requested content at the server level, reducing the load on backend applications and databases. While CDN caching handles global content distribution at edge servers, web server caching provides a critical intermediate layer between the CDN and your application — catching requests that miss the CDN cache before they hit your application server or database.

The caching hierarchy works in layers: browser cache → CDN edge cache → origin shield cache → web server cache → application cache → database cache. Each layer reduces the load on the layer behind it. A well-configured web server cache can reduce backend requests by 80-95%, even when the CDN cache is cold (after a purge or for personalized content).

How Do You Configure Nginx proxy_cache?

Nginx proxy_cache stores upstream responses on disk, serving cached responses without contacting the backend.

# Define cache zone (http context)
proxy_cache_path /var/cache/nginx/app
    levels=1:2
    keys_zone=app_cache:10m    # 10MB for keys (~80,000 keys)
    max_size=2g                # 2GB disk space
    inactive=60m               # Remove unused items after 60min
    use_temp_path=off;         # Write directly to cache path

# Apply caching (server/location context)
location /api {
    proxy_pass http://backend;
    proxy_cache app_cache;

    # Cache different status codes for different durations
    proxy_cache_valid 200 60m;     # Cache 200 OK for 60 minutes
    proxy_cache_valid 301 24h;     # Cache 301 redirects for 24 hours
    proxy_cache_valid 404 1m;      # Negative cache 404 for 1 minute
    proxy_cache_valid any 5m;      # Everything else for 5 minutes

    # Serve stale on errors (critical for reliability)
    proxy_cache_use_stale error timeout http_502 http_503 http_504;

    # Prevent cache stampede (only 1 request to backend per cache key)
    proxy_cache_lock on;
    proxy_cache_lock_timeout 5s;

    # Background refresh (serve stale, update in background)
    proxy_cache_background_update on;

    # Add debug header
    add_header X-Cache-Status $upstream_cache_status;
}

The X-Cache-Status header returns one of: HIT (served from cache), MISS (fetched from backend), EXPIRED (cache entry expired, refetching), STALE (serving stale due to error), UPDATING (stale served while background update runs), BYPASS (cache was bypassed).

How Does Nginx fastcgi_cache Work for PHP?

For PHP applications, fastcgi_cache caches the output of PHP-FPM directly:

Performance Comparison: Edge vs Origin Processing
Metric Edge Processing Origin Processing Improvement
Latency (p50)8–15 ms120–300 ms15–20x faster
Cache Hit Ratio85–95%N/AReduces origin load
TLS Handshake1–5 ms (nearby PoP)50–150 ms10–30x faster
Bandwidth Cost$0.01–0.04/GB$0.05–0.12/GB3–5x cheaper
Availability99.99% (multi-PoP)99.9% (single DC)10x fewer outages
# Define fastcgi cache zone
fastcgi_cache_path /var/cache/nginx/fastcgi
    levels=1:2
    keys_zone=php_cache:10m
    max_size=1g
    inactive=30m;

# Cache key design (include method, scheme, host, URI)
fastcgi_cache_key "$scheme$request_method$host$request_uri";

server {
    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php-fpm.sock;
        fastcgi_cache php_cache;
        fastcgi_cache_valid 200 15m;
        fastcgi_cache_use_stale error timeout;

        # Skip cache for logged-in users and POST requests
        set $skip_cache 0;
        if ($request_method = POST) { set $skip_cache 1; }
        if ($http_cookie ~* "wordpress_logged_in") { set $skip_cache 1; }
        fastcgi_cache_bypass $skip_cache;
        fastcgi_no_cache $skip_cache;
    }
}

How Do You Configure Apache mod_cache?

Apache's caching modules provide two backends:

# Enable disk-based cache
<IfModule mod_cache_disk.c>
    CacheEnable disk /
    CacheRoot /var/cache/apache2/mod_cache_disk
    CacheMaxFileSize 5000000     # 5MB max per cached file
    CacheDirLevels 2
    CacheDirLength 1
    CacheMaxExpire 86400         # Max cache TTL: 24 hours
    CacheDefaultExpire 3600      # Default TTL: 1 hour

    # Don't cache authenticated or POST requests
    CacheDisable /admin
    CacheDisable /api/auth
</IfModule>

What Is Microcaching and When Should You Use It?

Microcaching caches dynamic content for very short periods (1-10 seconds). This technique is extraordinarily effective during traffic spikes:

# Nginx microcaching for dynamic content
proxy_cache_path /var/cache/nginx/micro
    levels=1:2
    keys_zone=micro:5m
    max_size=500m
    inactive=5m;

location / {
    proxy_pass http://backend;
    proxy_cache micro;
    proxy_cache_valid 200 1s;          # Cache for just 1 second
    proxy_cache_lock on;
    proxy_cache_use_stale updating;    # Serve stale during refresh
}

With a 1-second microcache and 1000 requests/second, only 1 request reaches the backend while 999 are served from cache. The content is at most 1 second stale — acceptable for most dynamic content including news articles, product pages, and API responses.

Web caching hierarchy showing browser cache, CDN edge, origin server cache, application cache, and database as layers with decreasing speed and increasing durability Caching Hierarchy Browser Cache (fastest, per-user) CDN Edge Cache (global, shared) Web Server Cache (proxy_cache / mod_cache) Application Cache (Redis / Memcached) Database (slowest, authoritative)

How Do You Set Cache-Control Headers for CDN Integration?

Proper Cache-Control headers at the web server level control caching behavior across the entire hierarchy:

# Nginx: Set Cache-Control for different content types
location ~* \.(css|js|woff2|png|jpg|svg)$ {
    expires 365d;
    add_header Cache-Control "public, immutable";
}

location ~* \.html$ {
    add_header Cache-Control "public, max-age=300, s-maxage=3600, stale-while-revalidate=86400";
}

location /api/ {
    add_header Cache-Control "private, no-cache";
    add_header CDN-Cache-Control "public, max-age=60";
}

The s-maxage directive sets the CDN cache TTL independently from the browser cache TTL (max-age). This allows longer CDN caching with shorter browser caching — the CDN serves fresh content while the browser checks for updates more frequently. The CDN-Cache-Control header (used by Cloudflare and some other CDNs) overrides all other caching directives for CDN caching only.

External References

References & Standards: Cloudflare documentation · RFC 7234