# python-zeroconf, multicast DNS service discovery in pure Python

> python-zeroconf implements Zeroconf, the Bonjour and Avahi compatible service discovery protocol, entirely in Python with no D-Bus and no dependency on an event loop, so a printer or a home service can be found on the local network without the platform's own discovery daemon. It is LGPL-2.1-or-later, supports IPv4 and partially IPv6, and offers an optional Cython extension.

**python-zeroconf/python-zeroconf** — A pure python implementation of multicast DNS service discovery

- Repository: https://github.com/python-zeroconf/python-zeroconf
- Stars: 742 · Forks: 233
- Language: Python
- License: NOASSERTION
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/python-zeroconf-python-zeroconf

## What the library is and how it differs from the alternatives

python-zeroconf is a pure Python implementation of multicast DNS service discovery, and it is compatible with Bonjour and Avahi.

The project is a fork of pyzeroconf, originally by Paul Scott-Murphy, modified by William McBrine. That lineage matters because it explains the design: the original was an attempt to implement Zeroconf in Python rather than wrapping a system daemon, and the fork has kept that shape.

The README lists what it claims against other Zeroconf, Bonjour and Avahi Python packages. It is not tied to Bonjour or Avahi. It does not use D-Bus, which is the mechanism the reference implementations use to talk to the system resolver. It does not force you to use a particular event loop or Twisted, though asyncio is used under the hood without being required. It is pip-installable and has a PyPI distribution. And it has an optional Cython extension for performance, with pure Python supported as well.

The D-Bus point is the one to understand. The usual way to do discovery on Linux is to talk to Avahi over D-Bus, and on macOS to talk to Bonjour through its own API. Both require a system daemon to be running. This library implements the protocol itself, so it works on a machine with no daemon, inside a container, on a platform with no equivalent, and in an environment where you do not want to take a dependency on a system service.

The cost is that you are now responsible for the parts the daemon would do: choosing interfaces, handling conflicts, and dealing with the fact that multicast on a network segment is a shared, unreliable medium. That is the trade the library makes, and it is a reasonable one for a Python application that wants discovery to just work.

The documentation is on Read the Docs, the project is on PyPI as zeroconf, and the README says explicitly that the project is actively maintained.

## Browsing for services and listing service types

The usage is a listener class and a browser, and the example is short enough to read in full.

```python
from zeroconf import ServiceBrowser, ServiceListener, Zeroconf


class EventLogger(ServiceListener):
    def add_service(self, zc: Zeroconf, type_: str, name: str) -> None:
        info = zc.get_service_info(type_, name)
        print(f"discovered {name}: {info}")

    def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None:
        print(f"lost {name}")

    def update_service(self, zc: Zeroconf, type_: str, name: str) -> None:
        print(f"refreshed {name}")


with Zeroconf() as zc:
    ServiceBrowser(zc, "_http._tcp.local.", EventLogger())
    input("browsing, press enter to stop\n")
```

The three listener methods are the shape of the API. add_service fires when a service appears, and the service information is fetched on demand with get_service_info, which is the call that actually resolves the address and TXT records. remove_service fires when a service goes away, and update_service when a service refreshes its record, which is how a changed port or a changed TXT record reaches the application.

The Zeroconf object is used as a context manager, so the sockets are closed when the block ends. That is worth preferring to an explicit close, because a Zeroconf instance holds a multicast socket and a background thread, and leaking either keeps the port bound.

To see what service types exist on a network, there is a separate helper:

```python
from zeroconf import ZeroconfServiceTypes
print('\n'.join(ZeroconfServiceTypes.find()))
```

That is more useful than it first appears, because the service type strings are the actual wire format and the list of them is not something you can guess reliably. A browsable HTTP service is _http._tcp.local., and there are dozens of others, so discovering the list on a given network is the practical first step when you are looking for a device that does not respond to the type you expected.

The examples directory in the repository has more, and the README points to it.

## Interfaces, traffic and the IPv6 caveats

Three limitations are documented and they are the ones that will affect you.

The first is about interfaces. Discovery and service registration use all available network interfaces by default, and if you want to customise that you need to pass the interfaces argument when constructing the Zeroconf object. That default is convenient on a laptop but wrong in a container or on a host with a virtual interface, where the library may end up sending multicast onto an interface that goes nowhere. The README's own note is that the argument is where you customise it, and the code is the reference for what the values mean.

The second is traffic. The README explains that before version 0.32 most of the traffic reduction techniques described in RFC 6762 section 7 were not implemented, which could lead to excessive network traffic, and it is highly recommended that version 0.32 or later is used if this is a concern. That is a real operational point rather than a technicality. Multicast DNS is chatty by design, and a library that does not suppress the duplicate queries and the known-answer packets generates traffic that a busy network notices.

The third is IPv6, and the README is candid that support is relatively new and currently limited. InterfaceChoice.All is an alias for InterfaceChoice.Default on non-POSIX systems. Dual-stack IPv6 sockets are used, which may not be supported everywhere, with some BSD variants not having them. And listening on localhost with ::1 does not work, with help in understanding why being appreciated.

That last one is worth dwelling on, because it is a counter-intuitive failure. The point of an IPv6 link-local address on the loopback interface is that it gives you a scope that works without configuration, and a library that binds to it will find nothing when a test expects a service on the local machine. A test that assumes discovery works on localhost will pass on some platforms and fail on others for a reason that has nothing to do with the code under test.

The version history in the release list shows why these caveats are phrased that way. The releases are 0.151.3, 0.151.4 and 0.151.5, with two of them on the same day in September 2026, and the version numbers have long since moved past the 0.32 threshold the README sets for traffic reduction.

## Versioning, the licence and where this fits

The project uses semantic versioning, and the version numbers in the release list are 0.151.3 through 0.151.5, which is a lot of releases for a package with a stable API. The 0.x prefix means the minor version can still break, so the practical reading of semantic versioning here is narrower than it looks. Within a 0.151.x series, expect no breaking changes; across minor versions, expect some.

The Python compatibility statement is CPython 3.10 and later, and PyPy 3.10 and later. The optional Cython extension is the performance lever, and the README is explicit that pure Python is supported as well, so the choice of installation is a performance decision rather than a requirement.

The licence is GNU Lesser General Public License v2.1 or later. The full text of the LGPL 2.1 is in the COPYING file, and the README notes that you may at your option use the library under the terms of any later version of the LGPL published by the Free Software Foundation, with the canonical SPDX identifier given as LGPL-2.1-or-later. For a library you import into an application rather than one you distribute, the LGPL is a reasonable choice and generally does not oblige you to publish your own code, but the details of how you link and distribute matter and this is not legal advice.

The comparison to make is with the system resolvers. On a Linux server, Avahi is almost certainly already running, and talking to it over D-Bus is a few lines and gives you a maintained, hardened implementation with proper conflict handling and the traffic suppression the RFC describes. Doing it yourself in Python means you own all of that. The case for this library is therefore a machine where Avahi is absent or unwanted: a container, a locked-down appliance, a platform without a daemon, or an application that would rather not take a D-Bus dependency.

The other comparison is with the other pure Python implementations, and the README's list of advantages is the argument: not tied to a specific daemon, no D-Bus, no forced event loop, pip-installable, with an optional Cython extension. That is a real advantage for a library you want to vendor into a Python application.

The honest cost is the surface area you now own. Interface selection, the loopback limitation, and the fact that a discovery library that listens on the wrong interface appears to find nothing at all, with no error. Those are the failure modes to test for in your environment before you depend on discovery working.

## Conclusion

Adopt python-zeroconf if you need to find or advertise network services from Python without binding your application to Avahi, Bonjour or a particular event loop, particularly on a platform where none of those are installed. Skip it if you are writing a systemd service on Linux, since the system resolver already does this and does it better. Verify first by browsing for _http._tcp.local. on your own network and confirming you see the services you expect, then check the interface list, because discovery and registration use all available network interfaces by default.

## FAQ

### What does python-zeroconf implement?

A pure Python implementation of multicast DNS service discovery, the Zeroconf protocol, and it is compatible with Bonjour and Avahi. It can both browse for services and register them, without talking to a system resolver over D-Bus.

### How do I browse for services with python-zeroconf?

Create a Zeroconf object, pass it to a ServiceBrowser with a service type such as _http._tcp.local. and a ServiceListener implementation whose add_service, remove_service and update_service methods are called as services appear, disappear and refresh. The Zeroconf object works as a context manager so its sockets are closed at the end of the block.

### Which network interfaces does python-zeroconf use?

Discovery and service registration use all available network interfaces by default. To customise that you pass the interfaces argument when constructing the Zeroconf object, which the README says to see the code for details of the possible values.

### What are the known limitations of python-zeroconf?

IPv6 support is relatively new and limited. InterfaceChoice.All aliases InterfaceChoice.Default on non-POSIX systems, dual-stack IPv6 sockets are used which some BSD variants do not support, and listening on localhost with ::1 does not work. Before version 0.32 the RFC 6762 traffic reduction techniques were not implemented, so 0.32 or later is recommended.

### What licence is python-zeroconf released under?

GNU Lesser General Public License v2.1 or later, with the canonical SPDX identifier LGPL-2.1-or-later. The full text is in the COPYING file, and you may at your option use it under any later version of the LGPL published by the Free Software Foundation.

## Sources

- [Issues](https://github.com/python-zeroconf/python-zeroconf/issues)
- [python-zeroconf/python-zeroconf on GitHub](https://github.com/python-zeroconf/python-zeroconf)
- [README](https://github.com/python-zeroconf/python-zeroconf/blob/master/README.md)
- [Releases](https://github.com/python-zeroconf/python-zeroconf/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/python-zeroconf-python-zeroconf
