How HTTP works: the message, the layers and the four versions
What's actually inside an HTTP request, why HTTP sends messages and not packets, and how each version —from 0.9 to HTTP/3 over QUIC— fixes the jam the previous one left behind.

CONTENTS
This video is served by YouTube. Loading it sends your IP address to Google, so I don't do it without permission.
The letter your browser writes
You type an address and hit Enter. The first thing that happens is that DNS translates that name into an IP address: I covered that end to end in how DNS works. So we already know where the server is.
Now somebody has to ask it for the page. That's HTTP's job.
What your browser does at that moment is write a letter. Literally: in HTTP/1.1 it's plain text, line by line, and you could read it with your own eyes if you leaned in to look. In this article we open that letter, follow it all the way to the wire, and see why the protocol has been rewritten four times in thirty years.
What HTTP is: an agreement, not a program
HTTP stands for HyperText Transfer Protocol. Of the three words, the one that matters is the last.
A protocol is not a program. It isn't a cable. It isn't something you install. A protocol is an agreement about two things: what format the things we send each other have, and in what order we send them. Nothing more. Like the script of a very strict phone conversation: you ask, I answer, and neither of us speaks out of turn.
In HTTP, the one who asks is called the client —usually your browser, but also curl, a mobile app or one service calling another— and the one who answers is called the server. The client sends a request, the server returns a response.
One request, one response. That's it.
That exchange travels over port 80 by default when it's plain HTTP and over 443 when it's HTTPS. They're only conventions, but conventions baked into every firewall in the world.
No memory: why the server doesn't remember you
Here's the first trait that surprises almost everyone: HTTP has no memory. It's described as a stateless protocol, and that means every request is born knowing nothing about the one before it.
You've just typed your password, you ask for the next page, and to the server you're a stranger again.
That looks like a flaw, and it's exactly what let the web grow. If the server doesn't have to remember anyone, any server can handle any request: you can put a thousand machines behind a load balancer and it doesn't matter which one gets yours. That's the underlying reason a site scales by adding machines horizontally.
But something had to remember you. And that's where cookies and tokens come from: they're the note you show the server again on every single request. The server doesn't remember you; you refresh its memory each time.
Anatomy of a request: the four parts
Let's open the letter. An HTTP request has four parts, always in this order:
GET /articles/como-funciona-http HTTP/1.1
Host: antoniocintora.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: text/html,application/xhtml+xml,*/*;q=0.8
Accept-Language: en-GB,en;q=0.9
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
- The request line, which is the first one.
- The headers, one per line.
- A blank line.
- The body, which in a request like this one is empty.
That first line carries three things, separated by spaces:
GET /index.html HTTP/1.1
The blank line in the third part isn't decoration: it's the marker that says "the headers end here". It is exactly how the server knows where the envelope ends and the contents begin. That's why the lines of an HTTP/1.1 request end in CRLF —carriage return and line feed— and what separates them from the body is an empty CRLF line.
The body only shows up when you're sending something. A form, some JSON, a file:
POST /api/subscribe HTTP/1.1
Host: antoniocintora.com
Content-Type: application/json
Content-Length: 40
{"email":"hello@example.com","lang":"en"}
Notice Content-Length: without that header —or without Transfer-Encoding: chunked, which streams the body when you don't yet know how big it will be— the server would have no way of knowing where the body ends. Plain text doesn't end by itself.
Methods: the verb of the request
The method is what you want to do. Four of them get you most of the way, but they're worth knowing with their two formal properties: whether they're safe (they change nothing on the server) and whether they're idempotent (doing it ten times leaves the same result as doing it once).
| Method | What it means | Safe | Idempotent |
|---|---|---|---|
GET | Give me this | Yes | Yes |
HEAD | Give me just the headers | Yes | Yes |
OPTIONS | Tell me what you support | Yes | Yes |
POST | Here, store this | No | No |
PUT | Replace this with what I send | No | Yes |
PATCH | Change only this part | No | No |
DELETE | Delete this | No | Yes |
That idempotency column isn't theory: it's why the browser warns you before reloading a page you reached via POST, and why a client can safely retry a PUT that never got an answer but shouldn't blindly retry a POST.
Headers: the metadata of the letter
A header is a name and value pair, one per line, separated by a colon. They're the metadata of the letter: who you are, what language you prefer, what formats you accept, what cookies you carry. Header names are case-insensitive —Host and host are the same header— although HTTP/2 and HTTP/3 always transmit them in lowercase.
These are the ones you'll see in almost any exchange:
| Header | Which side | What for |
|---|---|---|
Host | Request | Which domain the request is for. Mandatory since HTTP/1.1 |
User-Agent | Request | Which client is sending it |
Accept | Request | What formats the client understands |
Accept-Encoding | Request | What compressions it accepts (gzip, br) |
Cookie | Request | The note that refreshes the server's memory |
Content-Type | Both | What's in the body |
Content-Length | Both | How many bytes the body takes |
Cache-Control | Response | How long this response may be stored, and by whom |
Set-Cookie | Response | The server hands you the note you'll have to show again |
Location | Response | Where to go when the response is a redirect |
And here's a number that surprises people: in a normal request, most of the weight is headers. The useful content is the smallest part. A request for an image can carry half a kilobyte of headers —cookies included— to ask for a file that didn't even need downloading. Hold on to that: it's one of the two things HTTP/2 came to fix.
The response: what the first digit of the code tells you
The response has the exact same shape as the request —first line, headers, blank line, body— but that first line changes. Instead of a method it carries a status code: three digits that sum up what happened.
HTTP/1.1 404 Not Found
A full response reads just as easily:
HTTP/1.1 200 OK
Date: Sun, 20 Sep 2026 09:14:22 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 18422
Content-Encoding: gzip
Cache-Control: public, max-age=3600
<!doctype html><html lang="en">…
And the first digit already tells you almost everything:
| Family | What it means | The ones you'll actually see |
|---|---|---|
1xx | Hold on, I'm still going | 101 Switching Protocols, when opening a WebSocket |
2xx | It went fine | 200 OK · 201 Created · 204 No Content |
3xx | That lives elsewhere, go there | 301 permanent · 302 temporary · 304 Not Modified |
4xx | You got it wrong | 400 · 401 · 403 · 404 · 405 · 429 |
5xx | I got it wrong, the server | 500 · 502 · 503 · 504 |
A redirect is the shortest response you'll ever see, and it reads at a glance:
HTTP/1.1 301 Moved Permanently
Location: https://antoniocintora.com/en/articles/como-funciona-http
Content-Length: 0
Two distinctions that save arguments:
401is not403.401 Unauthorizedmeans "I don't know who you are, identify yourself";403 Forbiddenmeans "I know who you are and you still can't". If your API returns401to an already authenticated user, what it's telling you is that the token is no good.304 Not Modifiedis a response with no body. The server is telling you that what you have cached is still valid. It's in the3xxfamily but it doesn't send you anywhere: it saves you a download.
And hold on to one thing, because it's going to matter shortly: everything we've just read is text. Readable. In HTTP/1.1 you could print a request out and read it aloud.
The misunderstanding: HTTP sends messages, not packets
People say a lot that HTTP "sends packets". It doesn't. HTTP doesn't even know what a packet is.
HTTP sends messages. A message is the complete letter we just wrote: request line, headers, blank line and body. That is HTTP's unit, and there is no other. Packets belong to the layer below.
And "layer" isn't a soft metaphor: the network is literally built in layers, and each one wraps the previous without reading it.
Every layer adds its own header. The packet doesn't exist until IP adds its own.
This is what really happens to your letter when you hit Enter:
- HTTP hands the message to TCP —Transmission Control Protocol—, whose job is exactly one thing: getting that there whole and in order. TCP chops it up, because it doesn't fit in one piece, and each chunk is called a segment and carries its own header with a sequence number.
- Each segment is handed to IP, the Internet Protocol, which is the one that knows how to route. IP adds another header with the source and destination addresses. And there, only there, is when it becomes a packet.
- And each packet is wrapped in a frame to cross the cable or the wifi. One more header.
Envelopes inside envelopes: where a packet is born
They're envelopes inside envelopes. And each layer only reads its own:
[ FRAME [ IP PACKET [ TCP SEGMENT [ HTTP MESSAGE ] ] ] ]
The router pushing your packet halfway around the world doesn't care whether there's an HTTP request, an email or a game session inside. It reads the address on its own envelope and pushes it to the next hop. That's why one layer can be swapped without touching the others: which is exactly what HTTP/3 is about to do further down.
And why does it have to be chopped up at all? Because of the MTU, the largest chunk a network segment will accept. On Ethernet that's about 1,500 bytes. So a normal page, with its HTML, its images, its fonts and its JavaScript, isn't a handful of packets: it's thousands.
The full journey, from the moment you let go of the key:
- 01
DNS translates the name into an IP address. Until that answer comes back there's nobody to connect to.
- 02
TCP opens the connection to the server. That's already a full round trip before anything has been asked for.
- 03
If it's HTTPS, TLS negotiates encryption on top of that connection. Another round trip, or two.
- 04The browser writes the HTTP request and hands it to TCP.
- 05
TCP chops it into segments, IP turns them into packets and the link layer wraps them in frames.
- 06
The server reassembles the message in order, processes it and returns a response that makes the same journey in reverse.
- 07
The browser reads the HTML, discovers the images, the styles and the scripts, and starts over for each one.
That last step is the key to everything that follows: a page isn't one request, it's dozens or hundreds. And on top of that there's TCP's promise —that everything arrives, and that it arrives in order— which sounds like it could only ever be good news. By the time we get to HTTP/2 you'll see that this promise is exactly what broke it.
Why HTTP has been rewritten four times
HTTP hasn't been rewritten on a whim. Each version fixes the problem the previous one left, and in fixing it creates a new one. It's a chain, and it makes far more sense followed end to end than memorised as a list of dates.
HTTP/0.9 (1991). One single line: GET and the path. No headers, no status codes, no version number. And it could only return HTML: there was no way to say "this is an image".
GET /index.html
Problem: it can't be extended.
HTTP/1.0 (1996). Headers arrive, status codes arrive, and one header changes everything: Content-Type. Now the server can say what it's sending you, and the web stops being only documents.
New problem: one TCP connection per file. And opening a connection costs a full round trip. A page with thirty images meant thirty connections set up and torn down.
HTTP/1.1: keep-alive, Host and head-of-line blocking
HTTP/1.1 (1997) is the version that has lasted nearly thirty years, and the one still serving an enormous share of internet traffic. It brought three things.
First, keep-alive: keeping the connection open to ask for several things over the same one. The toll of setting up a connection per file stops being paid.
Second, the Host header. It looks minor and it isn't: it allows many domains to be served from a single IP address, because the request says which domain it's for. Without Host there would be no shared hosting, and not much of hosting as we know it. It's the only mandatory header in HTTP/1.1.
And third came an idea that on paper solved everything: pipelining. Sending several requests back to back without waiting for each response.
The problem is that the responses had to come back in the same order you asked for them. If the first was slow, the rest waited behind it even when they were already done. That has a name:
Head-of-line blocking: a stalled car in a single lane holds up everybody behind it, however clear the road ahead may be.
It went so badly that browsers ended up disabling it. And the workaround was brute force: opening six connections in parallel to each server. Six queues instead of one. That patch lasted eighteen years, and it's the origin of half a dozen optimisation tricks that are still taught —bundling all your CSS into one file, packing small images into a sprite, spreading assets across several subdomains— and that stopped making sense with HTTP/2.
HTTP/2: binary, multiplexing, and a block that moves down a layer
HTTP/2 (2015) changes two things at the root.
The first: it stops being text and becomes binary. Remember how a few sections ago it was readable? That's over. You can no longer read an HTTP/2 request by eye: you need a tool. You lose convenience and you gain a message the machine parses without ambiguity or whitespace tricks.
The second, the important one: multiplexing. To multiplex is to fit many independent conversations through one channel. HTTP/2 splits the connection into streams, and each request travels on its own, tagged. They go and come back interleaved over a single connection.
There's no need to open six any more. And there's no head-of-line blocking inside HTTP.
It also compresses headers with an algorithm of its own, HPACK, which is exactly what was needed: remember that headers were most of the weight, and that across a page they repeat almost identically on every one of its hundreds of requests.
It looks solved. And here TCP's promise comes back.
Because underneath those streams there is still one single TCP connection. And TCP promised to deliver everything, in order. So when a packet is lost —and they are— TCP digs in: it waits for that piece to be retransmitted before delivering anything that came behind it. Even if what came behind has already arrived. Even if it belongs to a completely unrelated stream.
Head-of-line blocking didn't go away: it moved down a layer. It used to block HTTP; now it blocks TCP. And it can't be fixed from HTTP, because the problem sits below it.
That's why HTTP/2 performs spectacularly well on a good network and can perform worse than HTTP/1.1 with six connections on a lossy one: six connections are six independent queues, and one multiplexed connection is a single queue that freezes whole.
HTTP/3 and QUIC: swapping HTTP's transport
HTTP/3 (2022). Since the problem was no longer HTTP's, the fix wasn't to touch HTTP. It was to swap its transport.
HTTP/3 drops TCP and runs on QUIC, which runs on UDP. UDP promises neither order nor delivery: it just sends. And on that very simple base, QUIC rebuilds what was needed —reliability, ordering and congestion control— but per stream. Each stream keeps its own account of what has arrived and what is missing.
If a packet belonging to an image is lost, that image stalls. Everything else keeps arriving.
As a bonus, QUIC carries encryption inside, integrated with TLS 1.3, rather than as a separate layer on top. That lets it set up the connection and encrypt it in fewer round trips: where TCP plus TLS needed two or three trips before the first request, QUIC does it in one.
And hold on to this, because it's the part that almost never gets said: HTTP/3 is not "faster HTTP". The messages are the same. The methods are the same. The status codes are the same. HTTP/3 is HTTP with a different transport underneath.
| Version | Year | Format | Transport | What it fixes |
|---|---|---|---|---|
| HTTP/0.9 | 1991 | Text | TCP | — |
| HTTP/1.0 | 1996 | Text | TCP | Headers, status codes, Content-Type |
| HTTP/1.1 | 1997 | Text | TCP | keep-alive and the Host header |
| HTTP/2 | 2015 | Binary | TCP | Multiplexing and header compression |
| HTTP/3 | 2022 | Binary | QUIC (UDP) | The blocking that had moved to TCP |
One practical detail: the browser doesn't guess that a site speaks HTTP/3. It finds out because the response carries an Alt-Svc header advertising it —or because DNS publishes an HTTPS record— and from then on it tries QUIC. The first visit almost always comes in over HTTP/2.
HTTPS: where TLS fits and what doesn't change
Everything we've seen travels in the clear. That readable letter, with your path, your headers and your cookies, can be read by anyone along the way: your ISP, the café wifi, every box it passes through.
HTTPS is this exact same thing plus a layer of encryption. The S is for secure. And the key is where that layer goes: not above HTTP, not below IP, but right between HTTP and TCP.
HTTP ← the message, untouched
TLS ← encryption goes in here
TCP
IP
LINK
That layer is called TLS, and it doesn't change the message: it takes the letter as it is and puts it in a sealed envelope. That's why everything in this article holds just the same over HTTPS: the methods, the codes, the headers and the body are exactly the same.
What does change is what somebody listening along the way can see. With HTTPS they no longer see the path, the headers, the cookies or the content. They do still see which IP address you connect to and, in most cases, which domain, because the name travels in the clear during the initial negotiation so the server knows which certificate to present.
And a naming clarification, because this one always gets muddled: SSL is the old name. It stopped at version 3.0 and was replaced by TLS —Transport Layer Security—, which is now on 1.3. We've been using TLS and calling it SSL for more than twenty years; certificates are still sold as "SSL certificates". It isn't your mistake: it's industry inertia.
See it for yourself
None of this is abstract theory. The letter can be read.
The most direct way is to ask for it by hand over port 80, writing the protocol yourself. This sends exactly the bytes of an HTTP/1.1 request and gives you the raw response back:
printf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' | nc example.com 80
It's all there: the request line, the mandatory Host header and the final blank line. Skip that blank line and the server sits there waiting for more headers and never answers.
With curl you see the same thing without writing the protocol by hand. The -v flag prints the request with > and the response with <:
curl -v https://antoniocintora.com/
If you only care about the response headers, -I sends a HEAD:
curl -I https://antoniocintora.com/
And this is the one that answers the question from the video: it prints the version actually negotiated and the status code, without downloading the body.
curl -s -o /dev/null -w '%{http_version} %{http_code}\n' https://antoniocintora.com/You can force a specific version to compare: --http1.1, --http2 or --http3. Bear in mind that --http3 only works if your curl was built with QUIC support; check with curl --version and look for HTTP3 in the feature list.
On Windows, without leaving PowerShell:
(Invoke-WebRequest -Uri https://antoniocintora.com/ -Method Head).Headers
And without touching a terminal at all: open your browser's developer tools with F12, go to the Network tab, reload, and add the Protocol column —right-click on the table's header row—. You'll see h2, h3 or http/1.1 on every request. It's the fastest place to check everything in this article: the method, the status code, the headers and the version, row by row.
Errors you'll see in production
502 BAD GATEWAY
It isn't your site failing: it's a proxy —nginx, Traefik, a load balancer— that went to
ask the application behind it and didn't get a valid answer. Look at the backend logs,
not the proxy's: it's almost always the process down, listening on a different port, or
taking longer than the proxy is willing to wait, which is when you get 504 instead of
502.
ERR_TOO_MANY_REDIRECTS
A redirect loop. The classic case: a proxy terminates TLS and talks to the application
over plain HTTP; the application sees "this came in over HTTP" and redirects to HTTPS;
the proxy comes back in over HTTP. The fix is making the proxy send
X-Forwarded-Proto and the application trust it.
STILL SERVING THE OLD VERSION
You deployed and the change isn't there. Before blaming the deployment, look at
Cache-Control and ETag in the response: if you sent a long max-age without
versioning the filename, browsers and any CDN along the way will do exactly what you
asked them to. Check it with curl -I, which skips the browser cache but not the CDN's.
MIXED CONTENT
The page is served over HTTPS and asks for an image or a script over HTTP. The browser blocks that resource and the page looks broken without any status code warning you: it shows up in the browser console, not in the server logs.
Glossary
- Protocol
An agreement about what format two parties' messages take and in what order. Not a program, not something you install.
- Stateless
Every request is born knowing nothing about the previous one. Cookies and tokens exist precisely because the protocol doesn't remember.
- Message
HTTP's unit: the whole letter, with first line, headers, blank line and body. HTTP sends nothing else.
- Segment
The chunk TCP splits a message into, with its own header and sequence number.
- Packet
A segment with IP's header on it: source and destination. Before IP there is no packet.
- Frame
The packet wrapped up to cross one specific physical hop, cable or wifi.
- MTU
The largest chunk a network segment accepts. On Ethernet, around 1,500 bytes: it's the reason things have to be chopped up.
- Multiplexing
Fitting many independent conversations through a single channel. It's the central idea of HTTP/2.
- Stream
Each of those conversations inside the connection, with its own tag.
- Head-of-line blocking
What's behind waits even when it's already done. In HTTP/1.1 it happened in HTTP; in HTTP/2, in TCP.
- QUIC
HTTP/3's transport. It runs on UDP, carries encryption inside, and tracks delivery stream by stream instead of for the whole connection.
- TLS
The encryption layer of HTTPS. It sits between HTTP and the transport and doesn't modify the message. SSL is its old name.
Frequently asked questions
What is the difference between HTTP and HTTPS?
HTTPS is the same HTTP with an encryption layer —TLS— inserted between HTTP and the transport. The methods, headers, body and status codes are identical: the only thing that changes is that whoever sits along the way can no longer read them. HTTP uses port 80 by convention and HTTPS uses 443.
What is the real difference between GET and POST?
GET asks for something and shouldn't change anything on the server; its parameters ride in the URL, so they end up in history, in logs and in bookmarks. POST sends data in the request body and does change state. The difference that hurts most in practice is idempotency: repeating a GET is harmless, repeating a POST can create two orders.
Does HTTP run over TCP or UDP?
Up to and including HTTP/2, over TCP. HTTP/3 runs over QUIC, which is built on UDP. It isn't that HTTP/3 gives up reliability: QUIC reimplements it per stream, which is exactly what TCP couldn't do.
What does it mean that HTTP is stateless?
That the server remembers nothing between one request and the next. Every request arrives as if it were the first. Sessions don't exist in the protocol: they're simulated with cookies or tokens the client re-sends every time.
Is HTTP/3 faster than HTTP/2?
It depends on the network. On a clean connection the difference is small. On a lossy one —mobile, congested wifi— HTTP/3 wins clearly, because losing a packet only stalls its own stream instead of freezing the whole connection. And it starts sooner, because QUIC sets up connection and encryption in fewer round trips.
Do I have to change my application to use HTTP/2 or HTTP/3?
No. The version is negotiated between the browser and whatever first accepts the connection, which is normally the proxy, the web server or the CDN; your application still sees the same methods and the same headers. Enabling HTTP/2 or HTTP/3 is configuration at that entry layer, not a code change.
Why do people still say SSL when the protocol is TLS?
Inertia. SSL stopped at version 3.0 and was replaced by TLS in 1999; today the good version is TLS 1.3. The commercial name "SSL certificate" stuck to the product and nobody ever changed it. If someone says SSL, they almost always mean TLS.
Can I read an HTTP/2 request the way I read an HTTP/1.1 one?
Not by eye: HTTP/2 and HTTP/3 are binary. You need a tool that decodes them —the
browser's Network tab, curl -v or Wireshark— and over HTTPS you also need the session
key to decrypt them. The semantics you see there are exactly the same.
Let's recap
HTTP is an agreement about format and order, with no memory, in which the client sends a message and the server returns another. That message has four parts and, in HTTP/1.1, can be read with your own eyes.
It doesn't travel as a packet: it's handed to TCP, which chops it into segments; IP turns those into packets by giving them a source and a destination, and the link layer wraps them in frames. Envelopes inside envelopes, where each layer reads its own and never looks at the ones inside.
And the versions are a chain: each one fixes the jam the previous left behind, until HTTP/3 had to change transport to fix one that was no longer its own.
What's still open is the thing holding all of this up: how two machines that have never met agree on a secret key in front of everybody, and who decides whom your browser trusts. That's TLS, and it's next.