Updated October 2026: Fixed invalid or outdated directives (
tcp_fastopen,keepalive_requests 100,listen ... http2), switched TLS to Mozilla’s current intermediate profile, and added HTTP/3, real static caching headers and upstream keepalive.
I. Introduction
Nginx is renowned for its efficiency and versatility in serving web content. However, optimizing its configuration is crucial to ensure optimal performance. This log outlines key techniques to optimize Nginx configuration for enhanced performance.
After every change, test the configuration first and only then reload. A reload starts new workers with the new configuration and gracefully retires the old ones, so it does not drop connections the way a restart does:
sudo nginx -t && sudo systemctl reload nginx
II. Enabling Keepalive Connections
Keepalive connections allow persistent connections between clients and the server, reducing connection establishment overhead.
Example:
http {
keepalive_timeout 65s; # default is 75s
# keepalive_requests 1000; # already the default since nginx 1.19.10
}
Older guides often set keepalive_requests 100;. That was the default before nginx 1.19.10; on any current version it lowers the limit (the default is now 1000), so leave it out unless you really need a different value.
III. Implementing Gzip Compression
Gzip compression reduces the size of data transmitted between the server and clients, enhancing page load times and conserving bandwidth.
Example:
http {
gzip on;
gzip_vary on; # adds "Vary: Accept-Encoding" so caches keep both versions
gzip_proxied any; # also compress responses to requests that came through a proxy/CDN
gzip_comp_level 5;
gzip_min_length 256;
gzip_types text/plain text/css text/xml text/javascript
application/javascript application/json application/xml
application/rss+xml application/atom+xml image/svg+xml;
}
A few notes:
text/htmlis always compressed oncegzip on;is set, so it does not need to be listed ingzip_types.- JavaScript is served as
application/javascriptby themime.typesfile that ships with nginx, whiletext/javascriptis the type current standards recommend, so list both. The legacyapplication/x-javascriptis not needed. gzip_proxieddecides whether responses to proxied requests (detected by theViarequest header) are compressed; the defaultoffskips them.- Brotli usually compresses text better than gzip, but it is not part of nginx itself. It is available through the third-party ngx_brotli module (
brotli on;,brotli_types ...;), which must be compiled in or loaded as a dynamic module.
IV. Caching Static Assets
Caching static assets minimizes the need to fetch them from the origin server repeatedly, improving response times and reducing server load. There are two separate layers here: telling browsers and CDNs how long they may keep a file, and letting nginx itself cache file metadata or proxied responses.
1. Browser and CDN caching with expires / Cache-Control
Example:
server {
# Fingerprinted build output (e.g. app.3f9a1c.js): safe to cache "forever"
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
# Other images and fonts: cache for a week
location ~* \.(?:png|jpe?g|gif|webp|avif|svg|ico|woff2)$ {
expires 7d; # sets Expires and Cache-Control: max-age=604800
}
}
Only use immutable and a one-year max-age for files whose name changes when their content changes; otherwise users keep a stale copy.
Watch out for add_header inheritance: a block inherits add_header directives from its parent only if it defines none of its own. Adding a Cache-Control header in a location therefore silently drops any security headers (HSTS, Alt-Svc, …) set at the server level, unless you repeat them or, on nginx 1.29.3+, use add_header_inherit merge;.
2. Caching open file descriptors with open_file_cache
When nginx serves many small static files, open_file_cache keeps file descriptors, sizes and modification times in memory instead of looking them up on every request:
http {
open_file_cache max=10000 inactive=60s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
}
3. Caching proxied responses with proxy_cache
proxy_cache_path on its own only declares a cache zone; nothing is cached until a location uses it with proxy_cache:
http {
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
server {
location / {
proxy_cache my_cache;
proxy_cache_valid 200 301 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
}
}
}
proxy_cache_use_stale ... updating serves the cached copy while a fresh one is fetched, and proxy_cache_lock makes concurrent misses for the same key wait for a single upstream request. X-Cache-Status (HIT, MISS, EXPIRED, …) is handy for debugging.
V. Optimizing TLS/SSL Configuration
Optimizing TLS/SSL configuration involves specifying modern protocols and strong ciphers for improved security and performance. Rather than hand-picking a cipher string, follow Mozilla’s “intermediate” profile (now generated by the TLSRef Configurator, formerly ssl-config.mozilla.org), which is the recommended setting for general-purpose servers.
Example (Mozilla intermediate, guidelines v6.0):
http {
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name www.example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
# HSTS (63072000 seconds = 2 years)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
}
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1:secp384r1;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
}
What changed compared to older configs:
ssl_prefer_server_ciphers off: every cipher in the list is strong, so the client is allowed to pick the one that is fastest on its hardware (for example ChaCha20 on phones without AES acceleration).ssl_ciphersonly governs TLS 1.2. TLS 1.3 cipher suites are configured separately (viassl_conf_command Ciphersuites ...) and the OpenSSL defaults are fine.ssl_ecdh_curve X25519MLKEM768:...enables the post-quantum hybrid key exchange first.X25519MLKEM768requires OpenSSL 3.5 or newer; on older OpenSSL, drop it from the list or nginx will refuse the configuration.ssl_session_cache/ssl_session_timeoutlet returning clients resume a session instead of doing a full handshake, which saves a round trip and CPU.http2 on;is the way to enable HTTP/2 since nginx 1.25.1. The oldlisten 443 ssl http2;parameter still works but is deprecated.
Optional: HTTP/3 (QUIC)
nginx 1.25.0+ can also serve HTTP/3 over QUIC (UDP). The ngx_http_v3_module is still marked experimental and is not built by default, so check that nginx -V shows --with-http_v3_module (the nginx.org Linux packages include it). Add QUIC listeners on the same port as HTTPS and advertise them with Alt-Svc:
server {
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
# Tells browsers HTTP/3 is available on UDP 443 for the next 24 hours
add_header Alt-Svc 'h3=":443"; ma=86400';
# ... certificates and the TLS settings above
}
Socket options such as reuseport (and fastopen below) may appear only once per address:port pair, so with several server blocks put them on one listen only. Remember to open UDP 443 in your firewall.
VI. Fine-Tuning Worker Processes
Adjusting the number of worker processes and connections optimizes resource utilization and concurrent request handling.
Example:
worker_processes auto; # one worker per CPU core
worker_rlimit_nofile 65535; # raise the open-file limit for workers
events {
worker_connections 4096;
}
worker_connections counts all connections, including the ones nginx opens to proxied servers, so a reverse proxy uses roughly two per client request. The real limit is also capped by the number of open files, which is why worker_rlimit_nofile is raised alongside it.
VII. TCP Optimization
TCP optimization involves settings like sendfile, TCP_NOPUSH/TCP_CORK, TCP_NODELAY and TCP Fast Open to minimize latency and improve connection performance.
Example:
http {
sendfile on; # kernel copies files directly to the socket (default is off)
tcp_nopush on; # only takes effect together with sendfile
tcp_nodelay on; # already the default
server {
# TCP Fast Open: a parameter of `listen`, not a standalone directive
listen 443 ssl fastopen=256;
}
}
tcp_nopushlets nginx send the response headers and the start of a file in one packet and then send full packets, but it is applied only whensendfileis on.- There is no
tcp_fastopendirective in nginx; writing one makesnginx -tfail. TCP Fast Open is enabled per listening socket withlisten ... fastopen=N, whereNlimits the queue of connections that have not finished the handshake. On Linux the kernel must also allow server-side TFO (sysctl -w net.ipv4.tcp_fastopen=3, which enables both client and server support). Only enable it if your application can safely handle the same SYN data arriving more than once.
VIII. Minimizing Redirects
Reducing the number of redirects improves page load times and enhances user experience. Every redirect is an extra round trip (and, for a new host, an extra DNS lookup and TLS handshake) before the browser can even start loading the page.
A common anti-pattern is a redirect chain: http://example.com → https://example.com → https://www.example.com. Instead, send every non-canonical variant straight to the final URL in a single hop:
Example:
# http://example.com and http://www.example.com -> https://www.example.com (one hop)
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://www.example.com$request_uri;
}
# https://example.com -> https://www.example.com
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
# ssl_certificate ... (must cover example.com)
return 301 https://www.example.com$request_uri;
}
return is cheaper and clearer than rewrite for this, and $request_uri keeps the original path and query string. Also make sure your own links, sitemaps and canonical tags point at the final URL so users do not hit the redirect at all.
IX. Utilizing Load Balancing
Load balancing distributes traffic across multiple backend servers, improving scalability and reliability. To avoid opening a new TCP connection to the backend for every request, keep a pool of idle upstream connections with keepalive.
Example:
http {
upstream backend {
server backend1.example.com;
server backend2.example.com;
keepalive 32; # idle connections kept open per worker
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
}
}
}
Upstream keepalive needs HTTP/1.1 towards the backend and an empty Connection header (nginx otherwise sends Connection: close). Before nginx 1.29.7 both lines above are required, because the default was HTTP/1.0 with no keepalive. Since 1.29.7, proxy_http_version 1.1 is the default and upstream keepalive is enabled by default with 32 connections per worker, so the extra lines are harmless but no longer necessary.
X. Logging Optimization
Optimizing logging involves configuring access and error logging to balance information capture and performance impact.
Example:
http {
access_log /var/log/nginx/access.log combined buffer=16k flush=5s;
error_log /var/log/nginx/error.log warn;
}
buffer=16k batches log writes instead of writing on every request. Without flush, buffered lines are only written when the buffer fills or the worker reopens its logs, so on a quiet server they can sit in memory for a long time; flush=5s caps that delay. You can also turn off access logging entirely for noisy static paths (access_log off;).
XI. Conclusion
Optimizing Nginx configuration is essential for achieving peak performance in web applications. By implementing these optimization techniques, developers can enhance server efficiency, improve user experience, and ensure scalability for their applications. Always validate with nginx -t, reload rather than restart, and measure before and after each change.
Public comments are closed, but I love hearing from readers. Feel free to contact me with your thoughts.