Skip to content
Lesson 15 of 16

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:

  1. Port. Remove a trailing :port. For an IPv6 literal such as [::1]:8080 the colons are part of the address, so the address is the bracketed part and only what follows ] can be a port.
  2. Case. Host names are case-insensitive: lowercase the name.
  3. Trailing dot. example.com. is the fully qualified spelling of example.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 Host header. None is a 400. Two is a 400 too: 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 ignores Host and 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:

PatternMeaning
blog.example.comexact name
*.example.comany 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]:8080 into [.
  • Comparing the raw header text, so BLOG.example.com:80 misses blog.example.com.
  • Letting *.example.com match example.com.
  • Picking the first matching wildcard instead of the most specific one.
  • Trusting Host when 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.

Up nextWebSocket Upgrade HandshakeCaching, Compression & Beyond

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…