Skip to content
Faizul Karim Fahim

Nginx vs Apache Performance Tuning Guide

A practical sysadmin guide to Nginx and Apache configuration and performance tuning for production Linux servers.

Published 4 min read DevOps
Terminal screen showing Nginx and Apache configuration files on an Ubuntu server
On this page
  1. Nginx Configuration and Worker Tuning
  2. Optimizing Worker Processes and Connections
  3. HTTP Core and Keepalive Buffers
  4. Apache MPM Event Configuration
  5. Configuring MPM Event in Apache
  6. Reverse Proxy and Caching Best Practices
  7. Nginx Reverse Proxy Setup
  8. My setup
  9. What went wrong (gotchas)

Default Nginx and Apache packages leave a lot of performance on the table, so the nginx and apache configuration is one of the first things I tune on a new VPS. While both web servers power the majority of the modern web, their architectural differences demand completely different tuning strategies. Choosing the right settings prevents resource exhaustion, minimizes latency, and keeps your applications stable under heavy traffic spikes.

On my servers I usually audit multi-node clusters and single boxes alike, keeping an eye on how these servers handle memory, connections, and worker processes. Sometimes, optimizing infrastructure goes hand in hand with network diagnostics, much like troubleshooting local bottlenecks as detailed in my guide on why my 300 Mbps router shows only 144 Mbps on Wi-Fi.

Nginx Configuration and Worker Tuning

Nginx uses an asynchronous, event-driven architecture designed to handle thousands of concurrent connections with minimal memory footprints. Tuning Nginx begins with the main configuration file located at /etc/nginx/nginx.conf.

Optimizing Worker Processes and Connections

The worker_processes directive should generally match the number of available CPU cores on your server. Setting this to auto lets Nginx detect the core count dynamically.

user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}
  • worker_rlimit_nofile: Increases the limit on maximum open files for worker processes, preventing the dreaded 502 Bad Gateway or too many open files errors.
  • worker_connections: Defines how many simultaneous connections each worker process can handle. Combined with worker_processes, this dictates your max client capacity.
  • use epoll: Forces the scalable Linux kernel polling method.
  • multi_accept: Allows a worker process to accept all new connections rather than one by one.

HTTP Core and Keepalive Buffers

To optimize throughput for web applications, I adjust my HTTP block settings to reduce latency and drop slow clients.

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    client_body_buffer_size 128k;
    client_max_body_size 10m;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 4k;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;
}

Apache MPM Event Configuration

Historically, Apache relied on the Prefork Multi-Processing Module, spawning a separate process for every connection. On my production boxes I always use the Event MPM, which handles connections asynchronously similar to Nginx, making it vastly superior for high concurrency.

Configuring MPM Event in Apache

I open the MPM configuration file at /etc/apache2/mods-available/mpm_event.conf on Ubuntu systems and adjust the threading parameters:

<IfModule mpm_event_module>
    StartServers             3
    MinSpareThreads          75
    MaxSpareThreads          250
    ThreadLimit              64
    ThreadsPerChild          25
    MaxRequestWorkers        400
    MaxConnectionsPerChild   10000
</IfModule>
  • MaxRequestWorkers: Limits the total number of simultaneous requests that can be served. Calculate this based on available RAM to avoid swapping.
  • ThreadsPerChild: Sets the number of worker threads created by each child process. Combined with MaxRequestWorkers, this defines your active process pool.
  • MaxConnectionsPerChild: Forces child processes to recycle after serving a set number of requests, preventing memory leaks in older modules.

Reverse Proxy and Caching Best Practices

A very common architecture deploys Nginx in front of Apache. Nginx handles static assets, SSL termination, and slow clients, while Apache processes dynamic backend code like PHP.

Nginx Reverse Proxy Setup

I configure Nginx to pass dynamic traffic to Apache running on port 8080:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        root /var/www/html;
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }
}

Properly tuning your web stack also helps maintain overall uptime and system responsiveness, which is equally critical when optimizing environments for search indexing, as outlined in my guide on how to optimize website for Googlebot crawling.

My setup

On a standard 4 GB VPS running Ubuntu 24.04, I keep things simple. I disable the default Apache site, enable Nginx for port 80 and 443, and drop Apache down to port 8080. For PHP apps, I skip Apache entirely and run PHP-FPM directly behind Nginx via Unix sockets. Honestly, running both web servers on a tiny instance is overkill, but it works well when migrating legacy apps that rely heavily on .htaccess rewrite rules.

What went wrong (gotchas)

  • Forgetting to run sudo nginx -t or sudo apache2ctl configtest before reloading services will take your production site offline if there is a stray semicolon.
  • Setting MaxRequestWorkers too high in Apache will exhaust your RAM and push the kernel into swap death during traffic spikes.
  • When Nginx talks to Apache over a reverse proxy, missing X-Forwarded-For headers will log your own server IP for every single visitor.

Frequently asked questions

Should I use Nginx or Apache for a high-traffic static site?

Nginx is generally preferred for high-traffic static content due to its event-driven architecture, which consumes significantly less memory under heavy concurrent loads.

Can Nginx and Apache run on the same server?

Yes, they frequently run together where Nginx acts as a reverse proxy handling incoming traffic, static files, and SSL termination, passing dynamic requests to Apache running on a backend port.

How do I test my web server configuration changes safely?

Always run validation commands before reloading services. For Nginx use 'sudo nginx -t', and for Apache use 'sudo apache2ctl configtest'.

// keep reading

Related posts

All blog posts
Diagram illustrating Nginx acting as a reverse proxy in front of an Apache server.
Security 4 min read

Configuring Nginx as a Reverse Proxy for Apache

I've been using Nginx as a reverse proxy in front of Apache for years, and it's significantly improved my server's performance and security. Here's how I set it up, and some of the common pitfalls I've learned to avoid.