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 percurl
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 jq
Das 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
Nice one ^^
Ich habe eine simplere Variante für CF mal geschrieben. Wegen dem Handelskrieg bin ich nun am Überlegen zu Hetzner zu wandern.
Haben leider noch kein CDN und DDoS-Schutz.
Kennt ihr gute (kostenfreie) Anbieter die mit CF da mithalten können?
Danke danke :-)
Was Cloudflare betrifft bin ich leider raus.
Schau dir mal diese drei Alternativen an, vielleicht ist etwas dabei (das steht auch noch auf meiner viel zu langen Todo Liste): https://european-alternatives.eu/category/ddos-protection-services
Ich nutze freedns.afraid.org und habe ein deutlich einfacheres Skript zur Aktualisierung meiner dynamischen IP Adresse meiner eigenen domain. Es fragt die eigene IP Adresse des Systems bei mir daheim via einem angebotenen kostenlosen Service ab und vergleicht diese mit der alten. Bei Änderungen Update machen. Fertig.
So ähnlich geht das ja mit dem Skript auch. Es fragt über einen externen Dienst ab welche IP aktuell ist, wenn diese nicht mehr übereinstimmt mit der IP in der Hetzner DNS Konsole, wird ein Update gemacht.
Um einen DNS Record bei Hetzner zu aktualisieren, muss dieser nicht vorher gelöscht werden. Ihn vorher zu löschen bedeutet, dass er hinterher - weil er neu angelegt wird - auch eine neue Record ID erhält. Da beim reinen Aktualisieren des Eintrags die Zone ID und Record ID immer gleich bleibt, muss diese nicht jedes mal abgefragt werden und kann daher im Skript oder in einer separaten Datei abgelegt werden. Wenn man sich dann noch die IPv4 und IPv6 in einer Datei lokal merkt, müssen diese nicht erst durch separate API Calls ermittelt werden. Das reduziert die Anzahl der API Calls (und damit auch die Laufzeit und Last). Mein Skript:
Danke für den Kommentar. Das zwischenspeichern der IP ist eine gute Idee. Vielleicht ändere ich das ab. Möchte natürlich die API Calls reduzieren.
CNAME auf die fritz.box dnydns adresse. problem solved ;)
CNAME funktioniert bei Subdomains, nicht bei Hauptdomains.
Könnte man auch machen, aber wollte es schicker haben. 😃