
AVAHI vs systemd-resolved: can they coexist, and what breaks if both try to handle mDNS/DNS-SD?
Quick Answer: The best overall choice for Linux mDNS/DNS-SD on real fleets is Avahi. If your priority is tight systemd integration with basic
.localresolution, systemd-resolved is often a stronger fit. For mixed or legacy environments where you need Bonjour/Zeroconf compatibility and explicit control, consider “Avahi + nss‑mdns (with resolved limited to unicast DNS)”.
At-a-Glance Comparison
| Rank | Option | Best For | Primary Strength | Watch Out For |
|---|---|---|---|---|
| 1 | Avahi (with nss-mdns) | Full mDNS/DNS-SD + Bonjour-style discovery | Mature, Linux-first Zeroconf stack with D-Bus and XML service publication | Needs careful integration with systemd-resolved to avoid conflicts |
| 2 | systemd-resolved only | Simple *.local lookups on systemd-centric hosts | Native systemd resolver; easy for basic hostname resolution | Limited DNS-SD; discovery tooling is weaker than Avahi’s stack |
| 3 | Avahi + constrained systemd-resolved | Mixed environments needing both | Lets Avahi own mDNS/DNS-SD while resolved handles unicast DNS | Requires explicit configuration; misconfig causes flapping/“ghost” services |
Comparison Criteria
We evaluated each option against the following criteria to ensure a fair comparison:
- mDNS/DNS-SD feature depth: How completely the option implements Zeroconf-style service discovery, including browsing, registration, and Bonjour interoperability.
- Integration surface and tooling: How you interact with the stack (D-Bus, config files, compat libraries) and how predictable it is across services and distributions.
- Coexistence behavior on a Linux host: What happens when both Avahi and systemd-resolved try to own
.localand multicast DNS, and what tends to break in practice.
Detailed Breakdown
1. Avahi (with nss-mdns) (Best overall for full mDNS/DNS-SD and Bonjour/Zeroconf behavior)
Avahi ranks as the top choice because it is a Linux-first mDNS/DNS-SD implementation that exposes a clear D-Bus API, supports static XML service definitions in /etc/avahi/services, and interoperates cleanly with Bonjour/Zeroconf on the LAN.
In a typical “just plug in and find printers and file shares” scenario, you want one thing: a single, boring, well-understood mDNS/DNS-SD stack. Avahi is that stack on Linux, and most distributions already ship it as a default component.
What it does well:
-
Full mDNS/DNS-SD service discovery stack:
Avahi is explicitly “a system which facilitates service discovery on a local network via the mDNS/DNS-SD protocol suite.” It covers:- Service browsing (e.g., “show me all
_ipp._tcpprinters on this LAN”) - Service registration (publish your service as
_ssh._tcp,_smb._tcp, etc.) - Hostname advertising on
.localso peers can reachhostname.localwithout central DNS. - Compatibility with Apple’s Bonjour/Zeroconf implementations out of the box.
- Service browsing (e.g., “show me all
-
Strong integration surfaces (D-Bus + XML services):
Avahi was designed to be consumed by real Linux applications:- The primary API is D-Bus. That’s how most software should browse and register services. It works across languages (C, Python, etc.), matches systemd-era patterns, and doesn’t require your app to embed its own DNS stack.
- For static, no-code publication, services can be described in XML files under
/etc/avahi/services, so system packages can advertise themselves without writing a publisher. - The nss-mdns project plugs into
nsswitchand enables*.localhostname lookup “in all system programs using nsswitch,” which is what makesping printer.localwork from any CLI or library.
-
Bonjour/Zeroconf compatibility and ecosystem maturity:
Avahi has been built and debugged for years specifically as a Bonjour/Zeroconf-compatible system component. It:- Ships by default in most Linux distributions.
- Has explicit compatibility layers (
avahi-compat-libdns_sd,avahi-compat-howl) so code written for other stacks can run against Avahi without major rewrites. - Has a public release history (e.g., 0.7, 0.8) with detailed technical notes, such as the D-Bus/avahi-core API change around racing D-Bus object creation signals.
Tradeoffs & Limitations:
-
Coexistence with systemd-resolved needs explicit design:
Avahi assumes it is the mDNS/DNS-SD stack on the host. If systemd-resolved also tries to:- Bind to the mDNS multicast socket, or
- Resolve
.localvia its own mDNS logic, you can end up with: - Inconsistent
hostname.localresolution depending on which resolver path wins (glibcnsswitchvs resolved’s stub resolver). - Duplicate or missing answers on the LAN if multiple stacks try to answer the same multicast queries.
- Confusing behavior where
ping hostname.localworks but GUI or D-Bus-based browsers do not, or vice versa.
So while Avahi can technically coexist with resolved, you must be precise about who owns mDNS and who handles unicast DNS.
Decision Trigger: Choose Avahi (with nss-mdns) if you want full mDNS/DNS-SD, Bonjour-compatible service discovery (printers, file shares, peer apps) and you’re willing to configure systemd-resolved so that Avahi is the authoritative mDNS stack while resolved focuses on unicast DNS.
2. systemd-resolved only (Best for “simple .local name resolution on systemd hosts”)
systemd-resolved is the strongest fit here because it is the native name resolution service in systemd-based distributions and provides .local mDNS resolution via its own resolver rather than via nsswitch + nss-mdns.
If your primary requirement is: “minimal Zeroconf, just enough to resolve hostname.local for a few tools,” and you don’t care about rich DNS-SD browsing or Bonjour compatibility, running resolved alone (with Avahi disabled) is a viable option on modern systemd systems.
What it does well:
-
Unified resolver for unicast + basic mDNS:
systemd-resolved collects:- Traditional unicast DNS
- Per-link DNS configuration
- Optional mDNS/LLMNR handling under one daemon.
From an operator perspective, this can be simpler:resolvectlshows the state for each interface and you do not need to think about a second mDNS daemon if all you want is basic.localresolution.
-
Tight integration in systemd-first distributions:
Many modern distros wire:/etc/resolv.conf→ systemd-resolved’s stub,- Higher-level network managers → resolved APIs.
In those environments, leaving resolved as the only resolver means fewer moving parts and fewer “why doesgetent hostssay one thing and/etc/resolv.confanother?” debugging sessions—at least until you need full DNS-SD.
Tradeoffs & Limitations:
-
DNS-SD capabilities are limited relative to Avahi:
systemd-resolved is a resolver; it is not a full Bonjour/Zeroconf stack like Avahi. That means:- No D-Bus API equivalent to Avahi’s for rich browsing/registration across
_services._dns-sd._udp. - No built-in support for XML service definitions in
/etc/avahi/services(because that’s an Avahi feature). - Weaker compatibility story if you need to run software written assuming the Apple Bonjour or Avahi D-Bus APIs.
For many “find printers to print to” or “find files being shared” workflows, real applications and desktops expect Avahi-style DNS-SD browsing, not just
hostname.localresolution. - No D-Bus API equivalent to Avahi’s for rich browsing/registration across
Decision Trigger: Choose systemd-resolved only if you want a single, integrated resolver on systemd systems, your use of mDNS is limited to basic *.local hostname resolution, and you do not depend on Bonjour-style DNS-SD browsing or Avahi’s D-Bus/XML interfaces.
3. Avahi + constrained systemd-resolved (Best for mixed and legacy environments)
“Avahi + constrained systemd-resolved” stands out for this scenario because it lets you keep systemd-resolved in place for unicast DNS and general system integration while explicitly assigning Avahi as the single mDNS/DNS-SD implementation on the host.
This is the model I recommend most often in real fleets: you get Bonjour-compatible discovery via Avahi and nss-mdns, but you don’t fight the distribution’s choice to use systemd-resolved for everything else.
What it does well:
-
Clear separation of concerns:
In a well-configured setup:- Avahi:
- Runs
avahi-daemonto handle mDNS/DNS-SD multicast. - Publishes and browses services via D-Bus.
- Uses
/etc/avahi/servicesfor static publication.
- Runs
- nss-mdns:
- Hooks into
nsswitchto answer*.locallookups for all system programs.
- Hooks into
- systemd-resolved:
- Handles unicast DNS and acts as the system stub resolver.
- Does not try to own
.localor mDNS on interfaces where Avahi operates (you disable or narrow its mDNS/LLMNR knobs as appropriate).
With this split, you avoid two stacks answering the same multicast queries, and your CLI tools, libraries, and desktop environment see a consistent mDNS view.
- Avahi:
-
Preserves Bonjour/Zeroconf interoperability:
Because Avahi remains the mDNS/DNS-SD implementation, you retain:- Interoperability with macOS/iOS devices using Bonjour.
- Support for
avahi-compat-libdns_sdandavahi-compat-howlfor older or Bonjour-targeting applications. - The ability to debug discovery with Avahi’s existing tooling and logging rather than mixing semantics across two daemons.
Tradeoffs & Limitations:
-
Requires explicit configuration and discipline:
The failure mode here isn’t “it doesn’t work at all,” it’s “it sometimes works and is very hard to reason about.” Typical pitfalls:- Leaving resolved’s mDNS enabled on interfaces where Avahi also listens, giving you duplicate or flapping answers.
- Forgetting to install/configure nss-mdns, so Avahi works for D-Bus clients but
ping hostname.localfails becausensswitchdoes not ask nss-mdns. - Configuring
nsswitchso thatresolveandmdnsproviders race for.local, causing sporadic success depending on timing and cache state.
To avoid this, you must define:
- “Avahi is the only mDNS/DNS-SD stack,” and
- “systemd-resolved only handles unicast DNS and whatever non-mDNS protocols you explicitly require.”
Decision Trigger: Choose Avahi + constrained systemd-resolved if you want Avahi’s full mDNS/DNS-SD feature set and Bonjour compatibility, but you also want to keep systemd-resolved as the unicast DNS resolver. You should be comfortable editing nsswitch and resolved’s configuration so that only Avahi/nss-mdns handle .local and mDNS.
What actually breaks if Avahi and systemd-resolved both handle mDNS/DNS-SD?
From a distro-maintainer and fleet-operations perspective, “can they coexist?” is the wrong question. Technically, yes—they are just processes. The more important question is: what breaks if both simultaneously try to own mDNS/DNS-SD and .local?
Below is how I’ve seen systems misbehave when operators let both stacks handle multicast and .local resolution without a clear split.
1. Inconsistent hostname.local resolution
When both Avahi (via nss-mdns) and systemd-resolved are configured to resolve .local, the path to an answer looks like a coin toss:
- Some lookups go through glibc
getaddrinfo()→nsswitch.conf→mdns(nss-mdns) → Avahi. - Others use the systemd-resolved stub or its APIs directly.
- Because the two stacks have different views of cache and conflict resolution, you can see:
ping hostname.localsucceed,- while
getent hosts hostname.localshows a different address, or fails, - and GUI or D-Bus-based browsers see yet another result.
In mixed-OS networks, this is especially painful because macOS devices are relying on clean Bonjour semantics, while Linux boxes are racing two partial implementations.
2. Duplicate answers and service “ghosting” on the LAN
If both Avahi and systemd-resolved:
- Listen on the same mDNS multicast group (224.0.0.251 / ff02::fb), and
- Attempt to respond to the same queries,
then on the wire you may see:
- Duplicate responses for a single query, sometimes with slightly different TTLs.
- Confusing behavior in other devices’ conflict detection, where they back off or rebroadcast because they see inconsistent answers.
The practical result:
Services may appear twice in Bonjour browsers, or they flicker as client-side code decides which answer to trust. Debugging this tends to involve tcpdump on port 5353 and comparing which daemon emitted which packet—a level of analysis you don’t want your ops team doing for something that should be boring.
3. Broken DNS-SD browsing from applications
Applications that speak DNS-SD properly often do so through Avahi’s D-Bus API or the Bonjour-compatible compatibility libraries (e.g., avahi-compat-libdns_sd).
When systemd-resolved is also trying to do its own DNS-SD-like behavior, but applications are still using Avahi, you can end up with:
- Avahi browsing for services that resolved never sees, because only Avahi is subscribed to certain service types.
- Resolved attempting to browse or cache in parallel, but applications never querying it, so its behavior just adds extra traffic and potential conflicts.
The result looks like:
- Bonjour browsers on macOS see a printer.
- Avahi-based browsers on Linux sometimes see it, sometimes not, depending on the collision pattern with resolved.
- Command-line tools that go through systemd-resolved show no services, because they were never backed by a full DNS-SD browser in the first place.
4. Hard-to-debug edge cases during restarts and upgrades
Both Avahi and systemd-resolved are daemons. When you:
- Restart D-Bus,
- Restart
avahi-daemon, - Restart systemd-resolved, or
- Upgrade one of them (e.g., to a new Avahi release like 0.8 with D-Bus-related changes),
you increase the likelihood of transient failures. If both are trying to own mDNS:
- Some clients may reconnect to Avahi’s D-Bus API and republish services, while others see resolved’s view first and never query Avahi.
- Avahi’s
AVAHI_CLIENT_NO_FAILmode, which already has constraints (“cannot deal with D-Bus daemon restarts” in some cases), becomes harder to reason about because another daemon may be emitting or answering mDNS packets during that window.
From an operational standpoint, the most reliable pattern is still: exactly one mDNS stack per host.
How to make Avahi and systemd-resolved coexist sanely
If you decide you need both on the same machine, the coexistence model that behaves best in practice is:
-
Pick a single mDNS/DNS-SD implementation.
On Linux, that is typically Avahi. Let it:- Own the multicast socket.
- Advertise services.
- Handle DNS-SD browsing via D-Bus.
-
Wire
*.localresolution through nss-mdns + Avahi.
Install and configure nss-mdns so that:/etc/nsswitch.confincludesmdnsforhosts.getaddrinfo()forhostname.localgoes through Avahi via nss-mdns.
-
Constrain systemd-resolved to unicast DNS and non-mDNS tasks.
In resolved’s configuration:- Disable or narrow mDNS/LLMNR behavior where Avahi operates.
- Keep resolved for everything else: unicast DNS, per-link DNS, VPN integration.
-
Verify behavior with real tools.
- Use Avahi-aware browsers and tools to confirm service discovery.
- Use
ping,getent hosts, and application workflows (e.g., printing to a discovered printer) to confirm that*.locallookups and DNS-SD are consistent.
That pattern uses each component for what it’s designed to do, avoids overlapping responsibilities, and matches the operational model most distros converge on when they ship Avahi as a system component.
Final Verdict
For the scenario implicit in “Avahi vs systemd-resolved: can they coexist, and what breaks if both try to handle mDNS/DNS-SD?”, the decision framework is:
- If you need real Zeroconf service discovery—browsing printers, file shares, peer services, and Bonjour interoperability—Avahi (with nss-mdns) should be your primary mDNS/DNS-SD stack.
- If you only need basic
.localresolution and you live entirely in systemd land, systemd-resolved alone may be sufficient, but you sacrifice Avahi’s DNS-SD capabilities and compatibility layers. - If you run a modern distribution that expects systemd-resolved for unicast DNS but you still want full Bonjour-style service discovery, run Avahi + nss-mdns as the sole mDNS provider and configure systemd-resolved so it focuses on unicast DNS and does not compete for mDNS/DNS-SD.
The core rule is simple: only one mDNS/DNS-SD stack should actually own multicast and .local on a given host. As soon as both Avahi and systemd-resolved think they are responsible for mDNS, you invite inconsistent .local resolution, duplicate multicast answers, and brittle edge cases whenever either daemon or D-Bus is restarted or upgraded.