Multiple mDNS A Records with a Dynamic IP Address
I have very specific needs for my home server. Before I jump into my slightly unusual solution, let me briefly explain my requirements.
My home server is a tiny box that sits on the corner of my desk. I want to treat it like an appliance as much as I can. All configuration should live on the server itself. I don't want to do anything special in my router settings to make it work. My server gets a dynamic IP address just like everything else on my network. I want to be able to turn it on and it should Just Work™ assuming it can successfully connect to a network and get assigned an IP address.
I host multiple services on this one physical machine, such as a family wiki and a Plex server. My experience with setting up reverse proxies for situations like this has taught me that services I haven't developed to specifically support such a feature don't always play nice when I try to host them at different sub-paths on the same host. Path rewriting gets complicated quickly and can be fragile. Depending on the service I'm trying to run, sometimes it's just not feasible. Because of this, I need my home server to respond to multiple A records to make reverse proxying the various services as painless as possible. Using A records instead of special port numbers makes it easy to remember how to access any given service.
Here's how I met these requirements using Avahi and some user-level Systemd units.
The first step is to install Avahi and make sure its daemon is running. Next, add these unit files:
~/.config/systemd/user/mdns-publish-address@.service
[Unit]
Description=Advertise %i.local address via mDNS
After=network-online.target
Wants=network-online.target
PartOf=mdns.target
[Service]
Type=simple
ExecStart=/bin/sh -c '\
while [ -z "$(hostname -I)" ]; do sleep 1; done; \
IP=$(hostname -I | awk "{print \$1}"); \
exec avahi-publish-address "%i.local" "$IP" -R \
'
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=mdns.target
This is a template for advertising the current IP address as a particular A record in addition to the default one Avahi advertises.
Let's address the elephant in the room: that sleep loop. That is present because I don't want to attempt to advertise until the machine actual has an IP address. The reason I'm using a sleep loop instead of something more sophisticated like Systemd dependencies is because I'm running Debian 13, which still uses ifupdown to manage networking, not networkd or NetworkManager. It's grog-brained, but it's simple and it works.
It's worth noting that I don't enable this service template directly. Keep reading.
~/.config/systemd/user/mdns-publish-http@.service
[Unit]
Description=Advertise %i.local as HTTP via mDNS
Requires=mdns-publish-address@%i.service
After=network-online.target mdns-publish-address@%i.service
Wants=network-online.target
PartOf=mdns.target
[Service]
Type=simple
ExecStart=avahi-publish-service "%i.local" _http._tcp 80
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=mdns.target
In order for Avahi to work as expected, it needs to advertise both the A record and a service, like an HTTP server in my case. Instead of making a single shell script that runs both avahi-publish-address and avahi-publish-service, I decided to get a little fancy and make them two separate Systemd templates such that mdns-publish-http@.service depends upon an equivalent mdns-publish-address@.service. That means this is the only template that needs to be actualized. The other one comes along for free. Neat!
~/.config/systemd/user/mdns.target
[Unit]
Description=Target for mDNS service advertisement instances
After=network-online.target
Wants=network-online.target
[Install]
WantedBy=default.target
This is just a convenience target that makes it easy to bring all of the various Avahi services up and down together.
Enable the services
The last step is to enable the desired services.
systemctl --user enable mdns-publish-http@wiki.service
systemctl --user enable mdns-publish-http@plex.service
systemctl --user enable mdns.target
systemctl --user start mdns.target
Wait, why not...?
Yes, I'm fully aware that this is an unusual way to accomplish my goal. That's why I'm documenting it. Usually, you'd want to create XML files at /etc/avahi/services and add the necessary entries in /etc/avahi/hosts. But that doesn't work in my case for a few reasons.
For one, my home server does not have a static IP address, so I don't have a static value to insert into /etc/avahi/hosts. My solution works around that issue.
Also, when I was attempting the more typical solution, I ran into some issues with the Avahi daemon not liking a bunch of hosts pointing at the same IP address. I honestly might have been doing something else wrong when I tried that approach. But regardless, that approach doesn't meet my stringent requirements and the one I documented here does. 'Nuff said.