TL;DR
- Architecture: Nginx is event‑driven, async, non‑blocking → great at static files & reverse proxying with low memory at high concurrency. Apache uses MPMs (process/thread models: prefork/worker/event) → flexible, strong .htaccess and module ecosystem.
- Performance: For many concurrent connections, Nginx typically uses less RAM and has lower overhead; Apache can be competitive with the event MPM and proper tuning.
- Config model: Nginx has centralized config (
nginx.conf, no per‑dir overrides). Apache supports per‑directory.htaccess, handy for shared hosting but slower and harder to audit. - Dynamic apps: Both commonly front PHP‑FPM, Node, Python, etc. Nginx proxies to app servers; Apache can do the same (or run modules like
mod_phpin legacy setups). - HTTP/2/3 & TLS: Both support HTTP/2 widely; HTTP/3/QUIC support exists but may require newer builds or modules depending on distro.
- Choosing: Prefer Nginx as edge/reverse proxy and for high‑throughput static delivery. Prefer Apache if you rely on
.htaccess, deep.htaccess‑driven rewrites, or existing Apache‑centric hosting. Many stacks run Nginx in front of Apache.
Architecture & mental model (60 seconds)
Nginx
master ──> workers (event loop)
• Non‑blocking sockets
• Predictable RAM use as concurrency rises
Apache (choose an MPM)
prefork: 1 process per request (no threads)
worker: processes with threads
event: like worker but keeps idle keep‑alives off threads
- Nginx shines as a reverse proxy, static file server, and TLS terminator.
- Apache offers a rich module system and per‑dir config with
.htaccess(shared hosting, CMS rewrites).
Practical comparison
| Topic | Nginx | Apache |
|---|---|---|
| Concurrency model | Event‑driven workers | MPMs (process/thread/event) |
| Memory use at scale | Low per connection | Higher with many processes/threads (tunable) |
| Static file throughput | Excellent | Good; can be strong with event MPM |
| Reverse proxy | First‑class (proxy_pass, upstream) | First‑class (mod_proxy, mod_proxy_balancer) |
| Per‑directory overrides | ❌ None | ✅ .htaccess |
| Config style | Declarative, centralized | Hierarchical; per‑vhost + optional .htaccess |
| PHP | Via php‑fpm (fastcgi) | Via php‑fpm (proxy_fcgi) or legacy mod_php |
| HTTP/2 | ✅ | ✅ (mod_http2) |
| HTTP/3 (QUIC) | Available in newer builds | Available via newer modules/builds (varies by distro) |
| Windows support | Limited/less common in prod | More common than Nginx but still Linux favored |
Minimal configs (side‑by‑side)
Nginx — static + reverse proxy to app on :3000
server {
listen 80;
server_name example.com;
# Static
root /var/www/html;
location /assets/ { try_files $uri =404; }
# Security headers (sample)
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy same-origin;
# Reverse proxy
location /api/ {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Apache — equivalent vhost (proxy to :3000)
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/html
# Static
<Directory /var/www/html>
Options FollowSymLinks
AllowOverride None
Require all granted
</Directory>
# Security headers (sample)
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "same-origin"
# Reverse proxy
ProxyPreserveHost On
ProxyPass /api/ http://127.0.0.1:3000/
ProxyPassReverse /api/ http://127.0.0.1:3000/
</VirtualHost>
# Required modules (enable on Debian/Ubuntu):
# a2enmod headers proxy proxy_http
PHP‑FPM fast‑path
Nginx
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Apache (proxy_fcgi)
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost/"
</FilesMatch>
Performance notes (real‑world)
- Keep‑alive & H2: Nginx’s event loop handles many idle connections efficiently (think SPA + H2 multiplexing). Apache’s event MPM narrows the gap—use it if high concurrency matters.
- Static assets: Nginx often leads; or place a CDN/edge cache in front of either server.
- App backends: Both are usually not the bottleneck if you’re proxying to Node/Go/Python—network and app performance dominate. Measure before migrating.
Config model trade‑offs
- Nginx (centralized): predictable and fast. Changes require editing vhost files and reloading.
- Apache (
.htaccess): allows unprivileged per‑site tweaks (shared hosting), but every request may trigger per‑dir lookup/parsing. Disable when you don’t need it:AllowOverride None.
TLS, H2/H3 quick sketch
- Enable HTTP/2 on both; ensure ALPN via modern TLS stacks.
- HTTP/3/QUIC support is available in current ecosystems but may need specific packages or flags; test behind your load balancer/CDN first.
Observability & ops
- Logs: both support JSON/custom formats; ship via
syslogor agents. - Reloads: Nginx supports zero‑downtime config reloads (
nginx -s reload). Apache supports graceful reloads (apachectl graceful). - Rate limiting / WAF: Nginx has
limit_req,limit_conn; Apache hasmod_evasive,mod_securityon both sides (WAF often at a gateway/CDN).
Migration paths
A) Nginx → Apache
- Translate server blocks to
<VirtualHost>; replaceproxy_set_headerwithProxyPreserveHost+ headers. - If you relied on Nginx rewrites, port to
mod_rewriteor app routes. - Enable
eventMPM for concurrency; disable.htaccessunless needed.
B) Apache → Nginx
- Convert
.htaccessrules to Nginxlocation/rewrite(many CMS have guides). - Replace
ProxyPasswithproxy_passand upstreams; test header parity (X-Forwarded-*). - Move PHP to php‑fpm and verify uploaded file limits and timeouts.
- Use
nginx -T/-s reloadand stage behind a temporary port or blue/green cutover.
C) Layered: Nginx in front of Apache
- Keep Apache for complex
.htaccess/rewrite‑heavy apps; let Nginx handle TLS, H2/H3, compression, and static assets; proxy dynamic requests to Apache.
Pitfalls & fixes
| Problem | Why it happens | Fix |
|---|---|---|
| .htaccess rules don’t work on Nginx | Nginx doesn’t read .htaccess | Convert to Nginx directives; remove per‑dir overrides |
| High RAM with Apache under load | Process/thread per connection | Use event MPM, tune MaxRequestWorkers, keep‑alive timeouts |
| 502 Bad Gateway after swap | Backend headers/timeouts differ | Align proxy headers; increase proxy_read_timeout/ProxyTimeout |
| File permission/SELinux denials | Different user contexts | Align user/group; add SELinux labels (:Z), check logs |
| HTTP/3 not working | Build/stack mismatch | Verify packages and LB/CDN support; fall back to H2 while testing |
Quick checklist
- [ ] Decide on centralized config (Nginx) vs per‑dir overrides (Apache).
- [ ] Enable HTTP/2; test HTTP/3 only after confirming platform support.
- [ ] If using PHP, standardize on php‑fpm with proper socket/timeout tuning.
- [ ] For high concurrency, use Nginx or Apache event MPM; set keep‑alive sensibly.
- [ ] Mirror proxy headers (
X‑Forwarded‑*) and timeouts across environments. - [ ] Plan a blue/green or canary migration with health checks and fast rollback.
One‑minute decision guide
- Need max throughput as an edge proxy/static server with simple, centralized config? → Nginx.
- Need per‑site overrides, legacy CMS rewrites, or shared hosting ergonomics? → Apache.
- Can’t choose? → Put Nginx in front of Apache; move complexity inward over time.