Dynamic DNS for Gandi-hosted domains

If you're trying to host a website, or any other Internet-facing service, from a home network, there's a high chance you'll be faced with a common network problem. As most Internet service providers do not guarantee the stability of public IP addresses to small customers, the DNS record tying this IP to your domain name may turn out of date without notice, leading to your service becoming unavailable through the usual, domain-based URL.

The usual way to address (lol) this issue is setting up a dynamic DNS solution, which shall update your DNS records whenever your ISP changes your public IP. While some ISP routers integrate such solutions, others do not, and you're left with setting up things by yourself.

At this point, working around dynamic IP addresses will depend on your DNS hosting service. For many years now, I've been using Gandi as my main DNS registrar, which doubles as a DNS host (as most registrars do). Gandi provides an API which can be leveraged in order to update DNS records automatically, effectively acting as a dynamic DNS solution.

First things first, get your (Linux-based) system ready with the commands below. The only required package is the common curl. You need root access to set up the working directory, but use chown afterwards to downgrade permissions, since the script does not need root privileges to run.

sudo apt update && sudo apt install curl
sudo mkdir /opt/dyndns_gandi
sudo chown <username>:<usergroup> /opt/dyndns_gandi

Then, copy the following script into the new directory, as /opt/dyndns_gandi/dyndns_gandi.sh.

#!/bin/bash

# Get public IP address and check if it has changed
EXT_IP_LK_FILE="/opt/dyndns_gandi/last_known_ip"
EXT_IP=$(curl -s https://ifconfig.me)
if [ -f "$EXT_IP_LK_FILE" ] && [ $(cat $EXT_IP_LK_FILE) == $EXT_IP ]; then
return 0
exit 0
fi
echo "$EXT_IP" > $EXT_IP_LK_FILE

# Gandi LiveDNS API KEY + Gandi-registered domain
API_KEY="XXXXXXXXXXXXXXXXXXXXXXXXX"
DOMAIN="my-gandi-domain.com"

# Update the A Record of the domain's root (name '@')
curl -X PUT \
https://api.gandi.net/v5/livedns/domains/$DOMAIN/records/@/A \
-H "Authorization: Apikey $API_KEY" \
-H "Content-Type: application/json" \
-d "{\"rrset_values\": [\"$EXT_IP\"]}"

Should you find these lines cryptic, here's a more detailed version of what happens. We retrieve our public IP address from https://ifconfig.me, a free service operated by IPinfo (it seems as good as a third party gets). Then we compare it to the last IP address we stored: either they match and there's nothing to do, or the address has changed and we need to replace the stale DNS record.

Next, we register our secret Gandi API key, along with our own domain name. If you don't have one yet, you can retrieve a fresh API key by browsing the Security submenu of your Gandi account settings. Now we're ready for the curl API call which will update the DNS record on Gandi's side.

The specific endpoint is documented here. Note that you could change A to AAAA if you need IPv6 instead of IPv4, or change @ to www for targeting a subdomain instead of the root domain, etc.

Finally, since we want to counter unpredictable IP changes, the script should run periodically. A 30-minute interval makes for a reasonable balance between API consumption and non-commercial service availability. We can declare such a job by adding this line to crontab -e:

*/30 * * * * /bin/bash /opt/dyndns_gandi/dyndns_gandi.sh

And that's it! This small script has been running on two Raspberry Pis at home, and also on the server hosting this very website. It might be the most handy personal tool I've been using with my setups. Hopefully you'll find it helpful, or you've learnt something along the way!