When you type an address and a page appears, something on the other end heard you and handed it over. Not a building, not a company – a program, running on some computer, whose entire job is to hear “please send me that page” and send it. On something like a third of the world’s websites, that program is nginx.
The interesting thing isn’t what it does; every web server does that. The interesting thing is why this one ended up everywhere – and the answer is a genuinely lovely idea about the difference between being busy and being useful.
The program, not the machine
The word “server” gets used for two different things, and untangling them clears half the fog.
One is the machine: an ordinary computer that stays switched on in a building somewhere, waiting. The other is the program running on that machine – the thing that actually reads your request and answers it. The machine is the shop; the program is the shopkeeper.
The conversation between you and the shopkeeper is simpler than you’d guess. Your browser opens a connection and sends a few lines of plain text, roughly: “send me the /about page – and by the way, I’m Firefox, and I can read compressed pages.” The program on the other end reads that, finds the page, and sends it back with a few lines of its own. One of those lines is the shopkeeper politely giving its name.
You can see that name yourself, right now. The command below asks a website for just its headers – the label on the parcel, not the parcel – and prints them. Nothing is downloaded, nothing is changed:
curl -sI https://wordpress.org
Among the lines that come back is one reading server: nginx. That’s the shopkeeper
introducing itself. Swap the address for https://www.gnu.org and it says Apache
instead. (curl comes built into Macs, Windows and Linux alike, so this almost certainly
just works. And some websites keep the name to themselves – that’s allowed, and a
reasonable precaution.)
Those two names, nginx and Apache, are most of the story of the last thirty years of the web.
The Apache era: one waiter per table
Apache arrived in 1995, and from April 1996 it was the most-used web server on the internet – a position it held for more than twenty years. It was sturdy, it was free, it ran on anything, and for a long stretch “putting a website online” simply meant “installing Apache.”
Its way of handling visitors was the obvious, honest one: give each connection a worker of its own. Someone connects, and Apache sets aside a whole separate worker – its own process, its own slice of memory – which belongs to that visitor until they go away. Simple to reason about, simple to repair. If one visitor’s request broke something, it broke only their worker.
It is how a restaurant would work if you hired one waiter per table. Perfectly sensible – until the restaurant gets busy.
Because here is the thing about a table: most of the time, nobody at it needs anything. They’re reading the menu. They’re chewing. They’re putting their coat on. Yet that dedicated waiter stands there the whole while, paid, taking up floor space, doing nothing.
Connections are the same, only more so. A reader on a slow phone connection takes seconds to receive a page the server prepared in a millisecond – and the worker waits out every one of those seconds. Worse, browsers deliberately hold the connection open after they’ve got the page, in case you click again in a moment; it saves building a fresh connection from scratch. That’s an empty table with the coats still on the chairs. Under Apache’s model it costs a full worker anyway.
So ten thousand people connected at once means ten thousand workers, each carrying its own megabytes of memory. That’s gigabytes of a machine spent almost entirely on holding people who aren’t asking for anything right now – plus all the effort the computer burns switching its attention between ten thousand of them.
This pain got a name in 1999: the C10k problem. The C is for concurrent, the 10k for ten thousand.
Why “concurrent” is the word doing the work
Ten thousand visitors over an hour is nothing at all – about three a second, and a laptop could manage it. Ten thousand connections open at the same instant is a completely different animal, and it is the one that breaks things.
The engineer who named it, Dan Kegel, wasn’t speculating. He pointed at a real machine doing it in 1999: a file-download host serving ten thousand clients at once. The hardware was plainly up to it. The way software had learned to handle connections was not.
That gap – hardware that could, software that couldn’t – is what the next decade of web servers was really about.
nginx’s answer: waiting is not work
In 2002 a systems administrator named Igor Sysoev was watching this happen for real. He worked at Rambler, one of the big Russian web portals, whose Apache servers simply could not hold the number of people who wanted to be connected at once. He tried patching Apache first, writing add-ons for it, and eventually concluded that the trouble wasn’t a missing feature. It was the shape of the thing.
So he wrote a new one. The idea underneath it is one sentence long: stop hiring a waiter per table.
Instead, keep a small, fixed number of workers – typically one per processor core, so four or eight on an ordinary machine. Give each of them a list of every connection it is minding, thousands of them at a time. And have it ask the operating system one question, over and over: “of all of these, which have something for me right now?” Whatever is ready, it handles, quickly, in turn. Then it asks again.
That’s it. That’s the whole trick: a handful of waiters who never stand still, circulating among thousands of tables, stopping only where a hand is actually raised.
The pay-off is not subtle. nginx’s own documentation gives the figure: ten thousand idle held-open connections cost it about 2.5 megabytes. Not gigabytes – megabytes. All those people waiting, chewing, reading the menu, became nearly free, because waiting is not work and nginx stopped paying for it as though it were.
Why 2002 was the moment this became possible
Everything rests on that one repeated question – “which of these are ready?” – and for most of the internet’s life, asking it was itself the bottleneck.
The old way (two instructions named select and poll) meant handing the operating
system your entire list of connections every single time, and getting the entire list back
to sift through yourself. With ten connections, trivial. With ten thousand, you are
re-reading a phone book from the start to find the one person who called.
Then the operating systems learned better. FreeBSD gained kqueue in 2000; Linux gained
epoll in 2002. With these, a program registers its connections once, and afterwards
simply asks “anything?” and gets back only the handful actually ready. The cost of asking
stops growing with the number of connections you hold.
nginx was built directly on top of those, right as they appeared. That is the real reason it could be written in 2002 and not in 1996 – the ground had only just been laid.
nginx 0.1.0 went public on 4 October 2004. It spread through the big Russian portals first, then outward, to everyone who had hit the same wall.
Apache, to its credit, learned the lesson too: version 2.4, in 2012, brought a mode that works much the same way. But by then the habit had set. In April 2019, counting every site on the internet, nginx passed Apache to become the most-used web server in the world.
Its second life: the doorman
Here is the turn in the story, and it is the reason nginx is still everywhere. Most of what nginx does today is not serving its own pages at all.
Think about what a real website usually is. Not a folder of finished pages – a program. A shop that checks stock and prices. A forum that looks up who you are. These are written in languages like PHP, Python, Ruby or JavaScript, and they share an honest weakness: they are good at thinking and bad at crowds. They are built to handle a few things at a time, carefully.
So you stand nginx in front of them.
nginx takes the thousands of connections from the outside world – the thing it is extraordinary at. It serves the plain files itself: the images, the stylesheets, the fonts, which need no thinking. And when a request genuinely needs the program – what’s in my basket? – it passes that one question inward over a short, instant, local connection, and carries the answer back out. The application never meets a slow phone. It never waits. It thinks, answers, and moves on.
A middleman that stands in front of the thing doing the real work, facing the world on its behalf, is called a reverse proxy. A proxy is anything that stands in for someone else; the ordinary kind stands in front of you, hiding you from the sites you visit. This one is turned around – it stands in front of the server, and the world talks to it instead.
And once something reliable is standing at the door, you keep handing it jobs, because it is already there:
- Sorting by name. One machine, many websites. nginx reads which name the visitor asked for and sends them to the right one.
- The padlock. It handles the encryption, so nothing behind it has to.
- Squeezing. It compresses pages on the way out, so they travel faster.
- Remembering. It keeps copies of recent answers, so the application is asked the same question far less often.
- Sharing out. If several copies of your application are running, nginx spreads visitors evenly between them. That is load balancing.
- Turning people away. Limits and blocks, so a flood never reaches anything fragile.
This is what people actually mean when they say they “spent the weekend in nginx.” Almost never serving files. The door.
What that famous config file actually looks like
It has a reputation for being arcane. Mostly it isn’t. It’s nested braces, and each block says “in this situation, do these things”:
server {
server_name example.com;
listen 443 ssl;
location /images/ {
root /var/www/static;
}
location / {
proxy_pass http://127.0.0.1:3000;
}
}Read it aloud and it is nearly English. For the site called example.com, listen on the
secure port. Anything under /images/, hand back a file from this folder on disk.
Everything else, pass through to the program running here on this same machine, at door
number 3000.
That last line is the entire doorman idea, in one instruction. The site’s actual brain sits at
127.0.0.1:3000 – which just means “this very machine, door 3000” – and is never exposed
to the internet at all. It only ever talks to nginx.
Today: doors that let themselves in, and doors that moved out
Two things have changed since, and between them they explain where the web now is.
The padlock stopped being a chore. For most of the web’s life, HTTPS – the little padlock, the promise that nobody in between can read or alter what you’re sent – meant buying a certificate, installing it by hand, and remembering to renew it in a year’s time. Forget, and your site breaks publicly and instantly. Then Let’s Encrypt arrived, free and automated and generally available from April 2016, and made certificates cost nothing and need no human.
A newer web server called Caddy – written by Matt Holt, first released in 2015 – took that to its conclusion: you tell it your domain name, and it fetches and renews the certificate itself, with HTTPS simply on by default. nginx can do all of this too; the difference is that with nginx you assemble it, and with Caddy it is the assumption. That is the modern flavour of a great many tools – the same capability, minus the assembly.
And the door moved out of the building. This is the larger shift. For a growing share of websites there is no nginx of your own anywhere, because the entire doorman job – the padlock, the caching, the compression, the turning away of bots, the routing – now happens out on a network of machines spread across hundreds of cities, long before a request gets near your site. That is what Cloudflare and services like it are. Look closely and a content delivery network is a reverse proxy; it simply lives in several hundred places instead of one.
You can watch the handover in the numbers. In 2019 nginx passed Apache. By 2023, among the busiest million sites, Cloudflare had passed them both. By January 2026 one widely used survey put nginx at 33.3% of sites, Cloudflare at 25.8% and Apache at 24.4% – with Cloudflare the one still climbing. (Surveys count differently and disagree on the details; on the direction they agree.)
The most telling detail of all: Cloudflare itself ran on nginx for years. In 2022 it replaced nginx with something of its own, because at more than a trillion requests a day the way nginx divides work between its handful of workers had stopped fitting the problem. Which is precisely the same story again, one turn further along – the shape of the load changed, so the shape of the answer changed with it.
Try that first command again, this time on the site you’re reading. It is the end of the story printed in a single line:
curl -sI https://miron.blog
server: cloudflare. No machine of ours is answering. There is no nginx here at all.
A year inside that config file
That line has a story behind it, and it is the one this blog opens with.
The author spent about a year hand-tuning nginx – configuring HTTP/2, then HTTP/3, building bot filtering, obsessing over caching – and brought a page-load time down from four seconds to two through a great many small adjustments. Then he decided to hand the whole job to Cloudflare, and got most of it back for free on the first day.
It would be easy to read that as a wasted year. It wasn’t, and the reason matters more than the tuning did. Knowing what that door does – why it’s there, what it takes off your hands, what it costs to keep – is exactly what lets you judge whether handing it to someone else is a clever move or a mistake you won’t notice for six months. You can only safely outsource a job you understand.
Sometimes the best optimisation is choosing a better tool. You just have to know the tool you are leaving.
So, in one breath
nginx is the program that hears “send me that page” and sends it – and it took over the web not by being faster at any single thing, but by refusing to spend a whole worker on a visitor who is merely waiting. That one change let an ordinary machine hold tens of thousands of people at once, and it turned nginx into the web’s universal doorman: the thing standing in front of your application, absorbing the crowd, the padlock and the traffic so the application only ever has to think. These days that doorman increasingly stands somewhere else entirely – out in a network of cities, or in a server that arranges its own padlock – but the job it does is still the one nginx defined.