Toolmingo
Guides10 min read

Nginx vs Apache: Which Web Server Should You Use in 2025?

An in-depth comparison of Nginx and Apache — architecture, performance, configuration, use cases, modules, and which to choose for your stack.

Nginx and Apache are the two most widely deployed web servers in the world, together powering the majority of sites on the internet. They solve the same problem — serving HTTP traffic — but they do it with completely different architectures. Choosing between them (or combining them) can meaningfully affect your performance, scalability, and ops overhead.

At a glance

Nginx Apache
First release 2004 1995
Architecture Event-driven, asynchronous, non-blocking Process/thread-per-connection (default)
Concurrency model Single-threaded event loop Multi-Processing Modules (MPM)
Static file speed Excellent Good
Dynamic content Via reverse proxy (PHP-FPM, uWSGI) Via mod_php (in-process) or proxy
Config style Block directives, no per-directory overrides Directives + .htaccess per-directory
Memory under load Very low, predictable Higher, depends on MPM
Reverse proxy / LB Built-in, high performance mod_proxy (works, more overhead)
Market share (2025) ~34% (Netcraft) ~25%
Learning curve Moderate Moderate (.htaccess eases deployment)

Architecture: the core difference

This is the most important thing to understand. Everything else flows from it.

Apache: process / thread per connection

By default (prefork MPM), Apache spawns a new process for every connection. Each process handles one request at a time:

Request 1  →  Worker Process 1  (1 req × 1 proc)
Request 2  →  Worker Process 2
Request 3  →  Worker Process 3
...
1000 reqs  →  ~1000 processes  (high RAM)

With event MPM (modern default), Apache becomes more like Nginx — threads handle keep-alive connections without blocking. But the model is still heavier than Nginx's.

Nginx: event-driven, non-blocking

Nginx uses a fixed pool of worker processes (typically one per CPU core). Each worker handles thousands of connections via an event loop — no blocking I/O:

1 Worker Process
  ├── Connection 1  (waiting for DB)
  ├── Connection 2  (sending file)
  ├── Connection 3  (reading request)
  └── Connection 4  (writing response)
  ... (thousands more)

Result: Nginx uses far less RAM under high concurrency and handles the C10k problem natively.


Performance

Static files

Nginx is the clear winner for serving static assets. It was specifically designed to serve files efficiently from disk using sendfile() and kernel-level optimisations.

Scenario Nginx Apache
Static HTML/CSS/JS Very fast Fast
Large file streaming Excellent (sendfile) Good
10k concurrent connections ~2–4 MB RAM/worker 20–50 MB RAM/connection (prefork)
Latency at p99 under load Low Higher (process spawn overhead)

Dynamic content (PHP, Python, Node.js)

Neither server runs application code directly anymore. Both proxy to external processes:

Nginx Apache
PHP Proxy to PHP-FPM mod_php (in-process) or PHP-FPM proxy
Python Proxy to Gunicorn/uWSGI mod_wsgi or proxy
Node.js Proxy to Node process mod_proxy to Node process

mod_php (Apache only): Embeds PHP into every Apache worker process — simpler setup, but means every worker carries a full PHP interpreter, even when serving static files. Fine for small sites, wasteful at scale.

PHP-FPM (both): PHP runs in a separate pool of processes. Nginx + PHP-FPM is the most common high-performance PHP stack today (used by most managed WordPress hosts).


Configuration style

This is where the two servers feel most different day-to-day.

Nginx: block directives, centralised config

# /etc/nginx/sites-available/mysite.conf

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/mysite/public;
    index index.html index.php;

    # Serve static files directly; proxy PHP to FPM
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Cache static assets
    location ~* \.(css|js|png|jpg|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

Key points:

  • All config lives in /etc/nginx/ — no per-directory overrides
  • Changes require nginx -s reload (graceful, no downtime)
  • try_files replaces most rewrite rules
  • No .htaccess — intentional (disk I/O on every request)

Apache: directives + .htaccess

# /etc/apache2/sites-available/mysite.conf

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/mysite/public

    <Directory /var/www/mysite/public>
        Options -Indexes +FollowSymLinks
        AllowOverride All   # enables .htaccess
        Require all granted
    </Directory>
</VirtualHost>
# /var/www/mysite/public/.htaccess  (per-directory overrides)

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [QSA,L]

Header set Cache-Control "max-age=31536000, public"

Key points:

  • .htaccess lets each directory (even untrusted users) configure the server
  • Great for shared hosting — users can deploy PHP apps without root
  • Apache re-reads .htaccess on every request (small performance cost)
  • mod_rewrite is powerful but has a steeper learning curve than Nginx rewrite

Reverse proxy and load balancing

Nginx was built as a reverse proxy from day one. Its upstream module is lean and handles thousands of backend connections without spawning extra processes:

upstream backend {
    least_conn;                          # load balancing algorithm
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    server 10.0.0.3:3000 backup;
    keepalive 64;                        # reuse connections
}

server {
    location /api/ {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_cache_bypass $http_upgrade;
    }
}

Apache's mod_proxy works, but adds overhead:

<Proxy balancer://backend>
    BalancerMember http://10.0.0.1:3000
    BalancerMember http://10.0.0.2:3000
    ProxySet lbmethod=byrequests
</Proxy>

ProxyPass /api/ balancer://backend/
ProxyPassReverse /api/ balancer://backend/

For pure reverse proxy / API gateway workloads, Nginx is the default choice. For this reason Nginx is often used as the front-end even when Apache handles the PHP backend.


SSL/TLS

Both support TLS well. Nginx config is somewhat simpler:

# Nginx TLS (with Let's Encrypt / Certbot)
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_session_cache   shared:SSL:10m;
}
# Apache TLS
<VirtualHost *:443>
    ServerName example.com
    SSLEngine on
    SSLCertificateFile      /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile   /etc/letsencrypt/live/example.com/privkey.pem
    SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite          HIGH:!aNULL:!MD5
</VirtualHost>

Certbot has first-class plugins for both (certbot --nginx and certbot --apache).


Modules and extensibility

Nginx Apache
Module loading Compiled in (static) or dynamic (.so) Dynamic at runtime (a2enmod)
Key modules ngx_http_proxy, ngx_http_gzip, ngx_http_cache, lua-nginx-module mod_rewrite, mod_security, mod_php, mod_wsgi
Hot-reload modules Yes (nginx -s reload) Yes (graceful restart)
Custom scripting Lua (OpenResty), NJS mod_perl, mod_lua
WAF ModSecurity (Nginx connector) ModSecurity (native)

Apache's runtime module loading (a2enmod ssl, a2enmod rewrite) makes it easy to enable/disable features without recompiling. Nginx's compiled modules require a rebuild, though most distros ship with common modules pre-built.


WordPress: which is better?

Both work well. Most managed WordPress hosts (Kinsta, WP Engine, Cloudways) use Nginx + PHP-FPM for performance. Shared hosts (cPanel/Plesk) often use Apache because .htaccess makes WordPress's permalink rewriting work out of the box with no server config.

Nginx Apache
Permalink rewriting Requires try_files in server config Automatic via .htaccess + mod_rewrite
Performance Better under load Fine for low-traffic sites
Plugin compatibility Excellent Excellent
Managed host default Yes (most modern hosts) Yes (shared/cPanel hosts)
W3 Total Cache / WP Rocket Full support Full support

For Nginx, WordPress's permalink rewriting needs this in your server block:

location / {
    try_files $uri $uri/ /index.php?$args;
}

When to use Nginx

  • High-concurrency applications (APIs, streaming, real-time)
  • Reverse proxy / load balancer in front of app servers
  • Serving static assets (CDN origin, SPA builds)
  • Microservices architecture (API gateway)
  • Docker / Kubernetes ingress (Nginx Ingress Controller is the most popular K8s ingress)
  • High-traffic WordPress or Laravel with PHP-FPM

When to use Apache

  • Shared hosting environments (.htaccess is required)
  • Deploying apps that rely on .htaccess for rewriting (legacy PHP apps)
  • Teams that are already familiar with Apache config
  • Environments that need easy per-directory config by non-root users
  • When mod_security or mod_wsgi integration is required without extra plumbing

Nginx + Apache together

A common pattern is to put Nginx in front of Apache as a reverse proxy. Nginx handles:

  • Static files (serves them directly, never touches Apache)
  • SSL termination
  • Gzip compression
  • Rate limiting
  • Caching

Apache handles:

  • Dynamic PHP via mod_php (no PHP-FPM needed)
  • .htaccess rules (existing app compatibility)
Internet → Nginx (port 80/443) → Apache (port 8080, localhost only)

This was popular in the 2010s for legacy apps that couldn't easily migrate away from mod_php. Today, most new deployments go Nginx-only with PHP-FPM.


Full comparison

Feature Nginx Apache
Architecture Event-driven, async Process/thread-based (event MPM available)
Concurrency Excellent (C10k native) Good (event MPM), limited (prefork)
Static file performance Excellent Good
Memory efficiency Very high Moderate
Config syntax Block directives Directives + .htaccess
Per-directory overrides No Yes (.htaccess)
Module loading Mostly compiled Dynamic at runtime
Reverse proxy First-class mod_proxy (works, more overhead)
Load balancing Built-in, fast mod_proxy_balancer
PHP integration PHP-FPM proxy mod_php or PHP-FPM
SSL/TLS Excellent Excellent
HTTP/2 Yes Yes (mod_http2)
HTTP/3 / QUIC Experimental (1.25+) No (third-party patch)
WebSockets Yes Yes (mod_proxy_wstunnel)
WordPress Excellent (try_files) Excellent (native .htaccess)
Kubernetes ingress Default choice Available (less common)
Community / docs Large, active Very large, 30 years of docs
License BSD-2-Clause Apache 2.0

Common mistakes

Mistake Why it's a problem Fix
Using if in Nginx location blocks if is "evil" in Nginx — unexpected behaviour with try_files Use try_files, map, or return instead
Forgetting proxy_set_header Host Backend sees wrong hostname Always set Host, X-Real-IP, X-Forwarded-For
AllowOverride All on high-traffic Apache Re-reads .htaccess every request Use AllowOverride None and move rules to VirtualHost
Mixing Nginx and Apache config syntax They are not compatible Keep configs in separate files, know which server you're editing
Serving PHP files through Nginx directly Nginx can't execute PHP — it will serve PHP source code Always proxy .php to PHP-FPM
No worker_processes auto in Nginx Uses only 1 CPU core by default Set worker_processes auto; in nginx.conf
Not reloading after config change Old config stays active Run nginx -s reload or apache2ctl graceful
Hardcoding 127.0.0.1 in upstream Breaks in Docker/K8s Use service names / environment variables

FAQ

Which is faster, Nginx or Apache?
For static files and high concurrency, Nginx is faster. For dynamic content (PHP, Python), performance is similar once both use FPM/WSGI proxying. The difference matters most at scale — at low traffic, both are more than fast enough.

Can Nginx read .htaccess files?
No. Nginx intentionally does not support .htaccess. All config must be in the server config files. This is a feature, not a limitation — it avoids per-request disk reads and keeps config centralised.

Which should I use for WordPress?
If you control the server config, Nginx + PHP-FPM is the faster choice. If you're on shared hosting (cPanel), Apache with .htaccess is the practical choice. Both support WordPress fully.

Is Apache dead?
No. Apache still powers roughly 25% of the web and is actively developed. It's the better choice for shared hosting and legacy apps. The narrative that "Nginx replaced Apache" is overstated — they serve different niches.

What about Caddy, LiteSpeed, or Traefik?
Caddy auto-provisions TLS and has a simpler config — good for small teams. LiteSpeed is drop-in Apache compatible with better performance (used by LiteSpeed Web Host). Traefik excels as a Kubernetes-native reverse proxy/ingress. Nginx remains the most widely deployed in production.

How do I migrate from Apache to Nginx?

  1. Install Nginx alongside Apache (Apache on port 8080, Nginx on 80/443)
  2. Convert .htaccess rules to Nginx location blocks (use online converters as a starting point)
  3. Test thoroughly in staging
  4. Switch DNS / remove Apache once stable
    The main challenge is translating mod_rewrite rules to Nginx rewrite/try_files/return directives.

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools