How do I publish a custom service with AVAHI using an XML file in /etc/avahi/services (example for HTTP on port 8080)?
Networking System Software

How do I publish a custom service with AVAHI using an XML file in /etc/avahi/services (example for HTTP on port 8080)?

9 min read

Avahi’s XML service definitions are the simplest way to publish a static service on a Linux LAN without writing any D-Bus client code. You drop an XML file into /etc/avahi/services, Avahi reloads it, and suddenly every Bonjour/Zeroconf-aware system can see your service over mDNS/DNS-SD.

Below I’ll walk through a concrete example: publishing an HTTP service on port 8080 with an XML file, plus how to verify it works and what to check when it doesn’t.


What this approach is (and isn’t)

Avahi is a system for service discovery on local networks via the mDNS/DNS-SD protocol suite. On most Linux distributions it ships by default, targets Linux first, and interoperates with Bonjour/Zeroconf on other devices.

There are two main integration paths:

  • D-Bus API (primary interface, required for most dynamic usage)
  • XML service definitions in /etc/avahi/services (static service publication)

In this guide we’re using the XML route. It’s ideal for:

  • Services that always run on the same host/port (e.g., a local HTTP service on 8080)
  • Packaged daemons where your distro or deployment system can drop a file into /etc/avahi/services
  • Environments where you don’t want to ship or maintain a custom Avahi/D-Bus client

If you need to dynamically change ports, TXT records, or service lifetimes at runtime, you’d use the D-Bus API instead.


Prerequisites

Before creating the XML file, confirm the basics:

1. Avahi is installed and running

On a typical systemd-based distribution:

systemctl status avahi-daemon.service

You want to see active (running). If not:

sudo systemctl enable --now avahi-daemon.service

On non-systemd systems, use the init system your distro provides (e.g., /etc/init.d/avahi-daemon start or equivalent).

2. You have root access

You’ll be writing into:

  • /etc/avahi/services/

You need sufficient privileges (e.g., via sudo) to create files there.

3. Your HTTP service actually listens on port 8080

Make sure there is a real service behind the advertisement:

sudo ss -tlnp | grep ':8080'
# or
sudo netstat -tlnp | grep ':8080'

You should see a process bound to 0.0.0.0:8080 or at least to your LAN-facing IP. Avahi doesn’t validate the service; it only announces it.


Step-by-step: publish HTTP on port 8080 with an XML file

1. Create the services directory (if it doesn’t exist)

Most distros ship this directory already. If not:

sudo mkdir -p /etc/avahi/services

Permissions typically look like:

ls -ld /etc/avahi/services
# drwxr-xr-x root root ...

Leave ownership as root:root with world-readable files inside.

2. Create your XML service file

We’ll define a simple HTTP service pointing to port 8080.

Create /etc/avahi/services/http-8080.service with your editor of choice:

sudo nano /etc/avahi/services/http-8080.service

Paste the following:

<?xml version="1.0" standalone='no'?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">

<service-group>
  <name replace-wildcards="yes">%h HTTP on 8080</name>

  <service>
    <type>_http._tcp</type>
    <port>8080</port>
    <txt-record>path=/</txt-record>
  </service>
</service-group>

Save and exit.

What each element means

  • <!DOCTYPE service-group SYSTEM "avahi-service.dtd">
    Points to the Avahi service DTD; ensures the structure is valid.

  • <service-group>
    Top-level container. You can put one or more <service> entries here that share a common name.

  • <name replace-wildcards="yes">%h HTTP on 8080</name>

    • %h is replaced by the local hostname as known to Avahi (e.g., myhostmyhost HTTP on 8080).
    • replace-wildcards="yes" instructs Avahi to perform that substitution.
  • <type>_http._tcp</type>
    Service type in DNS-SD form. _http._tcp is the standard for HTTP. Other examples: _ipp._tcp for IPP printers, _ssh._tcp for SSH.

  • <port>8080</port>
    The TCP port your service listens on.

  • <txt-record>path=/</txt-record>
    Optional TXT record key-value data. For HTTP, path=/ is a common convention. You can add more txt-record lines if needed.

3. Check syntax and permissions

Verify the file is readable by Avahi:

ls -l /etc/avahi/services/http-8080.service
# -rw-r--r-- 1 root root ... /etc/avahi/services/http-8080.service

Ensure it’s plain text XML and not world-writable.

If you introduce syntax errors, Avahi will log them (see troubleshooting below).

4. Reload or restart Avahi

Many distributions watch /etc/avahi/services and reload automatically when files change, but I prefer an explicit restart to be certain:

sudo systemctl restart avahi-daemon.service

On non-systemd systems, use the equivalent service restart command.


Verifying that the service is published

To confirm that Avahi is actually advertising your HTTP-on-8080 service via mDNS/DNS-SD, use one or more of the following methods.

1. On the same Linux host: use avahi-browse

avahi-browse is usually provided by the avahi-utils or avahi tools package.

Install if necessary:

# Debian/Ubuntu
sudo apt-get install avahi-utils

# Fedora/RHEL/CentOS
sudo dnf install avahi-tools

# Arch
sudo pacman -S avahi

Then browse for HTTP services:

avahi-browse -rt _http._tcp

You should see output similar to:

+   eth0 IPv4 myhost HTTP on 8080          _http._tcp          local
=   eth0 IPv4 myhost HTTP on 8080          _http._tcp          local
   hostname = [myhost.local]
   address = [192.168.1.50]
   port = [8080]
   txt = ["path=/"]

Key checks:

  • The service name matches %h HTTP on 8080 with your hostname substituted.
  • port = [8080].
  • txt = ["path=/"] shows the TXT record you configured.

2. From another Linux machine: avahi-browse over the LAN

On another Linux host on the same local network (with mDNS allowed through firewalls):

avahi-browse -rt _http._tcp

You should see the same service resolved to the IP of the advertising host.

If you see nothing from other machines but you do see the service locally, it often indicates:

  • Firewall blocking mDNS (UDP 5353 multicast) or
  • Multicast being filtered on the network.

3. From macOS: use dns-sd

On macOS, open Terminal:

dns-sd -B _http._tcp local

You should see your service appear. To resolve details:

dns-sd -L "myhost HTTP on 8080" _http._tcp local

The service details should match the Avahi output, including the port and TXT record.


Optional: make hostname.local resolve with nss-mdns

The XML file publishes a DNS-SD service via mDNS. If you also want hostname.local lookups (e.g., curl http://myhost.local:8080/ from any Linux box on the LAN), you’ll typically pair Avahi with nss-mdns.

nss-mdns plugs into nsswitch so that *.local hostnames are resolved via mDNS in all system programs.

1. Install nss-mdns

Package names vary:

# Debian/Ubuntu
sudo apt-get install libnss-mdns

# Fedora
sudo dnf install nss-mdns

# Arch
sudo pacman -S nss-mdns

2. Update /etc/nsswitch.conf

Look for the hosts: line. A common baseline is:

hosts: files mdns_minimal [NOTFOUND=return] dns

Or, if you want more complete mDNS handling:

hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4

Refer to your distro’s nss-mdns documentation for the preferred configuration.

With nss-mdns and Avahi running, ping myhost.local and curl http://myhost.local:8080/ should just work from other Linux machines on the LAN.


Extending the example: multiple services and TXT records

Once you’re comfortable with a single HTTP service, you can extend the XML for more complex scenarios.

Multiple TXT records for HTTP

If your HTTP service needs more metadata:

<service-group>
  <name replace-wildcards="yes">%h HTTP on 8080</name>

  <service>
    <type>_http._tcp</type>
    <port>8080</port>
    <txt-record>path=/</txt-record>
    <txt-record>version=1.0</txt-record>
    <txt-record>role=api</txt-record>
  </service>
</service-group>

Avahi 0.7 added support for encoding binary (non-text) TXT records into XML service definitions, but typical HTTP use cases stick to ASCII text key-value pairs.

Publish more than one service from the same host

You can either:

  • Put multiple <service> entries in a single <service-group>, or
  • Create separate .service files under /etc/avahi/services.

Example with two services in one file:

<service-group>
  <name replace-wildcards="yes">%h Services</name>

  <service>
    <type>_http._tcp</type>
    <port>8080</port>
    <txt-record>role=web</txt-record>
  </service>

  <service>
    <type>_ssh._tcp</type>
    <port>22</port>
  </service>
</service-group>

Both services will share the same base name (%h Services) but appear under their respective types (_http._tcp, _ssh._tcp).


Common issues and how to debug them

When “I dropped a file into /etc/avahi/services but nothing shows up” happens, I usually check the following.

1. XML syntax or structure errors

Avahi logs parse errors. On systemd-based systems:

journalctl -u avahi-daemon.service -b

Look for lines mentioning the filename and XML errors.

Typical problems:

  • Missing closing tags
  • Wrong root element (must be <service-group>)
  • Typo in the DTD line

Fix the file, save, and restart Avahi:

sudo systemctl restart avahi-daemon.service

2. Service type or port mistakes

If the type is wrong, clients won’t discover it under the expected type:

  • For HTTP, the type must be _http._tcp.
  • Don’t forget the leading underscore and _tcp.

Double-check the port:

<port>8080</port>

If your actual service runs on a different port than you configured, discovery will work, but clients will fail to connect. Service discovery doesn’t auto-detect ports; it announces whatever you specify.

3. Avahi not running or blocked by firewall

Ensure Avahi is active:

systemctl status avahi-daemon.service

If other hosts can’t see the service:

  • Confirm the firewall allows UDP 5353 multicast and relevant unicast responses on the advertising host.
  • Some firewall frontends have specific “mDNS” or “Avahi” service toggles.

4. Network or VLAN boundaries

mDNS is multicast scoped to the local link. If clients and the advertising host are on different broadcast domains or VLANs without mDNS relaying/proxying, they will not see each other.

For basic setups, ensure:

  • The advertising host and clients are on the same Wi-Fi/ethernet segment.
  • There is no layer-2 segmentation filtering multicast.

When to switch from XML to the D-Bus API

The /etc/avahi/services method is intentionally static. It’s a good fit when:

  • You have a well-known service port.
  • You want to ship a simple, distro-friendly integration.
  • Configuration management (Ansible, Puppet, system packages) can drop the XML file for you.

Use the D-Bus API instead when:

  • Ports or TXT records change at runtime (e.g., ephemeral ports).
  • You want to publish services only while a process is actually running and tear them down programmatically.
  • You need to react to Avahi server state changes (e.g., hostname collisions, AVAHI_SERVER_REGISTERING).

Avahi’s documentation includes “Choosing an API,” “How to Register Services,” and “How to Browse for Services” sections that walk through the D-Bus-based approach.


Summary

To publish a custom HTTP service on port 8080 with Avahi using an XML file in /etc/avahi/services:

  1. Ensure avahi-daemon is installed and running.

  2. Confirm your HTTP service listens on port 8080.

  3. Create /etc/avahi/services/http-8080.service with a valid XML definition:

    <?xml version="1.0" standalone='no'?>
    <!DOCTYPE service-group SYSTEM "avahi-service.dtd">
    <service-group>
      <name replace-wildcards="yes">%h HTTP on 8080</name>
      <service>
        <type>_http._tcp</type>
        <port>8080</port>
        <txt-record>path=/</txt-record>
      </service>
    </service-group>
    
  4. Restart Avahi (systemctl restart avahi-daemon.service).

  5. Verify with avahi-browse -rt _http._tcp or equivalent tools on other hosts (or dns-sd on macOS).

  6. Optionally install nss-mdns and adjust nsswitch.conf so hostname.local resolves everywhere.

Once this is in place, your service behaves like any Bonjour/Zeroconf-advertised HTTP endpoint on the LAN: peers can discover it automatically without needing IP addresses or manual port documentation.

Next Step

Get Started