TLDR
- Created a Mythic Beasts DNS API key (“Dynamic DNS”) with permits to update
@andwwwA records forcainappleby.net. - Stored the API credentials in
~/.netrc, then confirmedcurl -n -X POSTcorrectly updated the@record via Mythic Beasts’ DDNS endpoint. - Wrapped the
curlcall in adynamicDns.shscript and exposed it via amythic-ddns.serviceoneshot unit. - Added a
mythic-ddns.timerunit to run once a minute after boot and then every 5 minutes. - Hit a “No credentials supplied” error because systemd was running as root and looking at
/root/.netrc, then fixed it by running the service as my user with the correct working directory. - Enabled the timer, confirmed the timer → service → script chain works, and verified each run and API response via
journalctl.
Task Requirements
- Use Mythic Beasts (current DNS provider) to keep the domain
cainappleby.netpointing at my current public IPv4 address. - Specifically allow DDNS updates for the
@andwwwA records without deleting the existing records. - Automate the DDNS API call on the Pi using systemd timers and services rather than cron.
- Ensure logs for the DDNS updates are accessible via
journalctl, relying on Ubuntu Server’s built-in log rotation and caps. - Keep the setup simple: one shell script, one service unit, one timer unit.
Goal
I wanted the A records for cainappleby.net (both @ and www) to track my ISP’s changing IPv4 automatically so the site stays reachable, with the Pi periodically calling the Mythic Beasts DDNS API instead of me manually editing DNS or living with stale records.
Implementation Steps
-
I logged into Mythic Beasts (which already hosts my domain) and created a DNS API key named
Dynamic DNS. I added two permits: one for hostname@in zonecainappleby.netof typeA, and one for hostnamewwwin the same zone and type, so the key can update both relevant A records. -
From the Mythic Beasts documentation I confirmed that their Dynamic DNS API updates existing records rather than creating new ones, so I left my current A records in place instead of deleting anything.
-
Following the docs, I created a
.netrcfile in my home directory withnano .netrc, using the precise format they specify socurlcan pick up the credentials from$HOME:machine ipv4.api.mythic-beasts.com login KEYID password SECRETThe key point here is that it lives in my home directory because
curllooks there by default when using-n. -
I initially referenced the documentation example:
curl -n -X POST https://ipv4.api.mythic-beasts.com/dns/v2/dynamic/myhost.example.comthen made the real call for my domain as recommended:
curl -n -X POST https://ipv4.api.mythic-beasts.com/dns/v2/dynamic/cainappleby.netThe response came back as JSON:
{"message":"Dynamic DNS for @ set to IPv4 REDACTED"}which confirmed that
.netrcwas wired correctly, the API key had the right permits, and a single POST updates the@A record to my current public IPv4. -
To avoid typing the
curlcommand manually, I placed it into a small shell script in my home directory. The script is just a file of terminal commands, but I explicitly declared bash so the system knows how to execute it:#!/bin/bash curl -n -X POST https://ipv4.api.mythic-beasts.com/dns/v2/dynamic/cainappleby.netI saved this as
dynamicDns.sh(camel casing just for readability). -
I decided to use systemd on the Pi (the system’s service manager) as an easy and reliable way to auto-call the API. The basic structure I wanted was: systemd timer → systemd service → shell script → DDNS API.
-
For the service unit, I went into
/etc/systemd/system/(where services live) and createdmythic-ddns.serviceusingnano mythic-ddns.service. The initial unit looked like this:[Unit] Description=Service to run dynamicDns.sh in home directory. [Service] Type=oneshot ExecStart=/home/santigold/dynamicDns.sh [Install] WantedBy=multi-user.targetType=oneshotfits because it’s a single POST that starts and finishes with a clear return, andExecStartpoints at the shell script with thecurlcommand. -
I knew systemd services log to the journal, so whatever the script prints (including the JSON from
curl) ends up accessible viajournalctl. For example, I can view logs for the service with:journalctl -u servicenameand Ubuntu Server will handle log rotation and caps, so log bloat isn’t a concern here.
-
With the service sketched out, I created the matching timer. Still in
/etc/systemd/system/, I rannano mythic-ddns.timerand used:[Unit] Description=Timer to run service mythic-ddns.service [Timer] OnBootSec=1min OnUnitActiveSec=5min [Install] WantedBy=multi-user.targetThe key settings are
OnBootSec=1minfor the first run one minute after boot, andOnUnitActiveSec=5minto rerun the service every five minutes after a successful run. -
Before trusting the whole chain, I tested the basic setup. First I ran the
.shfile manually to confirm it still worked. Then I reloaded systemd so it would see the new service and timer:sudo systemctl daemon-reloadAfter that I manually started the service:
sudo systemctl start mythic-ddns.serviceand checked the logs with:
journalctl -u mythic.ddns.serviceThe response from the service was:
{"error":"No credentials supplied","status":"Error"}which meant the script wasn’t finding the
.netrccredentials. -
I traced the issue to how systemd was running the service: because it was running as root, it looked for
/root/.netrcinstead of the.netrcin my home directory. To fix this cleanly, I made the service run as my user and set the working directory to my home:[Service] Type=oneshot User=santigold WorkingDirectory=/home/santigold ExecStart=/home/santigold/dynamicDns.shI edited the unit with
nano, then ran:sudo systemctl daemon-reload sudo systemctl start mythic-ddns.service journalctl -u mythic-ddns.service -n 10I ignored the older failed entries and just checked the latest log lines, which now showed a successful run.
-
With a working service, the final step was to enable the timer so it actually fires on schedule. I ran:
systemctl enable mythic-ddns.timerand, because I wasn’t rebooting the Pi, I started it immediately:
systemctl enable --now mythic-ddns.timerAfter waiting five minutes, I confirmed the timer had triggered, the service had run, and the A record update via the DDNS API worked perfectly. The final flow is:
Timer -> Service -> dynamicDns.sh -> curl -> Mythic Beasts DDNSwith everything logged in the journal.
Notes & Decisions
- I stuck with Mythic Beasts’ own Dynamic DNS API because they already host my DNS and provide clear guidance on using
.netrcand the DDNS endpoint. - I deliberately left the existing
@andwwwA records in place because the DDNS API updates them; deleting them would have broken this behaviour. - I chose systemd (service + timer) over cron so I get nicer integration with the Pi’s service manager and easy log access via
journalctl. - Relying on
.netrcinstead of passing credentials on the command line keeps secrets out of the unit files and logs while matching Mythic Beasts’ recommendations. - Running the service as my own user with a proper
WorkingDirectorysolved the.netrclookup problem and avoids unnecessary root access for this task.
Next Ideas / Follow-ups
None captured yet.