Nathan Grigg

Artisanal Web Server

I was playing around with IPv6 on the VPS that hosts this blog when I realized that, at least over IPv6, I no longer need virtual hosts.

In the old, old days, a “host” was a physical computer name, mapped to an IP address by DNS. As HTTP spread, it made sense to host multiple websites on the same machine, so browsers were required to send the “Host” header to tell the server what site they were looking for. Now a single server could do something different in response to www.example.com vs example.com.

Today, the vast majority of the web is served through a Content Delivery Network (CDN) such as Cloudflare, where millions of sites are assigned to the same IP address, and the Host header is used to route the request through the CDN to see if there is a cached version, falling back to the “origin” server if not. At that point, the origin server is likely serving a dozen or more sites, and it again looks at the Host header to actually figure out what content to return.

In an nginx config on the origin server, this might look like this:

server {
    listen 80;
    listen [::]:80;
    server_name example.com;
    ...
}

This says “use the following settings if you get any request on IPv4 port 80, or IPv6 port 80, as long as the host matches example.com.”

The server that hosts this blog hosts a dozen others, many of which are just redirects like www.nathangrigg.com -> nathangrigg.com. Until today, these all used the same IP address, with an nginx config to route the different virtual hosts to different sites on the same server.

While I wouldn’t want to pay for that many IPv4 addresses, which typically cost a couple dollars each per month, IPv6 addresses are plentiful. My host will give me, at no charge, a “/64 block”, meaning a 64-bit prefix for which I can use every address contaning that prefix. Since IPv6 address are 128 bits long, that is 2^64 possible addresses to use.

So I added a startup script on my server that runs

ip -6 addr add 2a01:7e03:e001:1ac::1/64 dev eth0
ip -6 addr add 2a01:7e03:e001:1ac::2/64 dev eth0
...

(That is my assigned 64-bit prefix in four groups of 16, and the double colon means “a bunch of zeroes”.)

Then I updated my DNS entries to map each domain to a different IP address, and updated my server block to say

server {
    listen [2a01:7e03:e001:1ac::1]:80
    ...
}

(I had to keep a separate server block with virtual hosts for the IPv4-based routing.)

Now, over IPv6, nginx uses the incoming address rather than the host name to decide what to serve.

Is it better? Probably not. But it lets me pretend like I have a different computer for each little website.

If you want to see it in action, try curl -vIL6 www.nathangrigg.com, and you can watch the procession from www-port-80 to www-port-443 to bare nathangrigg.com. (v for verbose, I for headers only, L to follow redirects, and 6 to force IPv6. If it fails for you, you may have to remove the 6 and watch the boring IPv4 version, where the IP address is the same for every request.)