1

Eigenen DynDNS Server mit dynamischer IP Adresse betreiben

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.