Static + CDN cheatsheet.
Static site config
server {
listen 80;
server_name static.example.com;
root /var/www/static;
index index.html;
location / {
try_files $uri $uri/ /index.html; # SPA fallback
}
location ~* \.(css|js|woff2|woff|svg|webp|jpg|jpeg|png|gif)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
location = /service-worker.js {
expires -1;
add_header Cache-Control "no-cache";
}
location = /robots.txt { log_not_found off; access_log off; }
location = /favicon.ico { log_not_found off; access_log off; }
}
Hashed asset filenames
If your build emits app.abc123.js:
location ~* \.[a-f0-9]{6,}\.(js|css|woff2|png|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
Safe to cache forever; content hash invalidates URL.
index.html — short cache
location = /index.html {
expires -1;
add_header Cache-Control "no-cache, must-revalidate";
}
Always-fresh entrypoint, immutable assets.
SPA fallback
location / {
try_files $uri /index.html;
}
For React/Vue/SPA where client routing handles paths.
Compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
application/javascript
application/json
application/xml
application/rss+xml
image/svg+xml
application/wasm
application/manifest+json;
gzip_min_length 256;
Brotli
brotli on;
brotli_comp_level 6;
brotli_types
text/plain text/css application/javascript application/json image/svg+xml;
Requires ngx_brotli module.
Pre-compressed files
Build outputs both .gz and .br:
gzip_static on;
brotli_static on;
Serves app.js.gz if client supports it, no on-the-fly compression cost.
sendfile
sendfile on;
tcp_nopush on;
sendfile_max_chunk 1m;
Zero-copy file transfers.
open_file_cache
open_file_cache max=10000 inactive=60s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
Big speedup for many small files.
CORS for fonts
location ~* \.(woff|woff2|ttf|otf|eot)$ {
add_header Access-Control-Allow-Origin "*" always;
expires 1y;
}
CDN headers
If behind Cloudflare / CloudFront / Fastly:
real_ip_header CF-Connecting-IP; # Cloudflare
real_ip_header X-Forwarded-For;
set_real_ip_from 173.245.48.0/20; # CF range
# ... add all CF ranges
real_ip_recursive on;
ETag / Last-Modified
Default on. Allows 304 responses.
etag on;
if_modified_since exact;
Server timing
add_header Server-Timing "nginx;dur=$request_time" always;
Service Worker (PWA)
location = /sw.js {
add_header Cache-Control "public, max-age=0, must-revalidate";
add_header Service-Worker-Allowed "/";
expires off;
}
Manifest
location ~ \.webmanifest$ {
types { application/manifest+json webmanifest; }
add_header Cache-Control "public, max-age=3600";
}
WebP serving
map $http_accept $webp_suffix {
default "";
~*image/webp ".webp";
}
location ~* \.(jpg|png)$ {
try_files $uri$webp_suffix $uri =404;
add_header Vary Accept;
}
If foo.jpg.webp exists and browser supports webp, serve it.
CDN as origin
When using a CDN, nginx is the origin. Tighten:
# Only allow CDN IPs to origin
allow 173.245.48.0/20; # Cloudflare
allow 103.21.244.0/22;
deny all;
CDN cache hints
add_header Cache-Control "public, max-age=31536000, s-maxage=31536000, immutable";
add_header CDN-Cache-Control "max-age=31536000"; # Cloudflare-specific
add_header Surrogate-Control "max-age=31536000"; # Fastly
Range requests (video / large files)
location ~* \.(mp4|webm)$ {
mp4; # nginx-mp4 module
}
Or use aio threads for big files:
aio threads;
sendfile off;
Image hot resize (avoid)
Image transformation belongs in a service (imgproxy, Imagen, Cloudinary). Don’t ImageMagick in nginx for prod.
Common mistakes
- Cache forever for
index.html→ stale frontend. - Missing
expiresfor assets → no client cache. - Compression on already-compressed (jpg/png) → CPU waste.
- gzip_min_length too low → compress tiny responses (negative).
- Forgetting
Vary: Accept-Encoding→ CDN caches wrong variant.
Read this next
If you want my static site templates, they’re at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .