1.1 The Web Server Landscape: Where NGINX Fits
Concept-heavy, hands-light. The hands arrive in 1.2 — this lesson builds the vocabulary.
Basic
What you will learn
What a web server actually is — the three jobs hiding behind that name.
The C10K problem, and why it spawned NGINX in 2002.
Process-per-connection vs. event-driven architectures — with diagrams.
The modern roles NGINX plays beyond "serving files".
Open source NGINX vs. NGINX Plus — what money actually buys.
1. Three jobs called "web server"
When someone says "web server", they may mean any of three different things, and NGINX is all three at once. Keeping these separate in your head will make every later configuration decision easier:
Job | Name | Question it answers |
|---|---|---|
1. File serving | Web server | "Can you read
|
2. Protocol termination | HTTP server | "Can you parse my HTTP/1.1, later my TLS, maybe my HTTP/3?" |
3. Routing & proxying | Reverse proxy / gateway | "Where should this request actually go, and what should happen to it on the way?" |
A pure file server (like Python's
http.server) does only job 1. NGINX was designed, from its first line of code, to be excellent at jobs 2 and 3 — file serving came almost as a side effect of being good at moving bytes.
2. 2002: the C10K problem
In the late 1990s the dominant web server, Apache, used a simple model: one process (or thread) per connection. A web page with 30 images and a keep-alive browser meant dozens of live processes per visitor. At 10,000 simultaneous connections — "the C10K problem" — the server spent its CPU not on HTTP, but on scheduling itself: context switches, lock contention, and memory for thousands of mostly-idle stacks.
Igor Sysoev's answer, first released in 2004 and written while he ran one of Russia's largest sites (Rambler), was radical simplicity:
Don't spawn anything per connection. Spawn a fixed number of workers up front, and let each one juggle thousands of connections inside a loop.
That is NGINX: N processes (one per CPU core), each running an event loop.
3. The two architectures, side by side
Process-per-connection (classic Apache prefork)
client connections: ●───●───●───●───●───●
│ │ │ │ │ │
[proc][proc][proc][proc][proc][proc] ← 1 process EACH
│ │ │ │ │ │ (blocked, reading
▼ ▼ ▼ ▼ ▼ ▼ or just waiting)
kernel socket buffersEach connection pins one process. If the client is slow (or idle on keep-alive), the process blocks — it occupies memory and a scheduler slot while doing nothing. 10,000 connections ≈ 10,000 processes. The kernel burns most of its time switching between them.
Event-driven (NGINX)
client connections: ●───●───●───●───●───●───●───●───●───●
│ │ │ │ │ │ │ │ │ │
╲ │ ╲ │ ╲ │ ╲ │ ╲ │ ... all multiplexed ...
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐
│ worker (core0)│ │ worker (core1)│ ← 2 fixed workers
│ event loop: │ │ event loop: │ (2-core machine)
│ epoll_wait()│ │ epoll_wait()│
└─────────────┘ └─────────────┘Each worker calls the kernel's
epoll_wait() and asks one question:
"which of my thousands of sockets have something for me right now?" The kernel returns a short list of
ready sockets; the worker services exactly those, in a non-blocking way, and loops. Slow clients cost nothing — a socket that has nothing to do simply never appears in the ready list.
The cost of the trade
Event-driven is not free, and an honest engineer knows the price:
Blocking is banned. One blocking syscall (disk I/O, DNS) would freeze the whole worker's loop. NGINX therefore wraps everything — file I/O, upstream connects, sends — in non-blocking state machines, and offloads what it must (e.g. reading large request bodies to disk) carefully.
CPU-bound work doesn't mix. The loop is for I/O. Heavy per-request computation (rendering, ML) does not belong in NGINX — it belongs behind NGINX. This is why NGINX is a gateway and your app is the application.
Shared state needs discipline. Workers don’t share per-request memory; anything cross-worker (rate-limit counters, caches) lives in explicit shared-memory zones — a concept you will configure in Lesson 7.2 (limit_req_zone) and Lesson 8.2 (proxy_cache_path’s keys_zone).
4. Where NGINX sits today
The event-driven core made NGINX the natural choice whenever something needs to sit in front of other things. Its modern roles, in rough order of how often you'll meet them:
Role | What it does | Covered in |
|---|---|---|
Reverse proxy | Accepts client traffic, forwards to backend apps | Module 4 |
Load balancer | Spreads traffic across a pool of backends | Module 5 |
TLS termination point | Decrypts at the edge, speaks plain HTTP inside | Module 6 |
API gateway | Routing, rate limits, auth, traffic shaping | Modules 4, 7, 11 |
CDN edge / cache | Serves repeated content from memory/disk | Module 8 |
Kubernetes ingress | The front door of a cluster | Module 14 |
L4 TCP/UDP proxy | Routes raw TCP/UDP (databases, media, MQTT) | Module 10 |
Observable control point | Logging, metrics, tracing of all traffic | Module 12 |
One binary, one config language, all of these roles. That economy of concepts is why NGINX knowledge compounds: everything you learn in Module 4 about proxying is reused by Modules 5–14.
5. Open source vs. NGINX Plus
Everything in this series applies to
open-source NGINX (the
nginx.org binary, BSD-2-Clause-style license, currently maintained by F5 after acquiring NGINX Inc. in 2019). A commercial sibling,
NGINX Plus, adds proprietary features on top of the same core. Know what's behind the paywall so you can decide consciously:
Capability | Open source | NGINX Plus |
|---|---|---|
Core proxying, load balancing, TLS, caching | ✅ | ✅ |
Active health checks (probes backends in the background) | ❌ (passive only — Module 5) | ✅ |
Dynamic upstream API (add/remove backends without reload) | ❌ | ✅ |
Session persistence (sticky learn/cookie) | limited (
| ✅ advanced |
Built-in dashboard & extended status API | ❌ (Module 12 builds one) | ✅ |
JWT / SSO auth validation built in | ❌ (njs can do it — Module 15) | ✅ |
Commercial support & SLAs | ❌ | ✅ |
6. Verify your own setup
Everything from Lesson 1.2 onward was executed against a real NGINX before it reached this page. Where the stable and mainline branches behave differently, this series shows both — 1.30.5 and 1.31.6 at the time of writing. A plain
curl already reveals the server introducing itself:
$ nginx -v
nginx version: nginx/1.30.5
$ curl -si http://127.0.0.1/ | head -2
HTTP/1.1 200 OK
Server: nginx/1.30.5That
Server: header is NGINX doing job 2 (protocol) and job 1 (file) before you have written a single line of configuration. In Module 2 you’ll learn to hide or reshape that header — and when you shouldn’t.
Try it yourself
Check the version and build you run:
nginx -vfor the version,nginx -Vfor the compile-time story (Lesson 1.3 dissects it).Read your own server’s greeting:
curl -si http://127.0.0.1/ | head -2— and find out which branch your distro ships in Lesson 1.2.Reload and watch the
Server:header survive — the machinery behind that is Module 1’s running theme.
Q&A
Q1. A browser holds 4 keep-alive connections open but is idle for a minute. What is each NGINX worker doing for it during that minute?
Answer
Nothing — and that's the design. Idle sockets are not in the worker's
epoll_wait() ready list, so they cost no CPU. The connection occupies a
worker_connections slot and a little kernel memory, nothing more. An Apache prefork server, by contrast, would have 4 processes blocked in
read() for that minute.
Q2. Your product team wants to add image thumbnail generation (CPU-heavy librsvg work) to the edge. Good NGINX design or bad?
Answer
Bad
inside NGINX — the event loop must never host CPU-bound work (see §3's costs). Good NGINX design: NGINX routes
/thumb/* via
proxy_pass to a small thumbnail service (Module 4), optionally caches the results at the edge (Module 8), and rate-limits abuse (Module 7). Gateway + cache + app, not web-server-as-render-farm.
Q3. Name the three "jobs" from §1 for this request:
GET /api/users that arrives encrypted, is decrypted, matched to a route, and forwarded to a Node.js backend.
Answer
Job 2 (protocol termination: TLS + HTTP parsing), job 3 (routing/proxying: location match →
proxy_pass), and — strictly — job 1 barely appears because no disk file is served. This is the "API gateway" role from §4.
Q4. Your CTO asks why you listed NGINX Plus in a procurement discussion if "open source is the same speed." What's the honest one-liner?
Answer
"Plus buys operations — active health checks, dynamic upstream API, support — not speed; we can start open source and only pay if we hit those walls."
Takeaways
NGINX = a small fixed set of worker processes, each running an event loop (
epoll) over thousands of non-blocking connections.It was born from the C10K problem; its answer was architectural, not tuning: never do per-connection work for idle connections.
The blocking ban explains NGINX's design everywhere: state machines, shared-memory zones, and the "put heavy work behind the proxy" philosophy.
NGINX's modern center of gravity is the edge: proxy, load balancer, TLS terminator, gateway, cache, ingress.
Open source vs. Plus differ in operational features, not performance.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.