Skip to content

home-operations/external-dns-unifi-webhook

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

248 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

ExternalDNS Webhook Provider for UniFi

Tests Lint 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

Component Minimum version
ExternalDNS v0.21.0
UniFi OS 5.x
UniFi Network 10.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

Variable Description Default
UNIFI_HOST Controller address, or https://api.ui.com for the cloud connector (required).
UNIFI_API_KEY API key used to authenticate (required). Local and cloud keys differ — see below.
UNIFI_SITE Site name (e.g. default) or site UUID. Resolved to the API's UUID at startup. default
UNIFI_CONSOLE_ID Console ID. Setting this routes requests through the api.ui.com cloud connector.
UNIFI_SKIP_TLS_VERIFY Skip TLS verification. Ignored in cloud mode. true
UNIFI_CA_CERT Path to a PEM bundle of extra trusted CAs (alternative to skipping verification).
UNIFI_APPLY_WORKERS Maximum concurrent record operations during a reconcile. 5
UNIFI_RETRY_ATTEMPTS Total attempts per request (including the first). 3
UNIFI_RETRY_INITIAL_DELAY Initial backoff before the first retry. 500ms
UNIFI_RETRY_MAX_DELAY Maximum 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

Variable Description Default
SERVER_HOST Webhook server bind address. localhost
SERVER_PORT Webhook server port. 8888
SERVER_READ_TIMEOUT Request read timeout. 60s
SERVER_READ_HEADER_TIMEOUT Read-header timeout (Slowloris mitigation). 5s
SERVER_WRITE_TIMEOUT Response write timeout. 60s
SERVER_IDLE_TIMEOUT Keep-alive idle timeout. 120s
SERVER_MAX_HEADER_BYTES Maximum request header size. 65536
SERVER_MAX_BODY_BYTES Maximum POST body size before returning 413. 5242880 (5 MiB)
HEALTH_SERVER_ADDR Address for the /metrics, /healthz, /readyz server. 0.0.0.0:8080
READINESS_CACHE_TTL How long /readyz caches the upstream probe result. 30s
PPROF_ENABLED Mount /debug/pprof/* on the health server (not in prod). false
LOG_LEVEL Log verbosity: debug, info, warn, error. info
LOG_FORMAT Set to text for human-readable output instead of JSON. JSON

Endpoints

Endpoint Port Purpose
/ 8888 ExternalDNS negotiate (returns the provider media type).
/records 8888 ExternalDNS GET (list) and POST (apply changes).
/healthz 8888, 8080 Liveness — 200 OK while the process is running.
/readyz 8888, 8080 Readiness — probes UniFi, cached for READINESS_CACHE_TTL.
/metrics 8080 Prometheus metrics.
/debug/pprof/ 8080 Go 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:

Setting Change
UNIFI_EXTERNAL_CONTROLLER Removed. 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_EXCLUSION Removed. Configure --domain-filter on the ExternalDNS controller instead (see Domain filtering).
LOG_FORMAT=test Renamed to LOG_FORMAT=text.
API endpoint Moved 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.