Subdomains with Avahi

Created at
16 January 2026
Last modified
16 February 2026
Last promoted at
19 January 2026
Status
🌲 tree
Tagged

Multicast DNS (or mDNS) is a protocol that allows devices on a local network to publish (and be referenced by) their own addresses (eg computername.local) rather than forcing everyone to resort to IP addresses (eg 192.168.1.52).

It's pretty cool because:

  • It's easier to remember your computer is at computername.local than it is to remember it's at 192.168.1.52.
  • While it's possible to set up your router so that your computer is always at the same IP, it's still a little work.

mDNS is implemented in OS X through Apple's Bonjour implementation, and (generally) in Linux through Avahi. When you do this, your device's URL will usually be <devicename>.local.

If you're running a homeserver, you might have multiple services, each running on its own port. And if you've hung out on the web for a bit, you'll be wondering if there's any way to set up subdomains of your lovely mDNS-implemented local URL. If you give it a web search, you'll find some people doing the same, some people telling you to use python libraries for Avahi (which I'm not sure are still maintained), and even some AI slop telling you to change config settings in Avahi which don't exist.

And here's how you actually do it.

Current settings
Because things get out of date on the web, here's some points of reference for you to gauge whether or not this advice is still current and correct:
  • Date of writing: January 2026
  • Operating system: Fedora release 43
  • Avahi version: 0.9~rc2

Step 0: Prerequisites

This assumes you're running on a Linux distro which usessystemd for service management. You should install avahi using your preferred package manager.

Step 1: Outline the subdomains you want

We're going to be cute for this step: we're going to set up a little file in Avahi's config space for our subdomains. For me, this is /etc/avahi. Your mileage may vary.

Let's set up three subdomains: tv, music, and wiki (again, assuming we're running some services on our home server) - these will go to eg tv.computername.local, music.computername.local and wiki.computername.local. Which will be pretty cool.

In your terminal:

cd /etc/avahi
echo tv\nmusic\nwiki > subdomains

This will create a file subdomains with three lines, each line showing a subdomain. Easy!

Step 2: Build the publication process

To let other devices on our network know about these subdomains, we need Avahi to publish them. The following shell script does this, leaning heavily on u1aryz' how-to-avahi-subdomain script.

#!/usr/bin/env bash
set -euo pipefail

# Default interface
readonly DEFAULT_INTERFACE="wlp0s20u2"

# Where we store aliases
readonly SUBDOMAIN_FILE="/etc/avahi/subdomains"


# Logging functions
log_info() {
  echo -e "[INFO] $*" >&2
}

log_error() {
  echo -e "[ERROR] $*" >&2
}

show_usage() {
  cat >&2 <<-EOF
    Usage: $0 [-i <interface>]

    Options:
      -i <interface>  Network interface to use (default: $DEFAULT_INTERFACE)
      -h              Show this help message
EOF
}

check_requirements() {
  local missing_deps=()
  local required_commands=("avahi-publish" "ip")

  for cmd in "${required_commands[@]}"; do
    if ! command -v "$cmd" &>/dev/null; then
      missing_deps+=("$cmd")
    fi
  done

  if [[ ${ #missing_deps[@]} -gt 0 ]]; then
    log_error "Missing required dependencies: ${missing_deps[*]}"
    exit 1
  fi
}

validate_interface() {
  local interface="$1"

  if ! ip link show "$interface" &>/dev/null; then
    log_error "Network interface '$interface' not found."
    log_error "Available interfaces:"
    ip link show | grep -E '^[0-9]+:' | awk -F': ' '{print "  " $2}' | sed 's/@.*//' >&2
    exit 1
  fi
}

get_interface_addresses() {
  local interface="$1"

  local ipv4_addresses=$(ip -4 -o addr show dev "$interface" scope global | awk '{print $4}' | cut -d/ -f1)

  # Check if we have IPv4 addresses
  if [[ -z "$ipv4_addresses" ]]; then
    log_error "No IPv4 addresses found on interface '$interface'."
    exit 1
  fi

  local ipv6_addresses=$(ip -6 -o addr show dev "$interface" scope global | awk '{print $4}' | cut -d/ -f1)

  # Combine IPv4 and IPv6 addresses
  local all_addresses="$ipv4_addresses"$'\n'"$ipv6_addresses"

  echo "$all_addresses"
}

publish_addresses() {
  local fqdn="$1"
  local addresses="$2"

  log_info "Publishing '$fqdn' with addresses:"

  for addr in $addresses; do
    log_info "  $addr"
    avahi-publish -f -a -R "$fqdn" "$addr" &
  done
}

main() {
  local interface="$DEFAULT_INTERFACE"

  # Parse command line options
  while getopts ":i:h" opt; do
    case "$opt" in
    i) interface="$OPTARG" ;;
    h)
      show_usage
      exit 0
      ;;
     \?)
       log_error "Invalid option -$OPTARG"
       show_usage
       exit 1
       ;;
     :)
       log_error "Option -$OPTARG requires an argument"
       show_usage
       exit 1
       ;;
     esac
   done
   shift $((OPTIND - 1))

   # Validate arguments
   if [[ $# -ne 0 ]]; then
     log_error "No arguments are required."
     show_usage
     exit 1
   fi

   # Check alias file exists
   if [ ! -f $SUBDOMAIN_FILE ]; then
     log_error "File $SUBDOMAIN_FILE does not exist."
     exit 1
   fi

   # Check dependencies
   check_requirements

   # Validate interface
   validate_interface "$interface"

   # Get addresses
   local addresses=$(get_interface_addresses "$interface")

   # Populate
   for subdomain in $(cat $ALIAS_FILE)
   do
     local fqdn="${subdomain}.$(hostname).local"

     # Publish addresses
     publish_addresses "$fqdn" "$addresses"
   done

   # Wait for all background processes
   wait
 }

main

If you've had to change where you keep your subdomain file, change the constant SUBDOMAIN_FILE. Depending on your computer, your wireless interface may also be different from mine - check out how you're connected to the network through ip link show and select the appropriate interface.

As a guided tour of the script:

  • Lines 1-85 set up various constants and auxiliary functions.
  • Lines 88-118 parse arguments to ensure we're advertising through the right interface.
  • Lines 119-123 are simply us checking a subdomain file exists.
  • Lines 125-132 involve us ensuring the system has the right things installed, that the interface exists, and collecting our IP address(es) on that interface.
  • Lines 134-141 are the loop where we do the work - we use avahi-publish to publish each subdomain, pointing to our IP addresses. These processes are run in the background as child processes to this one.
  • On Line 144 we call wait, allowing all background processes to halt before we quit out.

Store this where you will - I've saved mine as /usr/local/bin/avahi-publish-subdomain (and run chmod +x on it to ensure it can be run).

Of course, it sucks having to open up your terminal and run this script just to get the functionality going. Thankfully, we can run it through systemd.

Step 3: Run through systemd

To do this, we'll make a nice simple systemd entry. cd to /etc/systemd/system, and create a file avahi-subdomain.service with the content:

[Unit]
Description=Publish mDNS subdomains under %H.local
Requires=avahi-daemon.service
After=avahi-daemon.service
StartLimitBurst=5
StartLimitIntervalSec=10s

[Service]
Type=simple
ExecStart=/usr/local/bin/avahi-publish-subdomain
Restart=on-failure
RestartSec=2

[Install]
WantedBy=multi-user.target

You'll notice that this file references our script avahi-publish-subdomain above - point it to wherever your file is.

Now all we need to do it queue it up in systemd:

sudo systemctl enable avahi-publish-subdomain

And you should be good to go!