A modern re-implementation of Kea DHCP DDNS (D2) which accepts Kea NCR's and talks to RFC 2136 servers like the original, as well as Technitium DNS and PowerDNS via their API endpoints.
  • Go 92.7%
  • Shell 3.9%
  • Nix 1.8%
  • Just 0.9%
  • Dockerfile 0.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Elisamuel Resto ce56a9b671
All checks were successful
CI / Test (push) Successful in 2m9s
CI / Build (push) Successful in 1m44s
CI / Lint (push) Successful in 2m54s
Nix / Flake check + build (push) Successful in 2m47s
Nightly / nightly (push) Successful in 5m33s
ci(nix): use DeterminateSystems installer to enable the sandbox
The /homeless-shelter error is nix building the go-modules fixed-output
derivation without a sandbox; pre-removing the directory doesn't stick because
the build itself recreates it. cachix/install-nix-action falls back to
sandbox=false in the container runner. Switch to the DeterminateSystems
installer, which sets the sandbox up reliably in containerized CI.
2026-08-09 02:15:06 -05:00
.forgejo ci(nix): use DeterminateSystems installer to enable the sandbox 2026-08-09 02:15:06 -05:00
cmd/dhcpd-ddns feat(cmd): add version package and -version flag 2026-08-08 17:29:56 -05:00
deploy initial commit 2026-03-09 02:23:30 -05:00
examples feat(dhcid): add RFC 4703 conflict resolution for HTTP-API backends 2026-08-07 10:03:55 -05:00
internal chore: satisfy stricter linters ahead of CI 2026-08-08 17:31:16 -05:00
nix refactor(nix): derive version from git, drop the VERSION file 2026-08-09 01:50:32 -05:00
packaging feat(packaging): build deb, rpm, and OpenBSD release artifacts 2026-08-08 20:05:33 -05:00
tools feat(dhcid): add RFC 4703 conflict resolution for HTTP-API backends 2026-08-07 10:03:55 -05:00
.editorconfig ci: add Forgejo Actions pipeline, GoReleaser, and OCI image 2026-08-08 17:31:43 -05:00
.gitattributes initial commit 2026-03-09 02:23:30 -05:00
.gitignore feat(nix): add flake, package, NixOS module, and vendorHash CI 2026-08-08 20:05:01 -05:00
.golangci.yml ci: add Forgejo Actions pipeline, GoReleaser, and OCI image 2026-08-08 17:31:43 -05:00
.goreleaser.yaml feat(packaging): build deb, rpm, and OpenBSD release artifacts 2026-08-08 20:05:33 -05:00
Dockerfile ci: add Forgejo Actions pipeline, GoReleaser, and OCI image 2026-08-08 17:31:43 -05:00
flake.lock chore(nix): commit flake.lock 2026-08-08 20:06:15 -05:00
flake.nix refactor(nix): derive version from git, drop the VERSION file 2026-08-09 01:50:32 -05:00
go.mod test: add unit test suite across all packages 2026-08-07 16:50:32 -05:00
go.sum test: add unit test suite across all packages 2026-08-07 16:50:32 -05:00
justfile feat(nix): add flake, package, NixOS module, and vendorHash CI 2026-08-08 20:05:01 -05:00
LICENSE initial commit 2026-03-09 02:23:30 -05:00
README.md feat(packaging): build deb, rpm, and OpenBSD release artifacts 2026-08-08 20:05:33 -05:00

dhcpd-ddns

A drop-in reimplementation of ISC Kea's DHCP-DDNS server (D2) in Go. It accepts NameChangeRequests (NCRs) from Kea DHCP4/DHCP6 over the same JSON-over-UDP wire format as kea-dhcp-ddns, then applies the DNS updates to a configured backend.

Backends

  • RFC 2136 dynamic DNS updates (wire-compatible with Kea D2's dns-servers), with TSIG (incl. digest-bits MAC truncation per RFC 4635) and per-server retry/failover.
  • PowerDNS Authoritative HTTP API.
  • Technitium DNS Server HTTP API.

Features

  • Full RFC 4703 conflict-resolution modes (check-with-dhcid, no-check-with-dhcid, check-exists-with-dhcid, no-check-without-dhcid), with an application-level DHCID ownership store for the HTTP-API backends.
  • Durable NCR queue: in-memory, Redis, or SQLite.
  • Optional record reaper that removes stale DNS records when leases expire.
  • Live configuration reload on SIGHUP (zones, TSIG keys, backends, and listen address) without dropping the queue.

Quick start

# Build (injects version metadata):
just build

# Run with a config file:
./dhcpd-ddns -config /etc/dhcpd-ddns.conf

# Print version:
./dhcpd-ddns -version

With no -config, the daemon runs on built-in defaults (127.0.0.1:53001, in-memory queue) identical to kea-dhcp-ddns.

See examples/ for rfc2136, powerdns, and technitium configurations. The config file is the standard kea-dhcp-ddns DhcpDdns block, plus an optional dhcpd-ddns extensions block for the non-RFC-2136 backends and durable queues.

Installation

Releases ship several formats (see the repository's Releases page):

  • Debian/Ubuntu.deb (amd64/arm64). Installs the binary, a systemd unit, and a default config at /etc/dhcpd-ddns/dhcpd-ddns.conf. Edit it, then systemctl enable --now dhcpd-ddns.

  • RHEL/Rocky/EL9.rpm (amd64/arm64), same layout.

  • OpenBSD — a binary tarball (amd64/arm64); install the binary and run it under your own rc/supervision (there is no native OpenBSD package).

  • Nix / NixOS — a flake. Build with nix build github:erestodo/dhcpd-ddns, or use the NixOS module:

    {
      inputs.dhcpd-ddns.url = "git+https://scm.nullnetwork.cc/erestodo/dhcpd-ddns";
      # in your configuration:
      imports = [ dhcpd-ddns.nixosModules.default ];
      services.dhcpd-ddns = {
        enable = true;
        configFile = ./dhcpd-ddns.conf;
      };
    }
    

Container

docker run --rm \
  -p 53001:53001/udp \
  -v /etc/dhcpd-ddns.conf:/etc/dhcpd-ddns.conf:ro \
  scm.nullnetwork.cc/erestodo/dhcpd-ddns:latest

The image is distroless and runs as a nonroot user. Set ip-address to 0.0.0.0 in the config so the listener is reachable from outside the container. Persist the SQLite queue/store with a volume on /var/lib/dhcpd-ddns.

Development

just          # list tasks
just test     # lint + race tests
just fmt      # gofmt

CI (lint, race tests, snapshot build) and releases (multi-arch OCI images + archives) run on Forgejo Actions; see .forgejo/workflows/.

License

MIT — see LICENSE.