Apache httpd vs nginx: architecture, features and when to use which

Updated November 2026

Apache httpd and nginx can both serve static files, terminate TLS, speak HTTP/2 and act as reverse proxies. The real differences are architectural: Apache lets you change MPMs, load modules into the server and override settings per directory with .htaccess; nginx runs a small, fixed set of event-driven workers and keeps all configuration in central files. Which one fits depends on who edits the configuration and how your application runs, not on a benchmark headline.

At a glance

Apache httpd 2.4 and nginx compared (as of November 2026)
Apache httpdnginx
Current version2.4.69 (October 1, 2026)stable 1.30.6, mainline 1.31.8
LicenseApache License 2.02-clause BSD
Connection handlingPluggable MPM: event (default on modern systems), worker or preforkOne master process, several event-driven worker processes
Per-directory config.htaccess files, controlled by AllowOverrideNone; all settings in the main configuration
Dynamic contentIn-process modules, CGI (mod_cgi/mod_cgid), or proxying to FastCGI and HTTP backendsProxying to FastCGI, uwsgi, SCGI and HTTP backends; no built-in CGI
Loadable modulesLoadModule (DSO)load_module (dynamic modules, since 1.9.11)
HTTP/2mod_http2 (since 2.4.17)ngx_http_v2_module, http2 on;
HTTP/3 (QUIC)None in 2.4.x; an experimental mod_http3 was added to the development trunk on October 30, 2026ngx_http_v3_module since 1.25.0, documented as experimental
Test config / reloadapachectl -t / apachectl -k gracefulnginx -t / nginx -s reload

How Apache handles connections

Apache delegates connection handling to a Multi-Processing Module (MPM), chosen with LoadModule in 2.4:

prefork
One process per connection, no threads. Uses the most memory, but suits modules that are not thread-safe.
worker
Several processes, each with many threads; one thread per connection.
event
Like worker, but a listener thread in each process watches idle keep-alive connections and sockets that are only waiting to finish sending. Worker threads are free for new requests instead of waiting on slow or idle clients. The 2.4 documentation says the default MPM will almost always be event on modern Unix-like systems.

Because language runtimes and other extensions can run inside the server processes, a single Apache instance can do a lot on its own. The cost is that the MPM, its limits (MaxRequestWorkers, ThreadsPerChild) and the memory per process need sizing; see performance tuning.

How nginx handles connections

nginx has one master process that reads the configuration and manages workers, and several worker processes that do the actual request processing. The workers run as an unprivileged user and use the operating system's event mechanisms (for example epoll on Linux and kqueue on FreeBSD) to juggle many connections each. The number of workers is fixed in nginx.conf or set to auto to match the CPU cores.

nginx does not run application code itself (apart from its optional embedded Perl and njs JavaScript modules). PHP, Python and similar applications run in separate processes, such as PHP-FPM or an application server, and nginx passes requests to them over FastCGI, uwsgi, SCGI or HTTP.

On a reload (nginx -s reload), the master checks the new configuration, starts new workers with it and gracefully shuts down the old ones. Apache's apachectl -k graceful does the same job for its children.

.htaccess and configuration

The biggest practical difference is .htaccess. Apache can read per-directory files on every request, so users on shared hosting can add redirects, passwords and rewrite rules without touching the main configuration. The Apache documentation itself recommends putting everything in the main configuration whenever you have access to it, because directives there are read once at startup rather than on every request.

nginx has no equivalent. Every change goes into its configuration files and takes effect after a reload, which is faster and more predictable, but means the person changing a redirect needs access to the server configuration. This is one reason shared hosts and control panels have long been built around Apache, or around servers that read Apache-style rules.

Rewrite rules do not translate one-to-one. nginx's own guide on converting rules from Apache warns against copying RewriteCond %{HTTP_HOST} logic into if blocks and recommends separate server blocks instead:

# Apache (in the virtual host configuration)
RewriteEngine On
RewriteCond "%{HTTP_HOST}" "^example\.org$" [NC]
RewriteRule "^(.*)$" "https://www.example.org$1" [R=301,L]

# nginx
server {
    listen 80;
    server_name example.org;
    return 301 https://www.example.org$request_uri;
}

More on Apache's side in .htaccess files and mod_rewrite.

Performance without the myths

Old comparisons often pitted nginx against Apache's prefork MPM with PHP embedded in every process. That setup uses a full process per connection, so it runs out of memory long before an event-driven server does. A current Apache with the event MPM, PHP-FPM behind mod_proxy_fcgi and no .htaccess lookups is a different machine.

What actually changes results on either server:

Treat published benchmarks with care: they depend on hardware, configuration and workload. If performance matters, measure your own site with a tool such as ab, wrk or h2load against both setups, using the same content and TLS settings.

When to use which

Running nginx in front of Apache

Browsers HTTPS nginx :443 TLS, HTTP/2, HTTP/3 static files Apache httpd 127.0.0.1:8080 .htaccess, apps HTTP
nginx handles client connections; Apache only sees requests nginx forwards over the loopback interface.

This is a common way to get nginx's front end while keeping Apache for the application. nginx listens on ports 80 and 443; Apache moves to a local port. On the nginx side:

server {
    listen 443 ssl;
    http2 on;
    server_name www.example.com;
    ssl_certificate     /etc/letsencrypt/live/www.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;

    root /var/www/example;

    location /static/ {
        # files under /var/www/example/static/ are served by nginx directly
    }

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

By default nginx sends the backend address as the Host header, so setting Host $host keeps Apache's name-based virtual hosts working. $proxy_add_x_forwarded_for appends the client address to any existing X-Forwarded-For header.

On the Apache side, listen only on loopback (Debian: /etc/apache2/ports.conf; RHEL: /etc/httpd/conf/httpd.conf) and restore the real client address with mod_remoteip, so logs and Require ip rules see the visitor instead of 127.0.0.1:

Listen 127.0.0.1:8080

RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 127.0.0.1 ::1
SetEnvIf X-Forwarded-Proto "^https$" HTTPS=on
LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" proxied

<VirtualHost 127.0.0.1:8080>
    ServerName www.example.com
    DocumentRoot "/var/www/example"
    CustomLog ${APACHE_LOG_DIR}/example-access.log proxied
</VirtualHost>

The ${APACHE_LOG_DIR} variable exists on Debian and Ubuntu; on RHEL write logs/example-access.log. Enable the module on Debian with sudo a2enmod remoteip (Fedora's packages load it by default), then test both servers before reloading:

sudo apache2ctl -t && sudo systemctl reload apache2    # or: apachectl -t / httpd
sudo nginx -t && sudo systemctl reload nginx
curl -I https://www.example.com/

Only trust your own proxy. mod_remoteip trusts every host that sends the header unless you limit it with RemoteIPInternalProxy or RemoteIPTrustedProxy. Without that limit, and with Apache reachable from outside, anyone could fake their address in X-Forwarded-For.

More proxy options (timeouts, WebSockets) are in the reverse proxy guide.

Caddy and LiteSpeed in brief

Caddy is written in Go. It serves sites over HTTPS by default and gets certificates automatically, and its default protocol list is HTTP/1.1, HTTP/2 and HTTP/3. Its native configuration format is JSON, and the Caddyfile is a shorter format for writing configuration by hand.

LiteSpeed Web Server is a commercial server that its vendor describes as a drop-in replacement for Apache that can load Apache configuration files directly, which is why it appears on shared hosts with control panels. OpenLiteSpeed is the open-source (GPLv3) edition; its site says it understands Apache rewrite rules. Check the vendor's documentation for what is and is not compatible before migrating a complex Apache setup.

For which servers run how much of the web, see web server market share.

Sources