Ein weiteres Tutorial von mir; Ich betreibe seit Januar einen Server daheim wo meine Nextcloud, mein Blog und andere Dinge drauf laufen und auch öffentlich erreichbar sein sollen. Ich könnte natürlich auch die DynDNS Domain der Fritzbox nutzen oder einen unterstützten DynDNS Anbieter, aber die Domain ist dann nicht ganz so schick. Deswegen habe ich mir den Weg über den Hetzner DNS Server überlegt. Netterweise bietet Hetzner eine API für solche sachen an. So spart man sich den Weg jedesmal in die DNS Konsole gehen zu müssen. Auch ein Anlegen neuer Domains/Subdomains ist darüber möglich.
Aus diesem Grunde die folgende Anleitung mit dem passenden Skript.
Das Skript ist so geschrieben dass es keine unnötigen Änderungen und API‑Calls macht. Mit diesem Bash‑Skript prüfst du zuerst, ob der bestehende A‑ oder AAAA‑Record schon zur aktuellen IP passt. Nur wenn sich die IP tatsächlich geändert hat, wird der alte Record gelöscht und ein neuer angelegt.
Warum automatische DNS‑Updates?
Viele Internet‑Anbieter vergeben dynamische IP‑Adressen. Wenn sich deine IPv4 oder IPv6 ändert, ist deine Domain nicht mehr erreichbar – frustrierend wenn man permanent etwas verfügbar machen möchte. Ein automatischer DNS‑Update sorgt dafür, dass deine Domain immer auf die richtige Adresse zeigt, ohne dass du manuell eingreifen musst.
Das Skript muss auf dem Server laufen, der am Router angeschlossen ist. In meinem Fall habe ich das Skript im Pfad /usr/local/bin abgelegt.
So funktioniert das Skript
-
IP abrufen
Das Skript liest deine aktuelle IPv4 und IPv6 per curl von externen Diensten aus (z.B. ipv4.icanhazip.com). -
Zone‑ID ermitteln
Für jede Domain holt es sich per Hetzner‑API die passende Zone‑ID. -
Bestands‑Check
Es liest die vorhandenen A‑ bzw. AAAA‑Einträge aus und vergleicht deren Wert mit der aktuellen IP. -
Löschen nur bei Abweichung
Passt der Eintrag bereits, bleibt er bestehen und das Skript überspringt ihn. Weicht er ab oder fehlt er, löscht das Skript alle alten Einträge dieses Typs und legt einen neuen an. -
TTL und Performance
Mit einem niedrigen TTL‑Wert (z.B. 60 Sekunden) erreichen Änderungen schnell alle DNS‑Server. Gleichzeitig sparst du API‑Calls, weil nur bei echten Änderungen gearbeitet wird.
Abhängigkeiten installieren
Damit das Skript funktionieren kann, müssen 2 Abhängigkeiten installiert werden, nämlich jq und curl.
sudo apt update && sudo apt install curl jqDas Skript
#!/bin/bash set -euo pipefail # Hetzner API-Key HETZNER_API_KEY="SomeSecretKeyHetzner" HETZNER_API_URL="https://dns.hetzner.com/api/v1" # Domains mit A/AAAA-Records DOMAINS=( "domain.tld" "domainb.tld" "sub.domain.tld" ) # CNAME-Zuordnungen declare -A CNAME_RECORDS=( ["www.domain.tld"]="domain.tld" ["www.domainb.tld"]="domainb.tld" ) # Aktuelle öffentliche IP-Adressen CURRENT_IPV4=$(curl -s https://ipv4.icanhazip.com) CURRENT_IPV6=$(curl -s --max-time 5 https://ipv6.icanhazip.com || true) # Funktion zum Löschen eines DNS-Records delete_record() { local RECORD_ID=$1 if [[ -n "$RECORD_ID" && "$RECORD_ID" != "null" ]]; then echo "🗑 Lösche Eintrag: $RECORD_ID" curl -s -X DELETE "$HETZNER_API_URL/records/$RECORD_ID" \ -H "Auth-API-Token: $HETZNER_API_KEY" > /dev/null fi } # Funktion zum Aktualisieren von A- und AAAA-Records update_domain() { local DOMAIN=$1 local MAIN_DOMAIN=$(echo "$DOMAIN" | awk -F'.' '{print $(NF-1)"."$NF}') local SUBDOMAIN=${DOMAIN/.$MAIN_DOMAIN/} [[ "$SUBDOMAIN" == "$DOMAIN" ]] && SUBDOMAIN="@" local ZONE_ID=$(curl -s -H "Auth-API-Token: $HETZNER_API_KEY" "$HETZNER_API_URL/zones" | jq -r ".zones[] | select(.name==\"$MAIN_DOMAIN\") | .id") if [[ -z "$ZONE_ID" ]]; then echo "❌ Zone für $DOMAIN nicht gefunden!" return fi local RECORDS=$(curl -s -H "Auth-API-Token: $HETZNER_API_KEY" "$HETZNER_API_URL/records?zone_id=$ZONE_ID") # A-Record prüfen local RECORD_A=$(echo "$RECORDS" | jq -r ".records[] | select(.name==\"$SUBDOMAIN\" and .type==\"A\")") local EXISTING_IPV4=$(echo "$RECORD_A" | jq -r ".value") local RECORD_ID_A=$(echo "$RECORD_A" | jq -r ".id") if [[ "$EXISTING_IPV4" != "$CURRENT_IPV4" ]]; then delete_record "$RECORD_ID_A" echo "[$DOMAIN] ➡️ Setze neuen A-Record auf $CURRENT_IPV4" curl -s -X POST "$HETZNER_API_URL/records" \ -H "Auth-API-Token: $HETZNER_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"zone_id\": \"$ZONE_ID\", \"type\": \"A\", \"name\": \"$SUBDOMAIN\", \"value\": \"$CURRENT_IPV4\", \"ttl\": 60 }" > /dev/null else echo "[$DOMAIN] ✅ A-Record bereits aktuell ($CURRENT_IPV4)" fi # AAAA-Record prüfen if [[ -n "$CURRENT_IPV6" ]]; then local RECORD_AAAA=$(echo "$RECORDS" | jq -r ".records[] | select(.name==\"$SUBDOMAIN\" and .type==\"AAAA\")") local EXISTING_IPV6=$(echo "$RECORD_AAAA" | jq -r ".value") local RECORD_ID_AAAA=$(echo "$RECORD_AAAA" | jq -r ".id") if [[ "$EXISTING_IPV6" != "$CURRENT_IPV6" ]]; then delete_record "$RECORD_ID_AAAA" echo "[$DOMAIN] ➡️ Setze neuen AAAA-Record auf $CURRENT_IPV6" curl -s -X POST "$HETZNER_API_URL/records" \ -H "Auth-API-Token: $HETZNER_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"zone_id\": \"$ZONE_ID\", \"type\": \"AAAA\", \"name\": \"$SUBDOMAIN\", \"value\": \"$CURRENT_IPV6\", \"ttl\": 60 }" > /dev/null else echo "[$DOMAIN] ✅ AAAA-Record bereits aktuell ($CURRENT_IPV6)" fi fi } # Funktion zum Aktualisieren eines CNAME-Records update_cname() { local SUBDOMAIN=$1 local TARGET=$2 local MAIN_DOMAIN=$(echo "$SUBDOMAIN" | awk -F'.' '{print $(NF-1)"."$NF}') local ZONE_ID=$(curl -s -H "Auth-API-Token: $HETZNER_API_KEY" "$HETZNER_API_URL/zones" | jq -r ".zones[] | select(.name==\"$MAIN_DOMAIN\") | .id") if [[ -z "$ZONE_ID" ]]; then echo "❌ Zone für $MAIN_DOMAIN nicht gefunden!" return fi local RECORDS=$(curl -s -H "Auth-API-Token: $HETZNER_API_KEY" "$HETZNER_API_URL/records?zone_id=$ZONE_ID") local RECORD=$(echo "$RECORDS" | jq -r ".records[] | select(.name==\"$SUBDOMAIN\" and .type==\"CNAME\")") local EXISTING_TARGET=$(echo "$RECORD" | jq -r ".value") local RECORD_ID=$(echo "$RECORD" | jq -r ".id") if [[ "$EXISTING_TARGET" != "$TARGET" ]]; then delete_record "$RECORD_ID" echo "[$SUBDOMAIN] ➡️ Setze neuen CNAME-Record auf $TARGET" curl -s -X POST "$HETZNER_API_URL/records" \ -H "Auth-API-Token: $HETZNER_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"zone_id\": \"$ZONE_ID\", \"type\": \"CNAME\", \"name\": \"$SUBDOMAIN\", \"value\": \"$TARGET\", \"ttl\": 60 }" > /dev/null else echo "[$SUBDOMAIN] ✅ CNAME bereits aktuell ($TARGET)" fi } # Durchlauf echo "🌐 Starte DNS-Update..." for DOMAIN in "${DOMAINS[@]}"; do if [[ -n "${CNAME_RECORDS[$DOMAIN]:-}" ]]; then update_cname "$DOMAIN" "${CNAME_RECORDS[$DOMAIN]}" else update_domain "$DOMAIN" fi done echo "✅ DNS-Update abgeschlossen."Wenn das Skript korrekt durchläuft, dies kann man testen mit --dry-run, sieht dies so aus:
Abschließende Arbeiten
- Logfile anlegen: sudo touch /var/log/update-dns.log
- Cron‑Job einrichten: Lege das Skript z.B. in /usr/local/bin/update-dns.sh ab und starte es alle 15 Minuten über
- */15 * * * * /usr/local/bin/update-dns.sh >/dev/null 2>&1
-
Logging: Für längere Fehlersuche kannst du Ausgaben in eine Log‑Datei umleiten:
update-dns.sh >>/var/log/update-dns.log 2>&1
Probiere das Skript aus und behalte deine DNS‑Einträge immer im Griff, ohne unnötig Ressourcen zu verschwenden. Viel Spaß beim Automatisieren!
Das Skript habe ich mittels ChatGPT erstellt, da ich selber nicht so wirklich der Held bin im programmieren. Das Skript habe ich trotzdem gecheckt und läuft einwandfrei auf meinem Server daheim.
Beitragsbild: generiert mit ChatGPT
GNU/Linux.ch ist ein Community-Projekt. Bei uns kannst du nicht nur mitlesen, sondern auch selbst aktiv werden. Wir freuen uns, wenn du mit uns über die Artikel in unseren Chat-Gruppen oder im Fediverse diskutierst. Auch du selbst kannst Autor werden. Reiche uns deinen Artikelvorschlag über das Formular auf unserer Webseite ein.