Apache httpd vs nginx: architecture, features and when to use which
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 | nginx | |
|---|---|---|
| Current version | 2.4.69 (October 1, 2026) | stable 1.30.6, mainline 1.31.8 |
| License | Apache License 2.0 | 2-clause BSD |
| Connection handling | Pluggable MPM: event (default on modern systems), worker or prefork | One master process, several event-driven worker processes |
| Per-directory config | .htaccess files, controlled by AllowOverride | None; all settings in the main configuration |
| Dynamic content | In-process modules, CGI (mod_cgi/mod_cgid), or proxying to FastCGI and HTTP backends | Proxying to FastCGI, uwsgi, SCGI and HTTP backends; no built-in CGI |
| Loadable modules | LoadModule (DSO) | load_module (dynamic modules, since 1.9.11) |
| HTTP/2 | mod_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, 2026 | ngx_http_v3_module since 1.25.0, documented as experimental |
| Test config / reload | apachectl -t / apachectl -k graceful | nginx -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:
- Idle and slow connections. nginx and Apache's event MPM both avoid tying a thread to every idle keep-alive connection; prefork and worker do not.
- Per-request file lookups. With
AllowOverrideenabled, Apache checks for.htaccessin every directory on the path. Turn it off where you do not need it. - The application. For dynamic pages, the PHP, Python or Java back end usually costs far more than the web server in front of it.
- Caching and compression settings, TLS session reuse, and how many modules are loaded.
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
- Choose Apache when users or applications depend on
.htaccess(shared hosting, many PHP applications ship rewrite rules that way), when you need modules that exist only for httpd, or when your team already knows its configuration well. - Choose nginx when you mainly serve static files and proxy to application servers, want HTTP/3 at the edge (still marked experimental in nginx), or want one small front end for many back ends.
- Use both when you want nginx's front-end features but must keep an application that relies on Apache behavior.
Running nginx in front of Apache
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
- Multi-Processing Modules (MPMs), Apache HTTP Server 2.4 documentation
- event MPM, Apache HTTP Server 2.4 documentation
- .htaccess files tutorial, Apache HTTP Server 2.4 documentation
- New features in Apache HTTP Server 2.4 (mod_http2 and other modules)
- Module index, Apache HTTP Server 2.4 documentation
- Commit moving mod_http3 (Google Summer of Code) into trunk, October 30, 2026, apache/httpd on GitHub
- mod_remoteip, Apache HTTP Server 2.4 documentation
- Licenses, Apache Software Foundation
- Download page, Apache HTTP Server Project (2.4.69)
- nginx: features and architecture, nginx.org
- nginx download page (stable and mainline versions)
- Beginner's Guide, nginx documentation
- Command-line parameters, nginx documentation
- Converting rewrite rules, nginx documentation
- ngx_http_proxy_module, nginx documentation
- ngx_http_v2_module, nginx documentation
- ngx_http_v3_module, nginx documentation
- Core functionality (load_module), nginx documentation
- Caddy features
- Automatic HTTPS, Caddy documentation
- Caddyfile global options (protocols), Caddy documentation
- LiteSpeed Web Server product page, LiteSpeed Technologies
- OpenLiteSpeed project site