Wade's Site

Setting up mDNS and custom .local domains

Published on

Ever wanted domains like jellyfin.local or vaultwarden.local for your self-hosted services? With Multicast DNS (mDNS), you can do so without exposing your services to the internet, or using custom DNS servers. No need to edit hosts file, either!

This guide assumes you're using NetworkManager and systemd-resolved.

If you've not yet set-up systemd-resolved: add your preferred DNS servers to /etc/systemd/resolved.conf, and run

sudo rm /etc/resolv.conf
sudo ln -sf ../run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
sudo systemctl enable --now systemd-resolved.service

systemd-resolved has with its own mDNS service, so we need to disable it before we proceed. In /etc/systemd/resolved.conf, add:

Domains=~local
MulticastDNS=no
LLMNR=no

Run sudo systemctl restart systemd-resolved.service. The Domains=~local line tells it to not use upstream DNS servers to resolve .local domains.

Next, we configure Avahi. Avahi will be our mDNS responder. Let's make sure everything we need is installed.

sudo pacman -S avahi nss-mdns bind

In /etc/nsswitch.conf, there'll be a line that starts with hosts: mymachine. After mymachine, add the following:

mdns_minimal [NOTFOUND=return]

It should look something like hosts: mymachines mdns_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] .... This addition instructs the system to use nss-mdns for .local lookups.

Run sudo systemctl enable --now avahi-daemon.

We need to tell NetworkManager that we're using mDNS on a network interface. Run nmcli connection, find your network interface. Copy the UUID. Now run nmcli connection modify <uuid> connection.mdns yes.

Run host -t SOA local. If everything went good, you should see a Host local not found: 3(NXDOMAIN) error. Run ping $(uname -n).local, and you should see your machine's local IP responding. Congrats!

If you're having problems, check the output of journalctl -fu avahi-daemon -e. The Arch Wiki entries I've linked has some helpful troubleshooting info. Make sure your firewall isn't blocking the LAN subnet.

Now you can access your machine using .local instead of the LAN IP.

Next up, custom domains. To check, run avahi-publish -a test.local -R <your-lan-ip>. On another terminal, run ping test.local, and it should be resolving to . You can Ctrl+C the avahi-publish process.

Add the following systemd service template to /etc/systemd/system/[email protected] (make sure to set your LAN IP):

[Unit]
Description=Avahi publisher for %I.local
Wants=avahi-daemon.service
After=network-online.target avahi-daemon.service

[Service]
Type=simple
ExecStart=/usr/bin/avahi-publish -a %I.local -R <your-lan-ip>

DynamicUser=true
NoNewPrivileges=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
SystemCallFilter=@system-service
CapabilityBoundingSet=
AmbientCapabilities=

[Install]
WantedBy=multi-user.target

If you want a jellyfin.local domain, just run sudo systemctl enable --now avahi-publish@jellyfin, and it'll be taken care of! You can create as many as you want.

Now to actually make them resolve to the correct service, we'll need a reverse proxy. Caddy is my choice for this particular usecase.

sudo pacman -S caddy

Create /etc/caddy/conf.d/local.conf:

http://jellyfin.local {
    reverse_proxy localhost:8096
}

You can add as many as you want. If you want self-signed SSL, remove the http:// prefix and add tls internal inside the curly braces. Make sure your firewall is configured appropriately.

Run sudo systemctl enable --now caddy. If it's already running, you can do sudo systemctl reload caddy to reload the configuration.

Voila! Now every device on your LAN that supports mDNS (all Apple devices do!) can connect to your .local domains.