Linux VPS

Nginx performance tuning: workers, compression and FastCGI cache

Practical Nginx performance tuning for a VPS: worker settings, keep-alive, gzip/Brotli compression, static file caching headers, HTTP/2, and a FastCGI page cache for PHP sites.

AdvancedLinux VPS3 min read

Nginx is fast out of the box, but a few settings make a real difference on a busy VPS: compression, browser caching and, above all, a page cache so PHP does not rebuild the same page for every visitor. This guide covers practical Nginx performance tuning without risky tweaks.

Before you start

  • A working Nginx + PHP-FPM site on a Linux VPS.
  • A way to measure: browser developer tools, curl -w, or a load-testing tool. Measure before and after each change.
  • Always run nginx -t before reloading.

Worker processes and connections

In /etc/nginx/nginx.conf:

worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
    multi_accept on;
}

auto starts one worker per CPU thread. Total connections ≈ workers × worker_connections, far more than most VPSs need.

Keep-alive and timeouts

http {
    sendfile on;
    tcp_nopush on;
    keepalive_timeout 30s;
    keepalive_requests 1000;
    client_body_timeout 15s;
    send_timeout 15s;
    server_tokens off;
}

Shorter timeouts free connections held by slow or idle clients. server_tokens off hides the Nginx version.

Enable compression

gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_vary on;
gzip_proxied any;
gzip_types text/plain text/css application/javascript application/json image/svg+xml application/xml;

Level 5 is a good balance between CPU and size. Brotli compresses text better but needs the separate ngx_brotli module. Images like JPEG, PNG and WebP are already compressed – do not gzip them.

Browser caching for static files

location ~* \.(css|js|jpg|jpeg|png|gif|webp|avif|svg|woff2)$ {
    expires 30d;
    add_header Cache-Control "public";
    access_log off;
}

If your build tool adds version hashes to file names, you can safely use much longer expiry times.

HTTP/2

Enable it on HTTPS listeners. On Nginx 1.25.1 and newer:

listen 443 ssl;
http2 on;

On older versions use listen 443 ssl http2;.

Add a FastCGI page cache

  1. In the http block:
    fastcgi_cache_path /var/cache/nginx/fcgi levels=1:2 keys_zone=PHPCACHE:100m max_size=1g inactive=60m use_temp_path=off;
    fastcgi_cache_key "$scheme$request_method$host$request_uri";
  2. In the server block, decide what must not be cached:
    set $skip_cache 0;
    if ($request_method = POST) { set $skip_cache 1; }
    if ($query_string != "") { set $skip_cache 1; }
    if ($request_uri ~* "/wp-admin/|/wp-login.php|/cart|/checkout|/my-account") { set $skip_cache 1; }
    if ($http_cookie ~* "wordpress_logged_in|woocommerce_items_in_cart|comment_author") { set $skip_cache 1; }
  3. In the PHP location:
    fastcgi_cache PHPCACHE;
    fastcgi_cache_valid 200 301 10m;
    fastcgi_cache_bypass $skip_cache;
    fastcgi_no_cache $skip_cache;
    add_header X-Cache $upstream_cache_status;
  4. Create the folder, test and reload: sudo mkdir -p /var/cache/nginx/fcgi && sudo nginx -t && sudo systemctl reload nginx.

Check with curl -I https://example.com: the second request should show X-Cache: HIT.

Warning: Never cache pages for logged-in users, carts or checkouts – visitors could see someone else's data. Test carefully on shops and membership sites.

Clearing the cache

Delete the cache folder contents (sudo rm -rf /var/cache/nginx/fcgi/*) after deployments, or use a WordPress plugin designed to purge Nginx FastCGI caches.

Troubleshooting

  • X-Cache always MISS or BYPASS: a cookie or query string triggers the skip rules, or the app sends Cache-Control: no-cache/Set-Cookie headers.
  • Stale content after updates: lower fastcgi_cache_valid or purge after publishing.
  • "too many open files": raise worker_rlimit_nofile and the systemd LimitNOFILE for nginx.

FAQ: Nginx performance tuning

Is FastCGI cache the same as LSCache?

Both are server-level page caches. LSCache on LiteSpeed integrates with plugins that purge automatically; FastCGI cache needs your own purge strategy.

Should I raise worker_connections to the maximum?

No. Higher values only help if you actually have that many concurrent connections and enough file descriptors.

Does gzip slow down the server?

It uses some CPU, but at moderate levels the bandwidth and speed gains far outweigh it.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.