Step 1 of 5 · Reading · ~3 min
Read
Caching, Compression & Beyond
Virtual Hosting & the Host Header
One IP address routinely serves dozens of different sites: shared hosting, local development, CDN edges. The mechanism is the Host header, mandatory since HTTP/1.1. Same TCP socket, same IP, same process, and yet blog.example.com and shop.example.com get different document roots, different configuration and different logs. nginx calls them server blocks, Apache VirtualHost, Caddy site blocks. Your program will decide, for each request on a connection, which site it belongs to.
Which name did the client mean?
GET /index.html HTTP/1.1\r\n
Host: Blog.Example.com:8080\r\n
The site name is not the header text. It has to be normalised first, in this order:
- Port. Remove a trailing
:port. For an IPv6 literal such as[::1]:8080the colons are part of the address, so the address is the bracketed part and only what follows]can be a port. - Case. Host names are case-insensitive: lowercase the name.
- Trailing dot.
example.com.is the fully qualified spelling ofexample.com. Remove one trailing dot.
Then validate. The value must be non-empty and made only of letters, digits and . - _ [ ] :. A space, a @ (userinfo has no place in a Host header), more than one unbracketed colon, or a port that is not digits is a 400 Bad Request.
Where the name comes from
- HTTP/1.1 requires exactly one
Hostheader. None is a400. Two is a400too: if a front proxy takes the first and the back end takes the last, the attacker chooses which tenant answers. That is how cross-tenant data leaks happen. - If the request target is in absolute form (
GET http://shop.example.com/ HTTP/1.1, what proxies receive), RFC 9112 section 3.2.2 says the server ignoresHostand uses the authority in the target. - HTTP/1.0 predates
Host. Without one the request carries no site name at all and gets the default site.
Selecting the site
The table has three kinds of line:
| Pattern | Meaning |
|---|---|
blog.example.com | exact name |
*.example.com | any name with one or more labels in front of .example.com. It does not match example.com itself |
_ | the default site, used when nothing else matches |
Algorithm: an exact match wins; otherwise the wildcard with the longest literal suffix wins (*.a.example.com beats *.example.com for x.a.example.com); otherwise the default if there is one; otherwise 404 Not Found.
This is also why HTTPS needed SNI. TLS must choose a certificate before any HTTP bytes can be decrypted, so the client sends the server name in the clear inside the handshake.
The contract
Input: the vhost table (<pattern> <site>, LF terminated), an empty line, then the raw pipelined requests (CRLF, no bodies). Print one line per request: the site name, 404, or 400. After a 400 the connection is closed, so print nothing for the requests after it. A request line that is not exactly three space-separated fields, or a header line with no colon, is also a 400.
Common mistakes
- Cutting at the first
:, which turns[::1]:8080into[. - Comparing the raw header text, so
BLOG.example.com:80missesblog.example.com. - Letting
*.example.commatchexample.com. - Picking the first matching wildcard instead of the most specific one.
- Trusting
Hostwhen the target already names an authority.
Your exercise
The starter reads the table and walks the requests. You extract and normalise the name, then select the site.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…