Your Magento store can have perfect PHP-FPM pools, well-tuned Redis, and optimized MySQL — and still buckle under load because Nginx is configured with defaults meant for a blog, not an ecommerce platform. Nginx is the front door to your entire stack. If it's slow, everything behind it is slow.
This guide walks through every lever worth pulling: connection handling, compression, caching headers, microcaching, SSL/TLS, and real-world configs for a Magento 2 production server.
Why Nginx Defaults Aren't Enough
Out-of-the-box Nginx ships with conservative defaults:
-
worker_processes 1— single worker, ignoring your CPU cores - No gzip compression enabled
- Keepalive timeouts that force reconnects
- No browser cache headers
- TLS handshakes that repeat work they don't need to
For a low-traffic site this doesn't matter. For Magento — which serves category pages, product pages, checkout flows, and API calls simultaneously — these defaults become bottlenecks at a few hundred concurrent users.
1. Worker Processes and Connections
Start at the top of nginx.conf:
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
worker_processes auto — Nginx will match the number of workers to available CPU cores. A 4-core server gets 4 workers.
worker_rlimit_nofile 65535 — raises the OS file descriptor limit per worker. Each active connection uses a file descriptor; the default of 1024 is dangerously low for production.
worker_connections 4096 — maximum connections per worker. Total concurrent connections = worker_processes × worker_connections. On a 4-core server: 16,384 concurrent connections max.
use epoll — Linux-only but highly efficient; scales better than select or poll under thousands of connections.
multi_accept on — workers accept all pending connections at once instead of one at a time.
2. Gzip Compression
Magento pages are large: HTML pages often exceed 100KB before JS and CSS. Gzip cuts transfer sizes by 60–80%:
http {
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 1000;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/x-javascript
application/json
application/xml
application/xml+rss
application/vnd.ms-fontobject
font/eot
font/otf
font/ttf
image/svg+xml;
}
gzip_comp_level 5 — the sweet spot. Level 9 uses ~30% more CPU for only ~2% better compression. Level 5 is fast and effective.
gzip_vary on — adds a Vary: Accept-Encoding header so CDNs and proxies serve the right version to each client.
gzip_min_length 1000 — don't bother compressing tiny responses; overhead outweighs benefit below ~1KB.
3. Keepalive and Timeouts
Keepalive connections let the browser reuse TCP connections for multiple requests, eliminating repeated TCP handshakes:
http {
keepalive_timeout 65;
keepalive_requests 100;
# Upstream keepalive to PHP-FPM
upstream fastcgi_backend {
server unix:/run/php/php8.3-fpm.sock;
keepalive 32;
}
}
keepalive_timeout 65 — hold connections open for 65 seconds. Fine for most workloads; reduce to 15–30 on memory-constrained servers.
keepalive 32 in the upstream block — keeps up to 32 persistent connections open to PHP-FPM, avoiding socket overhead on every PHP request.
Also tune these to avoid slow clients tying up workers:
client_header_timeout 15;
client_body_timeout 15;
send_timeout 15;
reset_timedout_connection on;
4. Browser Cache Headers for Static Assets
Magento's static files (JS, CSS, images, fonts) are versioned via deploy version hashes. Set aggressive caching:
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|webp)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
immutable tells supporting browsers (Chrome, Firefox) to never re-validate the file during its lifetime — eliminating conditional GET requests entirely.
access_log off for static assets reduces disk I/O significantly on busy servers.
5. Microcaching with FastCGI Cache
Full Page Cache (Varnish or Magento built-in) handles authenticated sessions poorly by design. Microcaching fills the gap: cache PHP responses for just 1–5 seconds. At 500 req/s, a 1-second cache reduces PHP hits by ~99% for repeated URLs.
Define a cache zone in nginx.conf:
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=MAGENTO:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
In your server block:
set $no_cache 0;
# Don't cache POST requests
if ($request_method = POST) { set $no_cache 1; }
# Don't cache if session cookie present (logged-in users, active cart)
if ($http_cookie ~* "(PHPSESSID|frontend|adminhtml|checkout)") {
set $no_cache 1;
}
location ~ \.php$ {
fastcgi_cache MAGENTO;
fastcgi_cache_valid 200 1s;
fastcgi_cache_bypass $no_cache;
fastcgi_no_cache $no_cache;
add_header X-FastCGI-Cache $upstream_cache_status;
include fastcgi_params;
fastcgi_pass fastcgi_backend;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
The X-FastCGI-Cache header lets you verify HIT/MISS/BYPASS in response headers — essential for debugging. If you see BYPASS on every request, check that your cookie exclusions are correct.
Important: Do not use microcaching in place of proper FPC. Use it as a complement for high-burst traffic windows (flash sales, launches).
6. SSL/TLS Performance
TLS termination at Nginx is unavoidable on HTTPS-only stores. Tune it to minimize handshake overhead:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off; # Disable for perfect forward secrecy
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
ssl_session_cache shared:SSL:10m — a 10MB shared cache holds ~40,000 sessions, allowing TLS resumption (no full handshake for returning visitors).
OCSP stapling (ssl_stapling on) — Nginx fetches and caches the certificate validity check, so your clients don't have to. Eliminates one round-trip per new connection.
TLSv1.3 — if you can drop TLSv1.2 entirely (verify your CDN and payment providers support it), TLSv1.3 does a full handshake in 1 round trip vs. 2.
7. Buffer Tuning
Large Magento responses (admin grids, product list pages) benefit from proper buffer settings:
client_body_buffer_size 128k;
client_max_body_size 64m; # Required for media imports
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_buffers 16 16k — 256KB total buffer per request. Enough for most Magento pages without disk buffering.
If a page exceeds fastcgi_buffers, Nginx writes the overflow to a temp file — adding disk I/O to every large response. Set this high enough to avoid it.
8. Rate Limiting for Admin and API
Protect your admin panel and REST API from brute-force and abuse:
# Define zones in http block
limit_req_zone $binary_remote_addr zone=admin:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
# Apply in server block
location /admin {
limit_req zone=admin burst=10 nodelay;
# ... rest of config
}
location /rest {
limit_req zone=api burst=50 nodelay;
# ... rest of config
}
burst allows short spikes above the rate. nodelay processes burst requests immediately instead of queuing them — important for API clients that batch requests.
9. Monitoring What You've Done
After applying changes, verify with:
# Test config before reloading
nginx -t
# Reload without dropping connections
nginx -s reload
# Watch real-time connection states
ss -s
# Check cache hit rate (tail access log with cache status)
tail -f /var/log/nginx/access.log | grep 'X-Cache'
Use ab (ApacheBench) or wrk for quick load tests before and after:
# 1000 requests, 50 concurrent
ab -n 1000 -c 50 https://your-store.com/
Putting It All Together
Nginx optimization is one of the highest-leverage things you can do for a Magento store: it affects every single request before PHP even runs. The changes above — workers, gzip, keepalive, browser cache, microcaching, TLS tuning, and buffers — consistently yield 2–4× improvement in requests-per-second capacity on the same hardware.
Start with worker_processes auto and gzip (both zero-risk changes), then profile with a load test before adding microcaching, which requires careful cookie exclusion to avoid caching user-specific content.
The full picture: Nginx handles connections and serves static files; Varnish or Magento FPC serves full pages; Redis caches sessions and blocks; PHP-FPM processes what's left. Each layer does its job. Nginx's job is to be fast and efficient at the very edge — don't let defaults undermine the rest of your stack.

















