Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ddnsd

Unified DDNS Client

ddnsd is a single-binary daemon that keeps DNS records and CDN/edge origins pointed at your current public IP. It periodically detects the machine's public IPv4/IPv6 address and, whenever it changes, pushes the new address to every configured target — a Cloudflare record, an Alibaba Cloud DNS record, a Tencent EdgeOne origin, and more — from one config file.

Features

  • One daemon, many targets — sync any number of DNS records and CDN origins across different providers at once.
  • DNS and origin following — besides classic A/AAAA record updates, it can rewrite the origin address of CDN / edge-acceleration services (Tencent EdgeOne, Tencent CDN, Alibaba Cloud ESA), which plain DDNS clients cannot do.
  • Dual stack — IPv4 and IPv6 are detected, tracked, and retried independently per target.
  • Change detection — provider APIs are only called when the IP actually changed since the last successful apply; failed applies are retried on the next tick.
  • Deterministic IP detection — the v4/v6 probes use address-family-pinned HTTP clients, so a dual-stack IP service always reports the right family.
  • Hot reload — send SIGHUP to reload config.yaml without restarting; check period, probe DNS resolver, and provider list are updated in place, and last-applied state is carried over so nothing is re-applied spuriously.
  • Structured logging with rotation — zap logs to stdout plus optional rotating JSON files (a full log and a warn+ error log) via lumberjack.

Supported providers

DNS record providers (upsert an A / AAAA record)

type Service Credentials
cloudflare Cloudflare DNS API token
aliyun-dns Alibaba Cloud DNS (云解析 DNS) AccessKey ID / Secret
dnspod Tencent Cloud DNSPod (DNS 解析) SecretId / SecretKey
volcengine-dns Volcengine DNS (火山引擎云解析) AccessKey / SecretKey

CDN origin-following providers (rewrite a CDN/edge origin address)

type Service Notes
tencent-edgeone Tencent EdgeOne (边缘安全加速平台) Two modes: origin group (dual stack) or acceleration domain (single family)
tencent-cdn Tencent CDN (内容分发网络) Updates an accelerated domain's origin
aliyun-esa Alibaba Cloud ESA (边缘安全加速) Updates an origin inside an origin pool

Getting started

Build

Requires Go ≥ 1.26.

go build -ldflags="-s -w" -trimpath -o ddnsd

Configure

ddnsd reads config.yaml from its working directory. The config.yaml in this repository is a fully commented example — copy it next to the binary and edit it. A minimal config:

# Check interval in seconds.
period: 60

api:
  # Public IP source per family: "ipip.net", "gh.ink", or a custom URL.
  # Leave blank to disable that family.
  v4: "ipip.net"
  v6: ""
  # resolver: ""   # optional custom DNS server for probe-hostname lookups

providers:
  - type: cloudflare
    name: home
    ipv4: true
    ipv6: false
    token: "your-api-token"
    zone: "your-zone-id"
    record: "home.example.com"

Run

./ddnsd

The daemon runs in the foreground, checks the public IP every period seconds, and applies changes to all configured providers. Reload the config at runtime with:

kill -HUP $(pidof ddnsd)

Configuration reference

Global options

Key Description Default
period Check interval in seconds 60 when unset or invalid
api.v4 / api.v6 Public IP source for that family: ipip.net, gh.ink, or a custom URL. Empty disables the family. disabled
api.resolver Optional custom DNS server (an IP literal, optionally ip:port) used to resolve probe hostnames. Empty or invalid uses the system resolver. system resolver
log.file.all Rotating JSON log with everything (empty disables) disabled
log.file.err Rotating JSON log with warnings and above (empty disables) disabled
log.max_size Max size of a log file in MB before rotation
log.max_backups Max number of rotated files to keep
log.max_age Max age of rotated files in days
log.compress Compress rotated files false

A custom IP source must respond 200 OK with a bare IP as the body (e.g. 1.1.1.1) and include the protocol in the URL. The URL is fetched over the matching address family only, so the service must be reachable via that family.

Probe hostnames (myip.ipip.net, v4/v6-myip.gh.ink, or the host in a custom API URL) are resolved with the system resolver by default. Set api.resolver to a DNS server IP — optionally with a port, e.g. 1.1.1.1:5353 — to route those lookups through that server instead; useful when the probe hostnames are only reachable via a specific resolver.

Provider entries

Every entry under providers: shares four common fields; the rest are provider-specific:

Key Description
type Provider type, one of the tables above
name Optional friendly name used in logs
ipv4 Track the A record / v4 origin
ipv6 Track the AAAA record / v6 origin

At least one of ipv4/ipv6 must be true, otherwise the entry is skipped.

cloudflare

Key Description
token API token with DNS edit permission
zone Zone ID
record Full record name (FQDN), e.g. home.example.com

aliyun-dns

Key Description
access_key_id / access_key_secret Alibaba Cloud credentials
domain Apex domain, e.g. example.com
record FQDN; the RR/host label is derived from it
endpoint Optional API endpoint, defaults to alidns.aliyuncs.com

dnspod

Key Description
secret_id / secret_key Tencent Cloud credentials
domain Apex domain
record FQDN
line Optional record line, defaults to 默认

volcengine-dns

Key Description
access_key / secret_key Volcengine credentials
domain Apex domain
record FQDN
region Optional region, e.g. cn-north-1

tencent-edgeone

Key Description
secret_id / secret_key Tencent Cloud credentials
zone_id EdgeOne zone ID, e.g. zone-xxx
origin_group_id Mode A — origin group: rewrites the matching-family record inside the group via read-modify-write; other-family and domain records survive, so dual stack works
domain_name Mode B — acceleration domain: binds the accelerated domain directly to a single IP origin (no origin group needed); single family only
region Optional API region

Set exactly one of origin_group_id / domain_name.

tencent-cdn

Key Description
secret_id / secret_key Tencent Cloud credentials
domain Accelerated domain whose origin should follow the IP

aliyun-esa

Key Description
access_key_id / access_key_secret Alibaba Cloud credentials
site_id ESA site (站点) ID
origin_pool_id Origin pool (源站池) ID
origin_name Optional: which origin in the pool to update
endpoint Optional API endpoint, defaults to esa.cn-hangzhou.aliyuncs.com

Runtime behaviour

  • Each provider entry becomes one independent job per enabled family, so v4 and v6 are change-detected and retried separately.
  • A provider API is only called when the detected IP differs from the last successfully applied value; a failed apply keeps the old state so the next tick retries it.
  • Every provider API call is bounded by a 30-second timeout.
  • If an IP probe fails or a family is disabled, that family is simply skipped for the tick — other targets still sync.
  • On SIGHUP the config is re-read: the check period is re-registered if changed and the provider set is rebuilt, carrying over last-applied state for entries that still exist.
  • If a config_debug.yaml exists in the working directory it is loaded instead of config.yaml and the log level switches to debug.

Running as a systemd service

[Unit]
Description=ddnsd - unified DDNS client
After=network-online.target
Wants=network-online.target

[Service]
WorkingDirectory=/opt/ddnsd
ExecStart=/opt/ddnsd/ddnsd
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure

[Install]
WantedBy=multi-user.target

Development

go test ./...

Project layout:

main.go                    entry point: config → logger → checker → cron
internal/config            viper-based config loading, SIGHUP hot reload hooks
internal/checker           public IP detection (family-pinned clients) + sync driver
internal/provider          Provider interface, registry, change-detecting Manager
internal/provider/<type>   one package per provider, self-registered via init()
internal/cron              periodic scheduler (gocron)
internal/logger            zap + lumberjack logging

Adding a provider: create internal/provider/<name>, implement the Provider interface (Name() + idempotent Apply(ctx, family, ip)), call provider.Register("<type>", factory) in init(), and blank-import the package in internal/provider/all/all.go.

License

Apache-2.0

About

Unified DDNS Client

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages