Images, stylesheets, scripts and fonts often make up most requests on a page. Nginx is very good at serving static files, but a few directives make it even faster and, more importantly, let browsers and CDNs avoid asking again. This guide covers the kernel-level optimisations and the HTTP caching headers that matter.
Before you start
- Nginx on a VPS with sudo access.
- Asset filenames that change when content changes (e.g.
app.3f9a1c.js), which most build tools and WordPress plugins provide. This makes long caching safe.
sendfile, tcp_nopush and tcp_nodelay
In the http context:
sendfile on;
tcp_nopush on;
tcp_nodelay on;- sendfile lets the kernel copy file data straight to the network socket, without passing through Nginx's memory. Less CPU, fewer copies.
- tcp_nopush (with sendfile) sends response headers and the start of the file in full packets.
- tcp_nodelay sends the last small packets immediately, so keep-alive responses don't wait.
Most distribution configs already enable these. With HTTPS, sendfile only helps if the kernel supports TLS offload (kTLS, ssl_conf_command Options KTLS; with recent OpenSSL and Nginx); otherwise Nginx encrypts in user space anyway, which is still fast.
open_file_cache
Every request for a file means opening it and checking its metadata. Nginx can cache file descriptors and lookups:
open_file_cache max=10000 inactive=60s;
open_file_cache_valid 120s;
open_file_cache_min_uses 2;
open_file_cache_errors on;max: number of entries.inactive: drop entries not used in this time.valid: how often to re-check that a cached entry is still current. A deployed file may be served from the old handle for up to this long.errors on: also cache "file not found", useful against floods of missing-file requests.
This helps most on sites with many small files and high request rates.
Long browser caching for versioned assets
location ~* \.(?:css|js|mjs|woff2?|ttf|otf|svg|jpe?g|png|gif|webp|avif|ico)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable" always;
access_log off;
try_files $uri =404;
}immutabletells browsers never to revalidate during the max-age; perfect for fingerprinted filenames.- For files whose names don't change (e.g.
logo.pngreferenced everywhere), use a shorter max-age such as a week. - Remember that
add_headerhere replaces server-level headers in this location; re-include your security headers snippet. See security headers.
HTML: short or no caching
location / {
add_header Cache-Control "no-cache" always;
try_files $uri $uri/ /index.html;
}no-cache doesn't mean "don't store"; it means "revalidate first". Nginx answers revalidation with 304 Not Modified using ETag and Last-Modified, which it sends automatically for static files. More on headers: Cache-Control headers and CDNs.
Serve precompressed files
gzip_static on;
brotli_static on;If app.js.gz or app.js.br exists next to app.js, Nginx sends it directly instead of compressing on every request. Generate them at maximum compression in your build. See web server compression. (brotli_static needs the Brotli module.)
Large downloads and video
- Nginx supports byte-range requests automatically, so video seeking and resumed downloads work.
- For big files on fast networks,
sendfile_max_chunk 2m;stops one fast client from hogging a worker. - Throttle per connection if needed:
limit_rate_after 10m; limit_rate 2m;. - For MP4 streaming with seeking by time, the
mp4module (location ~ \.mp4$ { mp4; }) helps.
Verify
curl -sI https://example.com/assets/app.3f9a1c.js | grep -iE 'cache-control|expires|etag|content-encoding'
curl -sI -H 'If-None-Match: "PASTE-ETAG"' https://example.com/index.html | head -1The second command should return 304.
Troubleshooting
- Old CSS after deploy: long caching on non-versioned filenames. Version the files or shorten max-age.
- Security headers missing on assets: location-level add_header replaced them.
- Files 404 after moving them: open_file_cache still holds the old lookup until
validexpires; reload Nginx.
FAQ: Nginx static files
Do I still need a CDN?
A CDN serves files from locations near visitors and offloads bandwidth; Nginx tuning makes the origin efficient. They complement each other.
Is open_file_cache safe?
Yes, with a short valid period.
Should static files be logged?
Usually not; turning logs off for them saves disk I/O.
