DEV Community

Magevanta
Magevanta

Posted on • Originally published at magevanta.com

Magento 2 Nginx Optimization for High Traffic — Complete Server Tuning Guide

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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";
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)