Caching in nginx is configured in two steps: declare a cache zone on disk, then opt in from a location. The proxy_cache_path directive creates a managed cache directory tree with metadata stored in a shared memory zone — for example, proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;. In the location you then enable caching with proxy_cache my_cache; and define how long each response class lives via proxy_cache_valid 200 302 10m; and proxy_cache_valid 404 1m;. To see what is happening during debugging, add the X-Cache-Status header from $upstream_cache_status — values such as HIT, MISS, BYPASS, EXPIRED, STALE, UPDATING, and REVALIDATED tell you exactly why a given response came out of the cache or did not.
Two related directives let you carve holes in the cache. proxy_no_cache decides whether the response is saved at all, and proxy_cache_bypass decides whether to serve from cache if it is available or to fetch fresh from upstream. They are typically used together with cookie or query-string signals: proxy_cache_bypass $cookie_nocache $arg_nocache; proxy_no_cache $cookie_nocache $arg_nocache; ensures that logged-in users or requests tagged with ?nocache=1 skip the cache both ways. FastCGI caching follows the same pattern but uses fastcgi_cache_path, fastcgi_cache, fastcgi_cache_valid, and a fastcgi_cache_key. A typical WordPress key is fastcgi_cache_key "$scheme$request_method$host$request_uri", often extended with $cookie_logged_in so authenticated visitors bypass the cache automatically.
Compression is the other half of bandwidth reduction. gzip on; turns it on, gzip_types text/plain text/css application/json application/javascript text/xml; lists which MIME types to compress, and gzip_min_length 256; avoids wasting CPU on tiny responses. gzip_vary on; adds Vary: Accept-Encoding so intermediary caches store separate gzip and identity copies. When the response is already fingerprinted, you can precompress files at build time and serve them with gzip_static on;, which checks for file.js.gz alongside file.js. Brotli goes further but requires the nginx-module-brotli (or a fork that ships it built-in): enable it with brotli on; and a parallel brotli_types list, typically at brotli_comp_level 6;. Static-asset caching is handled by the expires directive: expires 30d; sets Cache-Control and Expires headers for general static files, while far-future caching for fingerprinted assets is usually written as expires 1y; combined with add_header Cache-Control "public, immutable";. For HTML you generally want the opposite — add_header Cache-Control "no-store, no-cache, must-revalidate"; with expires off;.