# Keep a name on a changing address with Dynamic IP A home line, an office connection or a server behind NAT gets a new public address now and then. **Dynamic IP** keeps DNS records pointing at it: something reports the current address, and InfraNest updates every record the source keeps, with a history, an alert when the address changes, and a `dynamic_ip.changed` event your automations and webhooks receive. A source can also keep a firewall rule open to that address only. Create the source in the app under **Automations → Dynamic IPs**, attach the records it should keep, and copy its update password. Then report the address one of three ways: | You have | Use | Credential | |---|---|---| | A router, `ddclient`, a cron job | [the update endpoint](#the-update-endpoint) | the source's own update password | | A script on the connection itself | [check in from your own address](#check-in-from-your-own-address) | an API token | | A script that knows the new address | [set the address](#set-the-address) | an API token | **Runnable code:** [`infranest-examples/examples/dynamic-ip`](https://github.com/InfraNest-Infrastructure-Organized/infranest-examples/tree/main/examples/dynamic-ip), run against the [sandbox](../sandbox.md) by CI. ## The update endpoint The update endpoint speaks **dyndns2**, the protocol routers and `ddclient` already know. It needs no API token: each source has its own update password, which can do this one thing and nothing else. ``` https://dyn.infranest.io/ password in HTTP Basic auth (recommended) https://dyn.infranest.io/ password in the path https://dyn.infranest.io/nic/update the dyndns2 path routers append; /v3/update and /update work too ``` Send the password as the Basic-auth **password** and the source's public id as the **username**, so it stays out of shell history, proxy logs and referrers. Without an address in the request, the address the request comes from is used — usually what you want. | Parameter | Meaning | |---|---| | `myip` or `ipv4` | Report this IPv4 address instead of the one the request comes from | | `myipv6` or `ipv6` | Report this IPv6 address | | `hostname` | Accepted and ignored: the source decides which records it keeps | | Answer | Status | Meaning | |---|---|---| | `good 198.51.100.7` | 200 | The address changed; records and firewalls are being updated | | `nochg 198.51.100.7` | 200 | Already this address; nothing to do | | `badauth` | 401 | No source has this password. The same answer whether or not the username exists | | `nohost` | 422 | Authenticated, but the request carried no usable address | At most 60 requests a minute from one address. A router that calls every few minutes is well inside that. ### curl, from cron ```sh # Every 5 minutes. The address is the one this request comes from. # /etc/infranest-dyn.env (chmod 600): DYN_USER= DYN_PASSWORD= */5 * * * * . /etc/infranest-dyn.env && curl -fsS -u "$DYN_USER:$DYN_PASSWORD" https://dyn.infranest.io/ >/dev/null ``` To report both families from a dual-stack connection, call it once over each: ```sh curl -4 -fsS -u "$DYN_USER:$DYN_PASSWORD" https://dyn.infranest.io/ curl -6 -fsS -u "$DYN_USER:$DYN_PASSWORD" https://dyn.infranest.io/ ``` ### ddclient ``` protocol=dyndns2 server=dyn.infranest.io ssl=yes # login: the source's public id; password: its update password login=dyn_PUBLIC_ID password='change-me' your-hostname.example.com ``` The last line is required by `ddclient` and ignored by InfraNest. ### A systemd timer ```ini # /etc/systemd/system/infranest-dyn.service [Service] Type=oneshot EnvironmentFile=/etc/infranest-dyn.env # DYN_USER=… DYN_PASSWORD=… (chmod 600) ExecStart=/bin/sh -c 'curl -fsS -u "$DYN_USER:$DYN_PASSWORD" https://dyn.infranest.io/' ``` ```ini # /etc/systemd/system/infranest-dyn.timer [Timer] OnBootSec=1min OnUnitActiveSec=5min [Install] WantedBy=timers.target ``` `systemctl enable --now infranest-dyn.timer`. ## Check in from your own address With an API token instead of the update password — a script that already holds one — check in, and the address the request comes from becomes the source's: ```sh curl -fsS -X POST https://api.infranest.io/api/organizations/42/dynamic-ips/7/check-in \ -H "Authorization: Bearer $INFRANEST_TOKEN" -H "Accept: application/json" ``` ```json { "ip": "198.51.100.7", "family": "v4" } ``` ## Set the address A script that knows the new address — a deploy step, a cloud hook — sets it: ```sh curl -fsS -X PUT https://api.infranest.io/api/organizations/42/dynamic-ips/7/address \ -H "Authorization: Bearer $INFRANEST_TOKEN" -H "Accept: application/json" \ -H "Content-Type: application/json" -d '{"ipv4": "198.51.100.7"}' ``` ```json { "changed": true, "applied": 1, "current_ipv4": "198.51.100.7", "current_ipv6": null } ``` `ipv4`, `ipv6` or both. Everything a router check-in does follows: the records, the history, the alert and the event. `changed` is `false` when it was already this address. **Token:** Monitoring → Write (`monitoring.write`) — dynamic IPs sit under the Monitoring scope. A token can also attach and detach DNS records, edit the label, alerts and tags, recheck, follow a server's agent, and delete a source; see [the reference](../reference.md). ::: warning A source that keeps a firewall open An API token may not change a source that keeps a **firewall rule** up to date: a new address would change who can reach the server. Those answer `403 not_token_writable`. Report their address through the update endpoint — its password is made for exactly this one source — or change them in the app. Creating a source and rotating its update password are app-only too: each makes a credential that outlives your API token. ::: ## Which to choose Use the update endpoint for anything that runs on the connection itself, and set the address with a token when something else knows it. Rewriting a DNS record directly, as in [Update DNS from a script](dynamic-dns-from-a-script.md), also works, but skips the history, the change alert and the firewall.