Wie Du weißt, nutze ich eine UniFi Dream Machine Pro. Sobald man von außen auf sein System zugreifen möchte, braucht man einen dynamischen DNS Dienst. Das ist nötig, weil sich die öffentliche IP-Adresse an privaten Internetanschlüssen in unregelmäßigen Abständen ändern kann. Wir brauchen aber eine Konstante, um nicht jedes Mal neue IP-Adressen auswendig lernen zu müssen. Dafür gibt es verschiedene Dienste, welche sich von außen aktualisieren lassen. Unser System Zuhause geht also aktiv auf den Dienst zu und sagt “hier ist meine neue IP-Adresse, bitte im DNS anpassen”.
Für genau dieses Thema gibt es viele Anbieter. Für die FritzBox gibt es beispielsweise MyFritz. Eventuell hast Du auch schon einmal von no-ip.com oder DuckDNS gehört. Ich nutze mittlerweile ganz gerne den Dienst von Dennis Schröder: ipv64.net. Wie genau man diesen mit UniFi konfiguriert, zeige ich Dir in diesem Beitrag.
Video
Update-URL konfigurieren
Auf den Einstellungen von Deinem WAN-Interface findest Du Dynamic DNS:
- Service:
custom - Hostname: Deine URL (z. B.
youtube.home64.de) - Username:
none - Password: Account Update Token von ip64.net
- Server:
https://ipv4.ipv64.net/update?hostname=%h
Außerdem gibt es weitere Platzhalter, welche je (je nach Dienst) in der Url benötigt werden:
%u: Username%p: Password%h: Hostname (siehe oben für IPv64)%i: IP-Address
all-inkl.de DDNS Update-Url
** Affiliate-Link
Ich bin zum Beispiel seit 2013 Kunde bei all-inkl.com ** und dort lautet die Url dyndns.kasserver.com/?myip=%i.
hetzner.de DDNS
Bei Hetzner ist das etwas komplizierter. Die API wurde Ende 2025 komplett umgestellt. Die DNS-Einstellungen sind seitdem auch in der Hetzner Console. Siehe Hetzner Dokumentation. Dort kann man in einem Projekt unter Sicherheit -> API-Tokens ein neues Token erstellen (Lesen und Schreiben).
Eine Zone für die jeweilige Domain sollte man schon haben (das passiert automatisch). Ein Projekt kann dabei natürlich mehrere Zones haben.
Alle Resource Record Sets (RRSets) dieser Zone kann man dann wie folgt ermitteln. Siehe Hetzner Dokumentation. Der Name der Zone muss natürlich angepasst werden:
curl -H "Authorization: Bearer <API-TOKEN>" \
"https://api.hetzner.cloud/v1/zones/haus-auto.com/rrsets"
Ein A-Record für meine Subdomain habe ich bereits in der Hetzner Console manuell erstellt (mit TTL 300)! Diesen möchte ich jetzt aktualisieren. Die Update-Url lautet:
https://api.hetzner.cloud/v1/zones/$ID_OR_NAME/rrsets/$RR_NAME/$RR_TYPE
- Meine Zone heißt
haus-auto.com - Resource Record ist die Subdomain. Bei mir im Beispiel
pub. - Resource Record Type ist ein A-Record:
A. Für IPv6 wäre esAAAA. - In diesem Beispiel ist die neue IP
4.4.4.4
curl -X POST -H "Authorization: Bearer <API-TOKEN>" -H "Content-Type: application/json" \
-d '{"records": [{"value": "4.4.4.4", "comment": "DynDNS"}]}' \
"https://api.hetzner.cloud/v1/zones/haus-auto.com/rrsets/pub/A/actions/set_records"
Klappt! Das Problem ist, dass man per Unifi-Oberfläche keine HTTP POST-Requests ausführen kann. Also baue ich einen kleinen Wrapper in PHP, welchen ich auf dem gleichen Hetzner-Server ablege. Den habe ich ja eh schon. UniFi ruft dann das PHP-Script per HTTP-GET auf und übergibt alle nötigen Parameter. Das PHP-Script macht daraus dann einen POST-Request und baut das passende JSON zusammen. Die Datei nenne ich dyndns.php:
<?php
// v0.2
declare(strict_types=1);
header('Content-Type: application/json');
// Debug logging (optional)
//$logEntry = sprintf("[%s] %s" . PHP_EOL, date('Y-m-d H:i:s'), json_encode($_GET));
//file_put_contents(__DIR__ . '/debug.log', $logEntry, FILE_APPEND | LOCK_EX);
$ipAddress = $_GET['ip'] ?? null;
$hostname = $_GET['hostname'] ?? null;
$type = $_GET['type'] ?? 'A';
if ($ipAddress === null || $hostname === null) {
http_response_code(400);
echo json_encode([
'status' => 'error',
'message' => 'Missing GET parameter. Required: ip, hostname'
]);
exit;
}
if (!filter_var($ipAddress, FILTER_VALIDATE_IP)) {
http_response_code(400);
echo json_encode([
'status' => 'error',
'message' => 'Invalid IP-Address'
]);
exit;
}
$parts = explode('.', $hostname, 2);
$count = count($parts);
if ($count < 2) {
http_response_code(400);
echo json_encode([
'status' => 'error',
'message' => 'Domain is invalid'
]);
exit;
}
$subdomain = $parts[0];
$domain = $parts[1];
if ($count < 2) {
http_response_code(400);
echo json_encode([
'status' => 'error',
'message' => 'Hostname is too short (must contain two dots - e.g. test.example.com)'
]);
exit;
}
$targetUrl = sprintf(
'https://api.hetzner.cloud/v1/zones/%s/rrsets/%s/%s/actions/set_records',
$domain,
$subdomain,
$type
);
$payloadData = [
'records' => [
[
'value' => $ipAddress,
'comment' => 'DynDNS'
]
]
];
$jsonPayload = json_encode($payloadData);
if ($jsonPayload === false) {
http_response_code(500);
echo json_encode([
'status' => 'error',
'message' => 'JSON encoding failed'
]);
exit;
}
$authUser = $_SERVER['PHP_AUTH_USER'] ?? null;
$authPass = $_SERVER['PHP_AUTH_PW'] ?? null;
// Fallback
if (($authUser === null) && isset($_SERVER['HTTP_AUTHORIZATION'])) {
if (str_starts_with(strtolower($_SERVER['HTTP_AUTHORIZATION']), 'basic ')) {
list($authUser, $authPass) = explode(':', base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)), 2);
}
}
/*
if ($authUser !== 'egal') {
http_response_code(400);
echo json_encode([
'status' => 'error',
'message' => 'Incorrect username. Script is protected by owner.'
]);
exit;
}
*/
$ch = curl_init($targetUrl);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_POSTFIELDS => $jsonPayload,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$authPass}",
'Content-Type: application/json',
'Accept: application/json'
],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($response === false) {
http_response_code(502);
echo json_encode([
'status' => 'error',
'message' => 'Connection to Hetzner API failed',
'details' => $curlError
]);
} else {
http_response_code(200);
echo json_encode([
'status' => 'success',
'responseCode' => $httpCode,
'details' => json_decode($response)
]);
}
Der Aufruf sieht dann wie folgt aus (in UniFi):
haus-auto.com/dyndns.php?hostname=%h&ip=%i
Ich nutze also die Parameter:
Hostnamefür die Zone (= Domain) und Resource Record (= Subdomain). Wird automatisch zerlegt.Usernameist ungenutzt und kann frei gewählt werden (darf nicht leer bleiben)Passwordist das API-Token (wird per Basic Auth übergeben, um nicht in den Logs zu stehen)
»Hetzner DynDNS API«
Update manuell anstoßen
Falls Du den SSH-Zugriff (als root) auf Deiner Dream Machine Pro freigeschaltet hast, kannst Du ein Update über die Shell manuell anstoßen. Dafür kannst Du folgenden Befehl nutzen:
inadyn -n -1 --force -f /run/ddns-ppp0-inadyn.conf
Transparenz-Hinweis (Level 1: Komplett selbst finanziert)
An diesem Beitrag ist kein Hersteller beteiligt! Sämtliche Produkte habe ich selber gekauft und trage die kompletten Kosten für diesen Beitrag alleine! Die Inhalte wurden somit von niemandem gesehen oder abgestimmt. Es handelt sich zu 100 Prozent um meine persönliche Meinung und Erfahrung! Danke an die Community, dass ich solche Inhalte für die Allgemeinheit zur Verfügung stellen kann!

