ExternalDNS Webhook Provider for UniFi

July 25, 2026 · View on GitHub

CI Release License Discord

A webhook provider for ExternalDNS that manages DNS records in a UniFi Network controller. ExternalDNS keeps DNS in sync with your Kubernetes Ingresses and Services; this provider applies those records to UniFi's built-in DNS via the Network Integration API.

Requirements

ComponentMinimum version
ExternalDNSv0.21.0
UniFi OS5.x
UniFi Network10.3.58

How it works

The provider runs as a sidecar alongside the ExternalDNS controller. It speaks the ExternalDNS webhook protocol on one side and the UniFi Network Integration API (/proxy/network/integration/v1/..., specifically the DNS Policies endpoints) on the other. It reaches UniFi one of two ways:

  • Local (default) — connects directly to the controller on your network.
  • Cloud connector — proxies through api.ui.com for consoles you can't reach on the LAN. See Cloud connector.

Domain filtering is handled by the ExternalDNS controller, not by this webhook — see Domain filtering.

Limitations

UniFi uses dnsmasq as its DNS backend, so the provider inherits its constraints:

  • Wildcards (*.example.com) are not supported.
  • One CNAME per name. The webhook reconciles this transparently:
    • creating a CNAME where one already exists evicts the existing record first;
    • if ExternalDNS sends multiple targets for a single CNAME, only the first is used and the rest are dropped with a warning.

Quick start

1. Create a UniFi API key

Every request authenticates with an API key; username/password auth is not supported.

Local controller — log into your console by IP, then go to Settings → Control Plane → Integrations → Create API Key and copy the key.

Only Super Admins can create API keys, but a key keeps working after the user is downgraded. For least privilege, create a dedicated external-dns user, generate its key while it's a Super Admin, then drop it to Site Admin — that's enough to manage DNS records.

Cloud connector — create an account-level key in the UniFi Site Manager under account settings → API. This is different from a per-console local key.

2. Store the key in a Secret

apiVersion: v1
kind: Secret
metadata:
  name: unifi-dns-secret
stringData:
  UNIFI_API_KEY: <your-api-key>

3. Install with Helm

Add the ExternalDNS chart repository:

helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/

Create a values file (external-dns-unifi-values.yaml):

fullnameOverride: external-dns-unifi
provider:
  name: webhook
  webhook:
    image:
      repository: ghcr.io/home-operations/external-dns-unifi-webhook
      tag: main # replace with a versioned release tag
    env:
      - name: UNIFI_HOST
        value: https://unifi.internal # your UniFi controller, or https://api.ui.com for the cloud connector
      - name: UNIFI_API_KEY
        valueFrom:
          secretKeyRef:
            name: unifi-dns-secret
            key: UNIFI_API_KEY
    livenessProbe:
      httpGet:
        path: /healthz
        port: http-webhook
      initialDelaySeconds: 10
      timeoutSeconds: 5
    readinessProbe:
      httpGet:
        path: /readyz
        port: http-webhook
      initialDelaySeconds: 10
      timeoutSeconds: 5
triggerLoopOnEvent: true
policy: sync
sources:
  - gateway-httproute
  - service
txtOwnerId: main
txtPrefix: k8s.main.%{record_type}-
domainFilters:
  - example.com # replace with your domain
serviceMonitor:
  enabled: true

Install:

helm install external-dns-unifi external-dns/external-dns \
  -f external-dns-unifi-values.yaml --version 1.21.1 -n external-dns

See the chart values for additional options.

Configuration

UniFi connection

VariableDescriptionDefault
UNIFI_HOSTController address, or https://api.ui.com for the cloud connector (required).
UNIFI_API_KEYAPI key used to authenticate (required). Local and cloud keys differ — see below.
UNIFI_SITESite name (e.g. default) or site UUID. Resolved to the API's UUID at startup.default
UNIFI_CONSOLE_IDConsole ID. Setting this routes requests through the api.ui.com cloud connector.
UNIFI_SKIP_TLS_VERIFYSkip TLS verification. Ignored in cloud mode.true
UNIFI_CA_CERTPath to a PEM bundle of extra trusted CAs (alternative to skipping verification).
UNIFI_APPLY_WORKERSMaximum concurrent record operations during a reconcile.5
UNIFI_RETRY_ATTEMPTSTotal attempts per request (including the first).3
UNIFI_RETRY_INITIAL_DELAYInitial backoff before the first retry.500ms
UNIFI_RETRY_MAX_DELAYMaximum backoff between retries (also caps Retry-After).10s

Cloud connector

To manage a remote console without exposing the controller on your LAN, set UNIFI_CONSOLE_ID and point UNIFI_HOST at https://api.ui.com. Requests are then proxied through /v1/connector/consoles/{consoleId}/proxy/network/integration/v1/.... The Integration API surface is identical to a local connection — only the routing prefix changes.

  • Use an account-level API key from the UniFi Site Manager, not a per-console local key.
  • Find your console ID via the Site Manager API (GET https://api.ui.com/v1/hosts) or the console URL in unifi.ui.com.
  • UNIFI_SKIP_TLS_VERIFY is ignored — api.ui.com presents a publicly-trusted certificate, and skipping verification would expose your API key.
  • Reconciliation depends on Ubiquiti's API availability and rate limits. The built-in backoff handles 429s, but a local connection is preferable when reachable.

Domain filtering

Configure --domain-filter (and its variants) on the ExternalDNS controller, not on this webhook. UniFi has no zone concept the webhook could narrow against, so it follows the ExternalDNS GetDomainFilter contract and leaves filtering to the controller.

Webhook server

VariableDescriptionDefault
SERVER_HOSTWebhook server bind address.localhost
SERVER_PORTWebhook server port.8888
SERVER_READ_TIMEOUTRequest read timeout.60s
SERVER_READ_HEADER_TIMEOUTRead-header timeout (Slowloris mitigation).5s
SERVER_WRITE_TIMEOUTResponse write timeout.60s
SERVER_IDLE_TIMEOUTKeep-alive idle timeout.120s
SERVER_MAX_HEADER_BYTESMaximum request header size.65536
SERVER_MAX_BODY_BYTESMaximum POST body size before returning 413.5242880 (5 MiB)
HEALTH_SERVER_ADDRAddress for the /metrics, /healthz, /readyz server.:8080
READINESS_CACHE_TTLHow long /readyz caches the upstream probe result.30s
PPROF_ENABLEDMount /debug/pprof/* on the health server (not in prod).false
LOG_LEVELLog verbosity: debug, info, warn, error.info
LOG_FORMATSet to text for human-readable output instead of JSON.JSON

Endpoints

EndpointPortPurpose
/8888ExternalDNS negotiate (returns the provider media type).
/records8888ExternalDNS GET (list) and POST (apply changes).
/healthz8888, 8080Liveness — 200 OK while the process is running.
/readyz8888, 8080Readiness — probes UniFi, cached for READINESS_CACHE_TTL.
/metrics8080Prometheus metrics.
/debug/pprof/8080Go pprof endpoints (only when PPROF_ENABLED=true).

/healthz and /readyz are served on both ports so Kubernetes probes can target the webhook port directly without exposing a second container port through the chart.

Upgrading

Migrating to the UniFi Network 10.3.58 Integration API introduced breaking changes:

SettingChange
UNIFI_EXTERNAL_CONTROLLERRemoved. Point UNIFI_HOST at the controller directly, or use the cloud connector for remote consoles.
DOMAIN_FILTER, EXCLUDE_DOMAIN_FILTER, REGEXP_DOMAIN_FILTER, REGEXP_DOMAIN_FILTER_EXCLUSIONRemoved. Configure --domain-filter on the ExternalDNS controller instead (see Domain filtering).
LOG_FORMAT=testRenamed to LOG_FORMAT=text.
API endpointMoved from the undocumented /proxy/network/v2/api/site/{site}/static-dns/* to the official /proxy/network/integration/v1/sites/{siteId}/dns/policies/* (requires Network 10.3.58+).

Community

Thanks to everyone in the Home Operations Discord community.