Apache vs nginx

Architecture and Configuration Style Compared

Igor Sysoev released nginx 75 on 4 October 2004 for what prefork handled worst, thousands of idle connections: one single-threaded worker per core runs an event loop over all its sockets. Apache 129 's event MPM (MPMs and Workers) closed most of that gap; what remains is configuration. nginx has no .htaccess, so every per-directory rule goes in the server configuration, edited by root and reloaded.

Apache and nginx as reverse proxies, September 2026
Aspect Apache 2.4.66 nginx 1.30.5
Connection model prefork, worker or event MPM event loop per worker process
Per-directory config .htaccess, read per request none; reload to change
PHP mod_php or PHP-FPM PHP-FPM only (fastcgi_pass)
Forwarded headers added automatically one proxy_set_header each
Active health checks free (mod_proxy_hcheck) commercial NGINX Plus only
Sticky sessions route cookie sticky cookie, free since 1.29.6
HTTP/3 not available since 1.25.0, experimental, opt-in build

The same proxy in nginx, run from the official image (docker run -d -p 8803:80 nginx:stable, which reported nginx/1.30.5) against the same backends:

The nginx equivalent of Sections 2.3.1 and 2.3.2 (conf.d/default.conf)CSS
upstream pool {
  zone pool 64k;
  server host.docker.internal:9203;
  server host.docker.internal:9213;
}
server {
  listen 80;
  server_tokens off;
  absolute_redirect off;
  location /app/ {
    proxy_pass http://host.docker.internal:9203/;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host  $http_host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_redirect http://127.0.0.1:9203/ /app/;
  }
  location /pool/ { proxy_pass http://pool/; }
}

The forged-header request of ProxyPass arrived with X-Forwarded-For: 203.0.113.9, 172.17.0.1 and no X-Forwarded-Server, since nginx adds only what you list. Two lines were added after a run went wrong. Without zone, four requests to /pool/ all reached app1, because each of the 36 workers kept its own round-robin position; the shared zone makes it global. Without absolute_redirect off, the redirect became http://127.0.0.1/app/dashboard, because nginx in the container believes it is on port 80.