This is a plugin that queries a Unifi Controller in order to determine dhcp clients and network domains.
The Unifi system will do odd things with regards to the dnsmasq implementation, such as:
- Aliases assigned in the interface will not be utilised as the domains assigned in
/etc/hostson gateway.- Hence misbehaving clients with bad/unconfigurable hostnames will not get assigned sane ones.
- Setting a network's search domain to a public domain will cause odd behaviour because the dnsmasq instance
will leak queries for internal clients to the upstream public DNS and subsequently fail.
- You can 'fix' this by using a non public subdomain, but still.
It is similar to USG Easy DNS and was adapted originally from the CoreDNS Traefik Plugin.
The plugin periodically fetches sites, clients, and networks from the UniFi controller. For each DHCP client, it builds a DNS A record by combining the client's name with its network's domain name (e.g. desktop.home.lan), and a matching PTR record in the corresponding in-addr.arpa zone (IPv4 only).
Client names are sanitized to be DNS-safe: lowercased, spaces and underscores replaced with hyphens, invalid characters stripped. The plugin prefers the UI-assigned Name (alias) over the DHCP-reported Hostname. Clients with no name or hostname, or on networks with no domain name, are skipped. Hostname collisions (two clients mapping to the same record) are detected and logged. Reverse-mapping collisions (two clients on the same IP) are likewise logged.
The plugin respects CoreDNS zone boundaries — it only answers queries that fall within the zones configured in the server block. To serve PTR records you must include the relevant reverse zone (e.g. 1.168.192.in-addr.arpa.) in the server block alongside the forward zone.
This plugin is compiled into CoreDNS — it is not a standalone binary.
# 1. Clone CoreDNS
git clone --depth 1 --branch v1.14.6 https://github.com/coredns/coredns.git
cd coredns
# 2. Add the plugin — insert above the forward: line
sed -i '/^forward:forward/a unifi:github.com/navistau/coredns-plugin-unifi' plugin.cfg
# Or edit plugin.cfg manually; the entry should read:
# unifi:github.com/navistau/coredns-plugin-unifi
# placed immediately above:
# forward:forward
# 3. Fetch the plugin module and build
go get github.com/navistau/coredns-plugin-unifi
go generate && go build -o coredns .
# 4. Verify the plugin is included
./coredns -plugins | grep unifiRequires CoreDNS >= v1.11 and Go >= 1.25.
The following multi-stage Dockerfile builds a CoreDNS binary with this plugin included. It is optimised for layer caching — the CoreDNS clone and dependency steps are cached across plugin source changes.
FROM golang:1.25-alpine AS builder
RUN apk add --no-cache git
# Clone CoreDNS and inject plugin — these layers are stable
RUN git clone --depth 1 --branch v1.14.6 https://github.com/coredns/coredns.git /coredns
WORKDIR /coredns
RUN sed -i '/^forward:forward/a unifi:github.com/navistau/coredns-plugin-unifi' plugin.cfg
RUN go get github.com/navistau/coredns-plugin-unifi && \
go generate && \
go build -o coredns .
FROM alpine:3.20
COPY --from=builder /coredns/coredns /usr/local/bin/coredns
EXPOSE 53 53/udp
CMD ["coredns", "-conf", "/etc/coredns/Corefile"]Note: This Dockerfile pulls the plugin from the module proxy (
github.com/navistau/coredns-plugin-unifi). For local development builds, see the integration harness in theintegration/directory.
unifi {
controllerurl http://controller:port/
username svc_coredns
password mysecretpassword
refreshInterval 30
ttl 30
sites default,branch-office
fallthrough
}- controllerurl — URL of the UniFi controller API.
- username — Username for controller authentication.
- password — Password for controller authentication.
- refreshInterval — How often (in seconds) to re-fetch clients from the controller. Default:
30. - ttl — TTL (in seconds) for DNS responses. Default:
30. - sites — Comma-separated list of UniFi site names to query. If omitted, all sites are queried.
- fallthrough — If present, pass unresolved queries to the next plugin.
CoreDNS substitutes {$ENV_VAR} tokens when it parses the Corefile, so credentials do not have to be written into the file:
unifi {
controllerurl http://controller:port/
username {$UNIFI_USERNAME}
password {$UNIFI_PASSWORD}
}Set UNIFI_USERNAME and UNIFI_PASSWORD in the environment of the CoreDNS process (systemd Environment=, Docker environment:, or a Kubernetes secret mounted as env vars). The substitution is a CoreDNS core feature and works for every directive, not just these two. The integration harness in integration/ uses this pattern.
If monitoring is enabled (via the prometheus directive) the following metric is exported:
coredns_unifi_request_count_total{server}- query count to the unifi plugin.
The server label indicates which server handled the request, see the metrics plugin for details.
This plugin reports readiness to the ready plugin. It will be immediately ready.
Create a read-only local admin in UniFi Network (Settings → Admins & Users) and use those credentials for username and password.
Serve a single internal domain. Unresolved queries fall through to the next plugin.
mylocal.tld {
unifi {
controllerurl http://controller:port/
username svc_coredns
password mysecretpassword
refreshInterval 30
ttl 30
fallthrough
}
}
Use the sites directive to limit which UniFi sites are queried. Useful when you have multiple physical sites but only want to expose clients from specific ones.
internal.corp {
unifi {
controllerurl http://unifi.internal:8443/
username svc_coredns
password mysecretpassword
refreshInterval 60
ttl 60
sites default,branch-office
fallthrough
}
}
Unifi handles dynamic DHCP clients. A hosts block allows static overrides (e.g. the router itself). Everything else is forwarded to upstream DNS.
home.lan {
unifi {
controllerurl http://unifi.home:8443/
username svc_coredns
password mysecretpassword
ttl 30
fallthrough
}
hosts {
192.168.1.1 router.home.lan
fallthrough
}
forward . 1.1.1.1 8.8.8.8
}
Include the matching in-addr.arpa. zone in the server block to enable PTR responses for the same DHCP clients.
home.lan 1.168.192.in-addr.arpa. {
unifi {
controllerurl http://unifi.home:8443/
username svc_coredns
password mysecretpassword
ttl 30
fallthrough
}
}
See the manual.