Some of the services I host are hosted behind dynamic DNS. There are lots of services to automatically update the dns records if the IP changes, but most of them are not free or require regular confirmation of the domain.

I wanted to have a solution that is as standard as possible, so ideally without any CNAME aliases pointing to a subdomain of a DDNS provider.

Luckily, Hetzner has a free DNS server hosting with a nice API. So what I ended up doing was regularly sending requests to the Hetzner DNS API to update the IP-address To reduce the amount of requests going to hetzner, the request is only sent when the IP really changed. The IP can be fetched from an external service like Ipify or a very simple selfhosted service if you have another server that is reachable on the internet.

The result is a simple daemon that automates all of that. My implementation is written in Rust, but it’s very small and would be easy to write using other languages too. If you want to use it, you can find it on Codeberg. The config format for it is a simple toml file that goes into /etc/hetzner-ddns/config.toml The skeleton config file looks like this:

update_interval = 30

[auth]
token = ""

[record]
id = ""
name = ""
zone_id = ""

Of course the empty strings need to be replaced with real data which you can get from the Hetzner API or the web interface. The API documentation contains examples on how to query the API using curl.

If you use systemd, you can use the following unit to run the service on a separate ddns user, which you need to create before.

[Unit]
Description=Hetzner DNS update
After=network-online.target

[Service]
Type=simple
Restart=on-failure
ExecStart=/opt/bin/hetzner-ddns
ExecReload=/bin/kill -USR1 $MAINPID
User=ddns

[Install]
WantedBy=multi-user.target

Btw, because I know some people were not too happy with this blog being hosted on GitHub, it is now also hosted on my own server (behind DDNS). If the uptime ends up being good enough, I’ll keep it this way.