legitdns.
FeaturesUse casesDocsSign inGet started

Client guides

The clients we have tested against legitdns ourselves - sent real updates, checked the answers in DNS, and checked our own history. That is what "supported" means here, and the list is short on purpose:

  • legitdns-update - our own shell script (below).
  • python_update.py - our own Python script (below).
  • ddclient 3.10.0 - with one IPv6 warning, in its section.
  • inadyn 2.11.0 - full IPv4 and IPv6.
  • curl 8.5.0 - for one-liners and your own scripts.

Routers are a separate page and are not tested yet, so the router guides say "should work" and mean it.

On a Windows machine instead? The Windows guide has a PowerShell script that Task Scheduler runs every few minutes, and the Windows clients that can be pointed at a custom server.

On a Mac instead? legitdns on macOS runs a small script on a launchd schedule with nothing to install, and covers the Homebrew route for ddclient.

Every guide uses the example hostname mypi.pihome.example. Use your own hostname and token.

Our shell script: legitdns-update

The standard way to keep a Pi or small server updated. One POSIX shell file; needs only curl. It discovers the device's public addresses via checkip, sends only on change, and stays silent under cron when there is nothing to say.

Install

Download it from the site, then install it:

curl -O https://legitdns.com/client/legitdns-update
sudo install -m 755 legitdns-update /usr/local/bin/legitdns-update

Configure

Create ~/.config/legitdns/legitdns.conf (or /etc/legitdns/legitdns.conf system-wide) and protect it - it contains a token:

mkdir -p ~/.config/legitdns
cat > ~/.config/legitdns/legitdns.conf <<'EOF'
HOSTNAME=mypi.pihome.example
TOKEN=YOUR-TOKEN
IPV4=auto
IPV6=auto
EOF
chmod 600 ~/.config/legitdns/legitdns.conf

The settings:

  • IPV4 / IPV6 - auto (discover and publish), off (never touch that family), or a fixed address.
  • API_BASE - defaults to https://legitdns.com. Plain HTTP to a non-local address is refused: the token travels with the request.
  • CHECKIP4_URL / CHECKIP6_URL - discovery endpoints; default is our checkip page, asked over the matching family.

The script keeps its last published addresses in a state file next to the config and sends nothing while they match. --force sends anyway (the service answers nochg when nothing changed).

Run it from cron

*/5 * * * * /usr/local/bin/legitdns-update --quiet

Exit codes: 0 = fine, 1 = the update or a self-update failed, 2 = the config is wrong. Errors go to stderr even in quiet mode; successes print nothing with --quiet.

Keeping the script itself current

The script can replace itself with the newest published copy:

legitdns-update --self-update

HTTPS only; the download is checked against the published SHA-256 checksum and parsed as shell before an atomic swap. Any failure keeps the current copy and exits non-zero. It never installs an older version and never touches your config or state. A weekly cron line is the intended setup:

17 4 * * 1 /usr/local/bin/legitdns-update --self-update --quiet

Our Python script: python_update.py

The same tool for machines where Python is handier than shell: pure Python 3 standard library, nothing to install. Same config file, same keys, same state file, exit codes, and --self-update behaviour.

python3 python_update.py --config ~/.config/legitdns/legitdns.conf

It also has a one-shot mode for quick tests:

python3 python_update.py https://legitdns.com mypi.pihome.example YOUR-TOKEN 192.0.2.44 2001:db8::44

(- in place of an address means "do not send that family".)

ddclient

ddclient 3.10.0 works with legitdns for IPv4, including web discovery of the address. IPv6 has a real limitation - read that part before relying on it.

Install on Debian/Ubuntu:

sudo apt install ddclient libio-socket-inet6-perl

(The Perl module is required for any IPv6 use on Debian/Ubuntu; without it ddclient fails with a module error.)

IPv4

/etc/ddclient.conf:

daemon=300
syslog=yes
pid=/var/run/ddclient.pid
ssl=yes
protocol=dyndns2
server=legitdns.com
login=mypi.pihome.example
password=<redacted>
usev4=webv4
webv4=https://legitdns.com/checkip
mypi.pihome.example

ddclient asks our checkip page for the address (over IPv4, so it learns the IPv4 address) and publishes changes as they happen. This discovery form is tested and works, as do ddclient's no-change and wrong-token behaviour.

To supply the address yourself instead (from a wrapper script, say), the other tested form writes it into the config: replace the usev4/webv4 lines with usev4=ipv4 and ipv4=192.0.2.44.

IPv6 - the honest warning

ddclient 3.10.0 cannot discover and publish an IPv6 address: its modern usev6 mode - including usev6=webv6 web discovery - fetches the address but then sends an empty one. That is a ddclient bug, not something a config tweak fixes; we reproduced the failure ourselves to describe it exactly.

The one ddclient form that does publish IPv6 is the legacy one, with the address written in:

daemon=300
syslog=yes
ssl=yes
protocol=dyndns2
server=legitdns.com
login=mypi.pihome.example
password=<redacted>
use=ip
ip=2001:db8::44
mypi.pihome.example

Works when the IPv6 address is stable, or something else rewrites the config when it changes. If your IPv6 address changes on its own, use inadyn or our script for IPv6 (or for both families).

inadyn

inadyn 2.11.0 is the fullest third-party client we tested: IPv4 and IPv6 both work, in one config. Install it (sudo apt install inadyn on Debian/Ubuntu), then /etc/inadyn.conf:

# The IPv6 line is required - without it inadyn
# refuses its own IPv6 results.
allow-ipv6 = true

custom legitdns4 {
    username = mypi.pihome.example
    password = <redacted>
    ssl = true
    checkip-command = "curl -s https://legitdns.com/checkip"
    ddns-server = legitdns.com
    ddns-path = "/nic/update?hostname=%h&myip=%i"
    hostname = mypi.pihome.example
}

custom legitdns6 {
    username = mypi.pihome.example
    password = <redacted>
    ssl = true
    checkip-command = "curl -6 -s https://legitdns.com/checkip"
    ddns-server = legitdns.com
    ddns-path = "/nic/update?hostname=%h&myipv6=%i"
    hostname = mypi.pihome.example
}

One block per address family: the -6 in the second block's checkip command is what makes it learn the IPv6 address. Both blocks are tested in this form, including no-change and wrong-token behaviour. Drop the second block if your connection has no IPv6.

Run inadyn as your distribution's service, or check a config once by hand with:

inadyn -f /etc/inadyn.conf -n -1 -l debug

One trap: do not give inadyn a checkip server with a port (checkip-server = host:port) - 2.11.0 drops the port silently and then cannot reach the server. The checkip-command form above has no such quirk and is the one to use.

One maintenance note: the maker archived the inadyn project in October 2025, so it is no longer maintained upstream. Version 2.11.0 above is the tested one and works as described.

curl

For a quick test, one command is the whole client. With no address in it, the update publishes the address you connect from (details in the API reference):

curl -H "Authorization: Bearer YOUR-TOKEN" \
  "https://legitdns.com/api/v1/update?hostname=mypi.pihome.example"

From cron, wrap it to send only on change - or use the shell script above, which is that wrapper, finished.

See also: Update methods, DynDNS2 protocol, IPv6 prefix mode, Troubleshooting.

legitdns - free dynamic DNS for home serversEULAPrivacyStatusReport abuseFeedback