This is a follow-up to the note DNS in General and at Home.

DNS

In the router, I assign a stable IP address to every server — just so I don’t have to look them up when something goes wrong.

localhost

Everyone knows that localhost refers to the current machine.

In modern operating systems (at least macOS), *.localhost also automatically resolves to the current machine.

This way, you can serve several sites on standard ports through caddy: myproject.localhost and api.myproject.localhost.

There are tools (for example, https://portless.sh/) that automatically let you self-sign such domains (and with them you don’t even need caddy locally). Obviously, these are not reachable from the outside, but locally they work perfectly fine.

local

*.local is a special domain from mDNS (aka Zeroconf / Rendezvous). The machine announces its own name.

The main scenario: you bring a new appliance home, plug it into an Ethernet outlet, power it on, it gets an IP address via DHCP, and on its own it announces that it’s ready to answer at nas.local. You glance at the sticker and — without logging into the router — simply type nas.local into the browser.

Same with WiFi, except the device creates its own WiFi network with its own DHCP server, and you connect to it to give it the password for your home WiFi network.

It looks neat, but it doesn’t always work because of VPNs and similar things (a VPN usually allows direct access to internal resources, but it can block internal broadcast messages — in that case local domains don’t work).

One of the problems: only self-signed certificates can be used for SSL (or no SSL at all). That already limits both convenience and security.

Overall, I keep this in mind, but in practice I mostly use it only for the initial setup of devices.

Split DNS

This is when a name resolves to different values inside the network and outside of it. For example, nas.myhome123.ru resolves to 192.168.1.10 from inside, and to 1.2.3.4 from outside (the public IP, usually the router’s).

This approach is essential for internal services exposed to the outside to work properly from the inside. It’s complicated, but that’s just how it is. There is a way to set all this up correctly without DNS, but it’s hard and few people do it (via separate routing tables).

The approach can also be used to obtain an SSL certificate via the public IP and then use that same certificate inside (the public side may even serve an empty page — that’s enough for the certificate).

It works poorly when internal clients have different DNS settings (when they’re configured to use something other than the router). For example, Yandex DNS that only shows child-safe resources. Such clients won’t see the internal resources, which is undesirable.

So I thought about it for a long time, even tried it, but in the end I abandoned this approach.

Public or Internal DNS

A public DNS record is available to everyone on the internet. An internal one is available only through the router’s DNS.

For the same reason as with split DNS, I use public: it works with all clients.

It’s slightly unfortunate that a potential attacker could figure out the internal IPs, but overall it’s no big deal.

This also leads me to use my own publicly registered domains rather than nas.home, nas.loc (internal unregistered domains).

SSL

SSL for Internal IPs

Caddy registers domains for external IP addresses just fine. If you need to register a certificate for internal IP addresses (given a public record exists for it), you need to use domain ownership verification via DNS (DNS Challenge).

After looking into it a bit, I liked the approach via https://github.com/acme-dns/acme-dns — a small service on your own server, and you don’t need to rebuild Caddy for different DNS providers.

The second option is to find a Caddy plugin for your DNS provider and build Caddy with it.

Overall it’s a workable option, but I decided to go with something simpler.

tls internal

Caddy can create self-signed certificates. But that’s not all. It also lets other Caddy instances use the same certificate.

So, we install a central Caddy on the Keenetic (acme_server and tls internal), and configure the other Caddy instances to use it (tls ca https://router/acme/local/directory and tls ca_root /etc/ssl/certs/root.crt).

That way, you’ll only need to add one certificate. On desktops I add it to Firefox, since that affects only Firefox rather than the whole system.

Summary

Given:

  • different clients use different (including public) DNS servers
  • there are no public services on the home network (when external access is needed, VPN/Tailscale is used)

Decisions:

  • internal IPs: fixed on the router
  • domains: publicly registered ones pointing to internal IPs, and *.local
  • SSL: self-signed for internal services (including *.local)
  • a central Caddy for a single internal self-signed certificate, but individual Caddy instances on each machine so there’s no central point of dependency