Apache vs Nginx: Architecture, How They Work, and Use Cases
#apache#nginx#web-server#reverse-proxy#devops#infrastructure
Apache HTTP Server and Nginx are the two most widely used web servers (and reverse proxies). They differ in architecture and concurrency model, which leads to different performance characteristics and use cases. This post describes how each works, compares them, and lists typical use cases for Apache and Nginx.
Apache HTTP Server: architecture and how it works
Overview
Apache is a process- and thread-based web server. A small number of control processes manage a pool of worker processes or threads that handle requests. Which workers run is determined by the Multi-Processing Module (MPM).
Core components
| Component | Role |
|---|---|
| Main process | Starts, monitors, and restarts child processes; reads configuration. |
| MPM (Multi-Processing Module) | Defines how connections are accepted and which workers serve them. |
| Worker processes/threads | Accept connections, read requests, run the request through the module chain, send responses. |
| Modules | Add features (PHP, auth, rewrite, SSL, logging). Many features are implemented as loadable modules. |
Multi-Processing Modules (MPMs)
- prefork: One process per connection. No threads. Each process handles one request at a time. High memory use; good for apps that are not thread-safe (e.g. older PHP). Default on many Linux distros for compatibility.
- worker: Multi-threaded. Fewer processes, each with multiple threads; one thread per connection. Lower memory than prefork; requires thread-safe code.
- event: Like worker, but delegates keep-alive connections to a dedicated thread so worker threads are freed for new requests. Better for long-lived HTTP keep-alive. Default in many recent Apache installs.
Request flow (simplified)
+------------------+
| Main process |
| (config, start |
| children) |
+--------+---------+
|
+-------------------+-------------------+
| | |
v v v
+-------------+ +-------------+ +-------------+
| Worker 1 | | Worker 2 | | Worker N |
| (process or | | (process or | | (process or |
| threads) | | threads) | | threads) |
+------+------+ +------+------+ +------+------+
| | |
| 1 connection | 1 connection | 1 connection
| per thread/ | per thread/ | per thread/
| process | process | process
v v v
[Socket] [Socket] [Socket]
\ | /
\ | /
+-------------------+-------------------+
|
Incoming TCP
connections
- Connection: A TCP connection is accepted (by the main process or a listener thread, depending on MPM).
- Assignment: The MPM assigns the connection to a worker (process or thread).
- Request handling: The worker reads the request, runs it through the module chain (e.g. rewrite, auth, handler for PHP or static files).
- Response: The worker sends the response and, with keep-alive, may reuse the connection for the next request; otherwise it closes and is ready for another connection.
Each connection ties up one thread or process for the duration of the request (and keep-alive). So 10,000 concurrent connections typically require on the order of 10,000 threads or processes (or a mix), which increases memory and context-switching.
Important Apache traits
- .htaccess: Per-directory configuration (auth, rewrite, etc.) is read from the filesystem on request. Flexible but adds I/O and can slow things down under load.
- Modules: Rich ecosystem (mod_php, mod_rewrite, mod_ssl, mod_proxy, etc.). Many behaviors are implemented as modules.
- Configuration: Typically one main config file plus includes and .htaccess; syntax and layout differ from Nginx.
Nginx: architecture and how it works
Overview
Nginx uses an event-driven, asynchronous model. It does not dedicate one thread or process per connection. A small number of worker processes run an event loop and handle many connections each using non-blocking I/O and OS mechanisms like epoll (Linux) or kqueue (BSD/macOS).
Core components
| Component | Role |
|---|---|
| Master process | Reads config, opens listen sockets, starts and manages workers; can reload config gracefully. |
| Worker processes | Run the event loop; accept connections, read/write on many sockets without blocking. |
| Event loop | Uses epoll/kqueue/etc. to wait for I/O; when a socket is ready, the worker does a small amount of work (read, parse, write) and moves on. |
| Modules | Built-in or loadable (HTTP, stream, SSL, rewrite, etc.); no .htaccess-style per-dir file parsing. |
Request flow (simplified)
+------------------+
| Master process |
| (config, manage |
| workers) |
+--------+---------+
|
| spawns
v
+------------------+
| Worker process |
| +-------------+ |
| | Event loop | |
| | (epoll/ | |
| | kqueue) | |
| +------+------+ |
| | |
| Many connections (non-blocking)
| | |
+---------+--------+
|
+------+------+------+------+
v v v v v
[Conn1] [Conn2] [Conn3] ... [Conn N]
- Listen: Master opens listen sockets and workers wait on them (often via accept mutex).
- Accept: A worker accepts a new connection and adds the socket to its event loop (e.g. epoll).
- Non-blocking I/O: The worker never blocks on a single connection. When data is ready (read or write), the kernel notifies the event loop; the worker does a small chunk of work (read headers, parse, maybe disk I/O, send response) and returns to the loop.
- Many connections per worker: One worker can handle thousands of idle or slow connections because it only does work when I/O is ready.
So 10,000 concurrent connections may need only a few workers (e.g. one per CPU), each handling thousands of connections. Memory use stays relatively low and scales with connection count much better than “one thread per connection.”
Important Nginx traits
- No .htaccess: Configuration is only in the main config (and included files). No per-directory file lookups on each request; better for performance and predictable behavior.
- Static and proxy first: Optimized for serving static files and acting as reverse proxy and load balancer; dynamic content is usually delegated to an app server (e.g. PHP-FPM, uWSGI, Node, backend services).
- Configuration: Declarative, block-based (server {}, location {}, upstream {}). Reload is graceful (no dropped connections when configured correctly).
Apache vs Nginx: comparison
| Aspect | Apache | Nginx |
|---|---|---|
| Concurrency model | Process- or thread-based (one connection per thread/process in practice). | Event-driven, asynchronous; many connections per worker. |
| Scalability (many connections) | Limited by threads/processes and RAM; 10k connections → many workers/threads. | Scales to tens of thousands of connections with few workers. |
| Memory | Grows roughly with number of connections (each thread/process has stack and state). | Grows slowly; small number of workers, each handling many connections. |
| Static content | Serves well; can be tuned; often used behind a cache or Nginx in front. | Very efficient; sendfile, efficient buffering; commonly used for static and as edge server. |
| Dynamic content | Native modules (e.g. mod_php) run inside the server; simple deployment. | Typically proxies to an app server (PHP-FPM, uWSGI, etc.); no in-process PHP. |
| Per-directory config | .htaccess allows per-dir overrides; flexible, cost on every request in that dir. | No .htaccess; all config in main config; no per-request dir lookup. |
| Configuration style | Global + includes + .htaccess; directive-based. | Declarative blocks (server, location, upstream); different syntax. |
| Modules | Large ecosystem; many features as loadable modules. | Core + many modules; fewer “kitchen sink” modules than Apache. |
| SSL/TLS | mod_ssl; fine for typical use. | Built-in; SNI, HTTP/2, modern TLS; often used for termination. |
| Reverse proxy / load balancing | mod_proxy, mod_proxy_balancer; capable. | Designed for this; upstream, load balancing, caching; very common. |
| OS support | All major OSes; long history. | All major OSes; strong on Linux/BSD. |
| Learning curve | Many used to it; .htaccess familiar to shared hosting users. | Different model and config; easy once you know blocks and proxy. |
Use cases: when to use Apache
- Classic LAMP (Linux, Apache, MySQL, PHP) with mod_php: PHP runs inside Apache; single server, simple setup; good for small/medium sites and legacy apps.
- Per-directory configuration: You need .htaccess so that different directories (e.g. per-customer or per-app) can override auth, rewrite, or options without editing the main config or restarting the server.
- Shared hosting: Hosting providers often use Apache so customers can drop .htaccess and get auth, redirects, and overrides without shell access.
- Heavy mod_rewrite / Apache-specific rules: Existing configs and docs are in Apache terms; migration cost is high; team is comfortable with Apache.
- Applications that assume Apache: Some apps (or installers) assume Apache and generate .htaccess or Apache config snippets.
- Rich module set: You rely on a specific Apache module (e.g. mod_security with a given setup, mod_perl, mod_wsgi in a certain way) and prefer to keep the same stack.
- Mixed static + in-process dynamic: You serve a mix of static and dynamic (e.g. PHP inside Apache) on one server and don’t need to maximize concurrent connection count.
Use cases: when to use Nginx
- High concurrency: Many simultaneous connections (e.g. long polling, WebSockets, many idle keep-alives); Nginx’s event-driven model handles them with less memory and CPU.
- Static file serving: Images, CSS, JS, downloads; Nginx is very efficient and often used as the front-end for static assets (with or without Apache behind for dynamic).
- Reverse proxy: Single entry point for multiple backends (Apache, Node, Go, etc.); Nginx as gateway in front of app servers.
- Load balancing: Distribute traffic across several backend servers (HTTP or TCP/UDP with stream module); health checks, least_conn, ip_hash, etc.
- SSL termination: Terminate TLS at Nginx and send plain HTTP to backends; centralize certificate and HTTP/2 handling.
- Microservices / API gateway: Route by path or host to different services; rate limiting, caching, and auth at the edge.
- Low memory: Embedded or constrained environments where a small number of worker processes is an advantage.
- Caching: Proxy cache for backends; cache static or dynamic responses at the edge.
- New or greenfield projects: When you don’t need .htaccess or in-process PHP, Nginx (or Nginx + PHP-FPM) is a common default for performance and simplicity.
Hybrid: Nginx in front, Apache behind
A very common pattern is Nginx as reverse proxy and Apache (or another app server) for dynamic content:
- Nginx: Handles client connections, TLS, static files, caching, and load balancing; passes dynamic requests to Apache (or PHP-FPM, etc.).
- Apache (or app server): Runs the application (e.g. PHP via mod_php or PHP-FPM, Python, etc.).
You get Nginx’s scalability and efficiency for connections and static content, and Apache’s (or the app’s) strengths for dynamic content and .htaccess if needed on the backend.
Summary
| Server | Architecture | Strength | Typical role |
|---|---|---|---|
| Apache | Process/thread per connection; MPM (prefork, worker, event); modules; .htaccess | Flexibility, .htaccess, in-process PHP, rich modules | LAMP, shared hosting, when you need per-dir config or Apache-specific features |
| Nginx | Event-driven, async; few workers, many connections; no .htaccess | Concurrency, static files, reverse proxy, low memory | High traffic, static serving, proxy, load balancer, SSL termination, API gateway |
Choose Apache when you need per-directory configuration, in-process PHP, or a classic LAMP/shared-hosting style. Choose Nginx when you need high concurrency, efficient static serving, or a reverse proxy/load balancer. Use both when Nginx handles the edge and proxies to Apache (or another app server) for dynamic content.
Comments