Dynamic DNS for a domain or subdomain (cPanel as the worked instance) (Home Port Forward Hosting)

**Note** from Bead: Home Port Forward Hosting · [canonical source](https://redfish.acequia.io/guerin/.agents/7049c694-e45b-4116-a887-49e64885ac8e/2026-06-13/notes/03-dynamic-dns.md) · session 2026-06-13 · discussion: Talk: Home Port Forward Hosting

**Date:** 2026-06-13. Part of the home-port-forward runbook ([00](00-home-port-forward-hosting.md)). Answers: *how do you keep a domain/subdomain's A record pointed at a home machine whose ISP IP drifts?*

## The layering (what dyndns actually touches) ``` Registrar (GoDaddy) owns the delegation — which nameservers are authoritative │ NS delegation (rarely changes) ▼ Authoritative NS (HostGo) dns1/2/3.usethisnameserver.com (== HostGo; ns1/ns2.hostgo.com also answer) │ holds the ZONE ▼ Zone records A <name> -> <ip> ◄── dyndns edits ONLY this one record ``` **Dynamic DNS edits a single A record in the zone.** It never touches the registrar delegation or the NS. "Domain vs subdomain" is just *which hostname* the A record is for — apex (`stephenguerin.live`), `www`, or a subdomain (`wiki.harvardviz.live`). Same mechanism either way; you choose the name when you create the dyndns entry. Keep that record's **TTL low (300s)** so an IP change propagates within minutes.

## Mechanism 1 — cPanel "Dynamic DNS" webcall (RECOMMENDED; the worked instance here) cPanel has a first-class **Dynamic DNS** feature that issues a **webcall URL**. Hitting that URL sets the chosen record's A to **the caller's public IP**. This is exactly what is already running for `wiki.harvardviz.live`: ``` Scheduled Task "update wiki.harvardviz.live dyndns": curl.exe -s https://harvardviz.live/cpanelwebcall/dhfxahqvzqerwdkqsgmvaqvfxqnhqufk trigger: daily; principal: steph (S4U) ``` ### How it works - In cPanel → **Dynamic DNS**, you add an entry for a domain/subdomain you control; cPanel creates/owns its A record and mints a unique URL `https://<domain>/cpanelwebcall/<opaque-token>`. - **GET that URL → cPanel sets that record's A to the requester's source IP.** So it must run **from the home machine** (the box whose IP you want published). - It is a normal HTTPS GET to the public site (port 443), **NOT** the cPanel UAPI on `:2083`. So it is **not blocked by Imunify360** (see Mechanism 2's caveat). Token in the URL is the credential — treat the URL as a secret. ### Setting it up for `stephenguerin.live` (to mirror wiki) 1. In the cPanel account that hosts the `stephenguerin.live` zone → **Dynamic DNS** → add `stephenguerin.live` (and optionally `www`). Copy the webcall URL. 2. Store the URL as a credential (e.g. `.credentials/stephenguerin-live-dyndns.json`) — it is secret. 3. Create a Windows Scheduled Task mirroring the wiki one: `curl.exe -s <webcall-url>`, trigger **at startup + every 15 min** (more frequent than wiki's daily, because the origin is now a live web host, not a camera). Run as the user, S4U, no stored password needed. 4. Verify: `nslookup -type=A stephenguerin.live ns1.hostgo.com` should equal the current public IP (`https://api.ipify.org`). > Today the `stephenguerin.live` A record already equals the home IP (set 2026-06-13). The webcall task makes it *self-healing* across ISP IP changes; without it, an IP change silently darkens the site until manually re-pointed.

## Mechanism 2 — cPanel UAPI zone edit (alternative; Imunify360 caveat) A script can read the home IP and PATCH the A record directly via UAPI: - Auth header `Authorization: cpanel <user>:<token>` (token in `.credentials/acequia.io-cpanel-token.json`) against `https://guerin.acequia.io:2083/execute/DNS/...` (use `guerin.acequia.io`, not `acequia.io`, which is Cloudflare-proxied and blocks `:2083`). - Functions: newer `DNS::mass_edit_zone` (serial + edit array), older `ZoneEdit::edit_zone_record`. Read first with `DNS::parse_zone`. - **CAVEAT (verified 2026-06-13): Imunify360 bot-protection blocks UAPI from the home IP** — `{"message":"Access denied by Imunify360 bot-protection…"}`. To use this path, **whitelist the home IP** in cPanel/WHM Imunify360, or run from an already-whitelisted host. **Prefer Mechanism 1**, which sidesteps this entirely.

## Caddy / cert interaction Caddy binds to the *host*, not the IP, and its Let's Encrypt cert is tied to the *domain*, not the IP — so an IP change needs **no** Caddy action. The only outage from an IP change is the DNS-propagation window before the next dyndns run; low TTL + a frequent webcall task minimizes it.

## INCIDENT 2026-06-14 — the predicted IP-rotation outage happened Overnight the ISP rotated the WAN IP and `stephenguerin.live` went **publicly unreachable** — the exact failure this note predicted, because no dyndns updater was ever installed. ``` local origin 127.0.0.1:3500 : 200 (home stack healthy: Caddy + origin service both Running) current public IP : 50.243.20.254 (rotated overnight) DNS A record stephenguerin : 174.56.31.65 (STALE — yesterday's IP) public https://stephenguerin : 000 (timeout) ``` Could **not** self-fix: cPanel UAPI still **Imunify360-blocked** from the home IP (Mechanism 2 dead), and no Dynamic-DNS **webcall** had been created for `stephenguerin.live` yet (Mechanism 1 not provisioned). So the fix required Stephen in the cPanel UI: Zone Editor → set `stephenguerin.live` (+`www`) A → the current IP, TTL 300. **Durable fix = provision the Mechanism-1 webcall + schedule it** (boot + every 15 min); the Windows Scheduled-Task half needs no API and was offered. Lesson: standing up the host is not done until the **dyndns updater is installed** — an internet-facing residential host without it has a guaranteed-eventual outage.

## GAP — registry automation (not yet built) Dynamic DNS automates the *record*, but standing up a **new** home-hosted domain still has a **manual registry layer** above the zone. To fully automate "register a domain and serve it from home," the missing pieces are: 1. **Register / transfer the domain** — registrar API. `stephenguerin.live` is at **GoDaddy**; GoDaddy exposes a [Domains API](https://developer.godaddy.com/) (OTE + production; API key/secret). Domains *registered through HostGo* could instead be driven via the **HostGo WHMCS API** (`portal.hostgo.com`, see [hostgo-domains.md](https://redfish.acequia.io/) §WHMCS API) for register/renew/transfer. 2. **Set delegation NS** at the registrar (to HostGo's `usethisnameserver.com` set) — registrar API. 3. **Create the cPanel zone / add-on domain** — cPanel UAPI (`DomainInfo`/`SubDomain`/add-on domain) or WHM (`createacct`/`create_domain`), so the zone exists to hold records. (Imunify360 caveat applies to scripted cPanel calls.) 4. *Then* the already-solved steps: dyndns entry (Mechanism 1) → router port-forward (already open) → Caddy route → origin. **Status:** steps 1–3 are currently **manual** (GoDaddy dashboard + cPanel UI). Automating them — a "provision a home-hosted domain end to end" skill wrapping registrar API + cPanel zone create + dyndns webcall mint + Caddy route push — is an open gap worth a dedicated skill later. Credentials needed that we do **not** yet hold: a GoDaddy API key/secret (and/or HostGo WHMCS API credentials). Flagged here so the next builder sees the boundary.