UDDNS is a small dynamic DNS updater. It obtains the current public IP address from a provider, updates a DNS record through an updater, and can send notifications when the IP address or update status changes.
- IPv4 and IPv6 update support.
- Providers: RouterOS, external IP services, and local network interfaces.
- Updaters: Cloudflare, Aliyun, DuckDNS, LightDNS and Scaleway.
- Notifiers: Telegram and Discord.
- Configurable update interval.
- Structured logs with optional daily rotated file logging and retention.
- Curl installer with optional systemd service installation.
- Built-in release update checks and atomic self-updates with rollback on Unix.
- GoReleaser-based release artifacts for multiple platforms, with SBOMs and GitHub Actions provenance attestations.
Install the latest release with curl:
curl -fsSL https://github.com/we11adam/uddns/releases/latest/download/install.sh | shFor a verified installation, download the script first and check its GitHub Actions provenance before running it:
curl -fsSLO https://github.com/we11adam/uddns/releases/latest/download/install.sh
gh attestation verify install.sh --repo we11adam/uddns
sh install.shOfficial release archives contain only the platform executable. The installer downloads them only over HTTPS, verifies their checksums, and validates archive contents before extraction. Each release archive also has an attached SBOM and a GitHub Actions provenance attestation; after downloading an archive manually, verify it with:
gh attestation verify /path/to/archive --repo we11adam/uddnsThe installer detects systemd and asks whether to install UDDNS as a systemd
service. The generated unit runs as the dedicated unprivileged uddns user and
passes the configuration through a protected systemd credential, so the source
config can remain owned by root with mode 0600. The installer validates the
configuration before starting the main process and restores the previous
executable, unit, and service state if startup fails. For non-interactive
systemd installation:
curl -fsSL https://github.com/we11adam/uddns/releases/latest/download/install.sh | sh -s -- --systemd --config /etc/uddns.yamlUseful installer options:
--version <tag> Install a specific release tag
--install-dir <dir> Install directory, default: /usr/local/bin
--systemd Install or update the systemd service
--no-systemd Skip systemd service installation
--config <path> Config file path used by the systemd service
--interval <duration> UDDNS_INTERVAL used by the systemd service
--log-dir <dir> Enable rotated file logging in systemd
--log-retention-days <n> Calendar days of logs to keepInstaller-provided install, config, and log paths must be absolute. If the selected config is missing or is not a readable regular file, the systemd unit is enabled but not started.
You can also install from source with Go 1.26.5 or newer:
go install github.com/we11adam/uddns@latestOr download a binary from the releases page.
Print the running build version or check for a newer stable release without changing any files:
uddns version
uddns version --json
uddns self-update --check
uddns self-update --check --jsonOn Linux, macOS, and FreeBSD, install the latest stable release with:
uddns self-updateThe updater selects the exact GoReleaser asset for the current platform,
downloads it over HTTPS, verifies checksums.txt.minisig against the release
public keys embedded in the running binary, and only then uses checksums.txt
to verify the archive's SHA-256 value. It strictly validates the single-file
archive and confirms the staged binary's reported version. It then creates an
adjacent .previous backup and atomically replaces the installed executable.
Unsigned historical releases are rejected as self-update targets. The resolved
target must be a regular file.
Because the operating system may resolve a symlink before UDDNS can inspect the
launch path, the updater cannot reliably identify every package-manager
installation. Use the package manager's own upgrade command when it manages the
installed binary.
If the executable is installed in a root-owned directory, run its absolute path
with sudo, for example:
sudo /usr/local/bin/uddns self-update
sudo systemctl restart uddns.serviceThe first self-update release does not restart a running service automatically. Restart the service after a successful update to run the new binary. Restore the previous binary with:
sudo /usr/local/bin/uddns self-update --rollback
sudo systemctl restart uddns.serviceAn interrupted replacement may leave <binary>.update-pending, which contains
the pre-update executable and is intentionally never discarded automatically.
Inspect both files with version --json. If the installed target is the new
version, move .update-pending to .previous; if the two files are identical,
remove only .update-pending. Resolve this recovery file before running another
update or rollback.
Use --version v1.9.1 to select a specific stable release. Downgrades require
--allow-downgrade and are limited to v1.9.0 or newer because earlier binaries
cannot perform the staged version check; install an older release manually if
needed. Builds reporting version dev require --allow-dev. Windows can check
releases but cannot yet replace a running executable.
UDDNS looks for a config file in this order:
- Path provided with
-c. UDDNS_CONFIG../uddns.yaml.~/.config/uddns.yaml./etc/uddns.yaml.
An unreadable path selected explicitly with -c or UDDNS_CONFIG is an error
and does not fall back to another location. UDDNS does not load .env files
automatically; set environment variables in your shell or service manager. On
Unix, the selected config file must not grant any permissions to group or other
users. Secure it before starting UDDNS:
chmod 600 /etc/uddns.yamlThe protected copy supplied through systemd credentials is an exception:
systemd may represent its service-user ACL as mode 0440. UDDNS accepts only
the tightly controlled, root-owned file directly under
$CREDENTIALS_DIRECTORY; an ordinary group-readable config remains rejected.
Use simple mode when one UDDNS process updates one DNS record. This is the traditional configuration format and remains fully supported.
providers:
use: ip_service
routeros:
endpoint: https://192.168.88.1
username: admin
password: ""
insecure: false
ip_service:
- ifconfig.me
- ip.fm
netif:
name: ppp0
updaters:
use: cloudflare
cloudflare:
apitoken: your-cloudflare-api-token
# Or use email + apikey:
# email: user@example.com
# apikey: your-cloudflare-api-key
domain: ddns.example.com
# Optional. Override the zone inferred from the Public Suffix List.
# zone: example.com
proxy: http://127.0.0.1:2080
aliyun:
accesskeyid: your-access-key-id
accesskeysecret: your-access-key-secret
domain: ddns.example.com
# Optional. Override the zone inferred from the Public Suffix List.
# zone: example.com
regionid: cn-hangzhou
duckdns:
token: your-duckdns-token
domain: your-subdomain
lightdns:
key: your-lightdns-key
domain: ddns.example.com
notifiers:
use: telegram
telegram:
chat_id: -1001234567890
token: 1234567890:telegram-bot-token
proxy: http://127.0.0.1:2080
# Optional. auto uses updater API verification when available.
verify: auto
logging:
level: info
dir: /var/log/uddns
retention_days: 7Configure at least one provider and one updater. Use providers.use and
updaters.use to select a specific implementation when multiple entries are
present. If use is omitted, UDDNS checks configured entries in deterministic
registry order. A configured but invalid provider or updater stops startup with
its configuration error instead of silently falling back to another entry.
Notifiers are optional; when configured, notifiers.use selects the notifier
and invalid notifier configuration stops startup.
Use jobs mode when one process should update multiple DNS records, use different DNS services, or update only selected address families. Providers describe how to obtain IP addresses, updaters describe how to access a DNS service, and each job connects one provider to one updater and one DNS record.
providers:
ip_service:
- ifconfig.me
- ip.fm
netif:
name: ppp0
updaters:
cloudflare:
apitoken: your-cloudflare-api-token
# proxy: http://127.0.0.1:2080
duckdns:
token: your-duckdns-token
jobs:
- name: home-cloudflare
provider: ip_service
updater: cloudflare
record: home.example.com
# Optional. Override the zone inferred from the Public Suffix List.
zone: example.com
families: [ipv4, ipv6]
verify: updater_api
- name: nas-duckdns
provider: netif
updater: duckdns
record: your-subdomain
families: [ipv4]
verify: off
notifiers:
use: telegram
telegram:
chat_id: -1001234567890
token: 1234567890:telegram-bot-token
logging:
level: info
dir: /var/log/uddns
retention_days: 7Job fields:
name: Optional unique job name. Defaults tojob-<n>when omitted.provider: Provider implementation to use, for exampleip_service,routeros, ornetif.updater: Updater implementation to use, for examplecloudflare,aliyun,duckdns,lightdns, orscaleway.record: DNS record to update. For DuckDNS this is the subdomain without.duckdns.org.zone: Optional DNS zone override for Cloudflare, Aliyun, and Scaleway.families: Optional address families. Supported values areipv4andipv6; omitted means both.verify: Optional verification mode. Supported values areauto,off, andupdater_api; omitted meansauto.
When jobs is present, each job has its own last IPv4/IPv6 state and is run
sequentially on the global update interval. Without jobs, UDDNS behaves as a
single implicit default job using the simple-mode config. Notifications from
named jobs are prefixed with the job name.
Transient public-IP service and DNS update requests are retried. Repeated provider, strict-verification, or updater failures apply exponential backoff with jitter only to the affected job; other jobs continue to run.
Jobs select provider and updater implementation names. Jobs that use the same
implementation share that implementation's configuration, for example all
cloudflare jobs use updaters.cloudflare credentials.
Verify behavior:
auto: Use updater API verification when the selected updater supports it; otherwise skip verification.off: Do not verify DNS records before deciding whether to update.updater_api: Require the selected updater to query the current DNS record through its DNS provider API. Cloudflare, Aliyun, and Scaleway support this. DuckDNS and LightDNS do not, soconfig checkfails if they are used withverify: updater_api.
When updater API verification is active, UDDNS updates if the detected IP
differs from the job's last successful IP, or if the current DNS record returned
by the updater API does not match the detected IP. A matching record on startup
initializes local state without an unnecessary rewrite. auto verifies on
startup, when the provider IP changes, and periodically while stable, querying
only the configured address families. A temporary auto verification failure
does not block an update caused by a changed provider IP. updater_api remains
strict, verifies every cycle, and skips the job's current cycle if verification
fails.
routeros: Reads IP addresses from a MikroTik RouterOS device.endpoint: RouterOS API endpoint.username: RouterOS username.password: RouterOS password.insecure: Skip TLS verification. Optional, defaults tofalse.
ip_service: Reads the public IP from external services.- Supported services:
ip.fm,ifconfig.me,ip.sb,3322.org. - Only public, globally routable addresses are accepted.
- Supported services:
netif: Reads IP addresses from a local network interface.name: Network interface name.
cloudflare:apitoken: Cloudflare API token.emailandapikey: Alternative Cloudflare API key authentication.domain: DNS record to update, for exampleddns.example.com.zone: Optional DNS zone, for exampleexample.co.uk.proxy: Optional HTTP or HTTPS proxy.
aliyun:accesskeyid: Aliyun access key ID.accesskeysecret: Aliyun access key secret.domain: DNS record to update.zone: Optional DNS zone, for exampleexample.co.uk.regionid: Optional, defaults tocn-hangzhou.
duckdns:token: DuckDNS token.domain: DuckDNS subdomain without.duckdns.org.
lightdns:key: LightDNS DDNS key.domain: DNS record to update.
scaleway:access_key: Scaleway access key.secret_key: Scaleway secret key.project_id: Scaleway project ID.domain: DNS record to update.zone: Optional DNS zone, for exampleexample.co.uk.ttl: Optional DNS record TTL in seconds, defaults to150.
telegram:token: Telegram bot token.chat_id: Telegram chat ID.proxy: Optional HTTP or HTTPS proxy.
discord:url: Discord webhook URL.id: Discord webhook ID. (Alternatively tourl)token: Discord webhook token. (Alternatively tourl)proxy: Optional HTTP or HTTPS proxy.
Cloudflare, Discord and Telegram proxy values must be absolute URLs with a host. URL
userinfo credentials are supported; paths other than /, queries, and
fragments are rejected.
Run directly:
uddns -c /etc/uddns.yamlCheck configuration without starting the updater:
uddns config check -c /etc/uddns.yamlOr run in the background:
nohup uddns -c /etc/uddns.yaml > uddns.log 2>&1 &The default update interval is 30s. UDDNS_INTERVAL accepts Go duration
strings from 10s through 24h; invalid or out-of-range values emit a warning
and fall back to 30s:
UDDNS_INTERVAL=5m uddns -c /etc/uddns.yamlLogging can be configured in uddns.yaml:
logging:
level: info
dir: /var/log/uddns
retention_days: 7level:debug,info,warn, orerror. Default:info.dir: Enables file logging when set. If omitted, logs only go to stdout.retention_days: Number of calendar days to keep, including today. Default:7.
File logs rotate by local calendar date and are named:
uddns-YYYY-MM-DD.log
Environment variables override config-file logging values:
UDDNS_LOG_LEVELUDDNS_LOG_DIRUDDNS_LOG_RETENTION_DAYS
UDDNS creates managed log directories with mode 0700 and log files with mode
0600. Symlinks and non-regular log targets are rejected.
See CHANGELOG.md for release history and unreleased changes. Chinese release notes are available in CHANGELOG.zh-CN.md.
- Add more providers.
- Add more updaters.
- Add granular configuration options.
- Add Dockerfile.
- Add daemon mode beyond systemd installation.
- Add sensible logging.
- Add tests.
- Add CI/CD release workflow.
- Add curl installer.
- Add systemd service installer.
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Maintainers should follow the release-signing runbook before creating a release.
MIT