Einen eigenen DynDNS Server mit dynamischer IP Adresse zu betreiben stellt eine grundsätzliche Herausforderung dar. Man hat ein handfestes Henne-vor-Ei-Problem: Wenn sich die IP-Adresse ändert, muss die Adresse im DNS geändert werden, obwohl der DynDNS Server unter dieser Adresse nicht mehr erreichbar ist…
Detailprobleme
Meine Konstellation dürfte im privaten Umfeld vermutlich des öfteren vorkommen. Ich habe mir irgendwann einmal günstig eine Domain registrieren lassen — nennen wir sie example.com
. Diese liegt bei einem günstigen Registrar, der mir per Webfrontend Zugriff auf die cPanel-Administration erlaubt, worüber ich die DNS-Einträge für example.com
editieren kann. Um jetzt zu Hause einen kleinen Server betreiben zu können (z.B. für Nextcloud oder einen kleinen Blog) sollte der A-Record für example.com
auf die gerade aktuelle IP-Adresse des DSL-Anschlusses zeigen. Die Subdomains für blog
oder cloud
können per CNAME-Record einfach auf example.com
verweisen. Analog dazu sollte der DynDNS Dienst unter ddns.example.com
erreichbar sein. Somit müssen wir also erstmal eine Lösung finden, um unseren zentralen A-Record von example.com
dynamisch aktualisieren zu können.
DynDNS-Client für cPanel-DNS
Die Lösungen hier hängen ausschließlich an den Möglichkeiten, die unser Registrar für example.com
anbietet. Im Idealfall bietet er eine DynDNS Option an, die vom DSL-Router angesprochen werden kann. Mein Anbieter hat es mir leider nicht ganz so einfach gemacht. Immerhin gibt es aber die Möglichkeit auf das cPanel-API zuzugreifen. Damit kann man also programmatisch DNS-Einträge anzeigen und manipulieren.
Konzept
Somit haben wir also folgendes Konzept: Ein Skript prüft regelmäßig, ob sich die IP-Adresse geändert hat. Falls ja, macht es per cPanel-API ein Update des A-Records von example.com
. Somit kann unser DynDNS-Dienst als „normaler“ Webserver unter ddns.example.com
laufen und ist immer erreichbar.
Tipp: Die TTL für „ddns.example.com“ sollte sehr klein (z.B. 60 Sekunden) gesetzt werden.
Meine Anforderungen an einen DynDNS Server sind sehr gering. Ich habe im Familien- und Freundeskreis eine Handvoll Fritzboxen, die per VPN immer erreichbar sein sollen und somit einen DDNS-Eintrag brauchen. Es ist für mich (und die beteiligten anderen) völlig akzeptabel, dass ihre Fritzbox unter familienname.example.com
erreichbar ist. Hätten sie einen kommerziellen DynDNS Dienst im Internet wäre es ja schließlich auch nicht anders.
Somit kann ich also ohne Probleme das gleiche Skript für meinen eigentlichen DynDNS Server verwenden, wie ich selbst brauche, um meine eigene IP-Adresse aktuell zu halten.
Umsetzung
Unter https://github.com/raceybe/cpanel-ddns habe ich bereits eine passende Lösung hierfür gefunden. Also im wesentlichen ein PHP-Skript, das per cPanel-API einen beliebigen DNS-Eintrag aktualisiert. Das Readme ist bereits sehr ausführlich und die Umsetzung sollte eigentlich keine Probleme machen. Deswegen beschreibe ich jetzt nur kurz die Einbettung in den Apache-Server und das Zusammenspiel aller Komponenten.
Das zentrale Update.php
braucht eine Config.php
an seiner Seite mit den Zugangsdaten, um auf das cPanel-API zugreifen zu können. Ausserdem gibt es eine Testclient.php
, das ebenfalls eine Config.php
braucht, um einen bestimmten Eintrag zu aktualisieren. Der Testclient spricht also mit Update.php
auf der Serverseite. Ich nutze den Testclient, um per cron-job meine eigene IP aktuell zu halten. Dafür muß Update.php
als Vhost im Apache-server sowohl über Port 80 intern erreichbar sein, als auch über Port 443 unter ddns.example.com
aus dem Internet.
Apache Server
Datei /etc/apache2/sites-available/ddns.example.com
Um die Konfiguration des eigentlichen Vhosts nicht für Port 80 und 443 doppelt zu pflegen, habe ich sie ausgelagert:
<VirtualHost *:80> Include /etc/apache2/ddns.vhost </VirtualHost> <VirtualHost *:443> Include /etc/apache2/ddns.vhost SSLCertificateFile /etc/letsencrypt/live/ddns.example.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/ddns.example.com/privkey.pem Include /etc/letsencrypt/options-ssl-apache.conf </VirtualHost>
Auf die Anforderung und Einbindung des Let’s-Enctrypt Zertifikats gehe ich jetzt an der Stelle nicht gesondert ein, dafür gibt es genügend Dokumentation im Internet.
Datei ddns.vhost
ServerName ddns.example.com DocumentRoot /var/www/vhosts/ddns/ DirectoryIndex index.php index.html ErrorLog /var/log/apache2/ddns.example.com-error.log TransferLog /var/log/apache2/ddns.example.com-access.log <Directory /var/www/vhosts/ddns/> AllowOverride All Options FollowSymLinks <IfVersion < 2.3> Order allow,deny Allow from all </IfVersion> <IfVersion >= 2.3> Require all granted </IfVersion> </Directory>
Datei /var/www/vhosts/ddns/.htaccess
CGIPassAuth on SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.+)$ $1.php [QSA,L] </IfModule>
Unter /var/www/vhosts/ddns/
kommt dann die Update.php
. Da es mir etwas zu unsicher erschien die Konfigurationsdatei mit den Zugangsdateien im selben Verzeichnis neben die aus dem Internet zugängliche Update.php
zu legen, habe ich den Pfad angepaßt und die Config.php
unter /var/www/
abgelegt:
require "../../ddns.config.php";
Datei /var/www/ddns.config.php
<?php /********************************************************************************* *Config Example * *Make sure this file is in the same folder as update.php and is called config.php *********************************************************************************/ //cPanel API user, password, server, and port $cpUser = 'username'; $cpPassword = 'MySuperSecretPassword'; $cpServer = 'https://cp188.myregistrar.net'; $cpPort = '2083'; //Authorized ddns users, passwords, and hostnames $authUsers=array('user1','user2'); $authPasswords=array('Password1','Password2'); $authHostnames=array('example.com','host1.example.com');
Hier können also für jeden zu aktualisierenden Host unterschiedliche Zugangsdaten hinterlegt werden. Der erste Eintrag ist für unseren cron-job, da er den A-Record für die Domain selbst aktualisiert. Jeder weitere Host braucht auch einen Benutzer und ein Passwort.
An der Stelle merkt man auch, dass das ganze nicht für größere Installationen geeignet ist. Aber für meinen Bedarf reicht es locker.
cron-job
Analog und passend zu den Zugangsdaten, die in der ddns.config.php
hinterlegt wurden, muss natürlich auch der Testclient konfiguriert werden:
Datei /opt/updateIP/config.php
<?php /********************************************************************************* *Config Example * *Make sure this file is in the same folder as testclient.php and is called config.php *********************************************************************************/ //ddns client configuration details $myip='1.2.3.4'; $hostname='example.com'; $login='user1'; $password='Password1'; $server='ddns.example.com';
Die Variable $myip= wird vom cron-job gesetzt und enthält die aktuelle IP-Adresse, die nach cPanel geschrieben werden soll.
Datei /opt/updateIP/update.sh
#!/bin/bash oldip=`grep myip= /opt/updateIP/config.php |sed 's/;.*//' |grep -v ^$ |sed s/.*=\ *//` ip=`curl -4s http://my.ip.fi` if [ "$oldip" != "$ip" ] then sed -i "s/myip=$oldip/myip='$ip'/" /opt/updateIP/config.php cd /opt/updateIP php testclient.php fi
Der Skript holt sich als erstes die bisherige IP-Adresse aus der Config.php
. Dann holt er sich über http://my.ip.fi
die aktuelle externe IP-Adresse des Anschlusses. Wenn der Router hierfür ein API anbietet, oder das ganze Script vielleicht eh auf dem Router läuft, kann man sich die externe IP-Adresse eventuell auch direkt vom DSL-Router holen. Hat sich die Adresse geändert, trägt er die neue Adresse in der Config.php
ein und ruft den Testclient auf.
Damit das funktioniert, muss lediglich sichergestellt sein, dass der cron-job unseren DynDNS-Server auch intern (d.h. wirklich ohne Internet!) unter ddns.example.com erreichen kann. Sollte sich die IP-Adresse nämlich geändert haben, würde ein externer DNS ja auf die alte IP-Adresse auflösen, womit der Update in die Hose geht! Das ganze läßt sich durch einen Eintrag in der /etc/hosts Datei lösen.
Die Datei /opt/updateIP/update.sh
kann jetzt per cron, z.B. über /etc/crontab
eingebunden werden, sodaß sie beispielsweise zu jeder Stunden in Minute 13 läuft:
13 * * * * root /opt/updateIP/update.sh >/dev/null 2>&1
Zusammenfassung
Damit sollten wir jetzt am Ziel sein: Der cron-job hält den A-Record für die ganze Domäne aktuell, womit der DynDNS-Server immer unter ddns.example.com erreichbar ist. Jetzt fehlt nur noch, externe Hosts, wie z.B. eine Fritzbox so zu konfigurieren, dass sie diesen DynDNS-Server verwendet. Die dafür nötige URL ist:
https://user1:Passwort1@ddns.example.com/update?hostname=$hostname&myip=$myip
User1
und Passwort1
müssen natürlich den hinterlegten Zugangsdaten in der Datei ddns.config.php
entsprechen. Die genaue Bezeichnung der Variablen $hostname
und $myip
hängt vom verwendeten Router ab, bei der Fritzbox lauten sie z.B. <domain>
und <ipaddr>
. Am Besten in der Doku des Routers nachsehen.