Diese Dokumentation beschreibt den Aufbau und die Migration eines bestehenden Webservers hinter einen Reverse Proxy mit OPNsense und Caddy. Im Mittelpunkt steht eine Umgebung mit Apache, mehreren WordPress-Websites und Nextcloud, bei der sowohl die bestehende URL-Struktur als auch der direkte Zugriff auf den internen Webserver erhalten bleiben sollen.
Die externe HTTPS-Verbindung wird dabei vollständig durch Caddy übernommen. Caddy terminiert TLS, verarbeitet die eingehenden Anfragen und leitet sie anhand des Hostnamens an den zuständigen Apache-VirtualHost weiter. Apache selbst verbleibt auf dem internen Webserver und stellt die eigentlichen Websites und Anwendungen bereit.
Ein besonderer Schwerpunkt liegt auf dem Zusammenspiel zwischen Caddy und Apache, der Trennung zwischen externer und interner URL-Struktur sowie der parallelen Unterstützung von neuen Subdomain-Adressen und bestehenden pfadbasierten URLs. Dadurch können vorhandene Links, WordPress-Strukturen sowie Nextcloud-DAV-Zugriffe während und nach der Migration weiterhin genutzt werden.
Die dargestellte Konfiguration basiert auf einer tatsächlich aufgebauten und getesteten Umgebung. Neben der eigentlichen Konfiguration werden auch die erforderlichen Anpassungen und Prüfungen dokumentiert. Dadurch lässt sich der gesamte Aufbau nachvollziehen und bei einem späteren Server-, Domain- oder Infrastrukturwechsel gezielt anpassen.
| Beispielwert | Bedeutung |
|---|---|
olddomain.de |
alte Domain (Pfad-basierte Adressierung) |
newdomain.de |
neue Domain (Subdomain-basierte Adressierung) |
olddomain/myblog / myblog.newdomain.de |
Blog-Site; alter | neuer Pfad /myblog/ | /myblog.newdomain.de/ |
www.website1.de … www.website4.de |
WordPress-Sites; Verzeichnis /var/www/www.websiteN.de; Subdomain websiteN.newdomain.de; Alias /www.websiteN.de |
olddomain.d/nextcloud / nextcloud.newdomain.de |
Nextcloud; alter| neuer Pfad /nextcloud | nextcloud.newdomain.de (direkt per IP und als alter Pfad auf der alten Domain) |
192.178.0.0/24 |
internes Netz des Reverse Proxys |
192.178.1.0/24 |
Netz des Webservers: 192.178.1.100 = Webserver, 192.178.0.1 = Adresse des Gateway aus Sicht des Webservers |
Inhaltsverzeichnis
1. Ziel und Architektur
1.1 Ziel
Ein bestehender Apache-Webserver mit mehreren WordPress-Sites und einer Nextcloud-Instanz wird hinter einem Reverse Proxy (Caddy auf OPNsense) betrieben. Die Domain wechselt von olddomain.de (Pfad-basierte Adressierung, z. B. /myblog/) auf newdomain.de (Subdomain-basierte Adressierung, z. B. myblog.newdomain.de). Konfigurationen und Weiterleitungen sind so weit wie möglich nicht von der Domain abhängig, damit bei einem weiteren Umzug nur wenige Stellen anzupassen sind.
- Jede WordPress-Site ist über eine eigene Subdomain des Reverse Proxys und zusätzlich direkt über die Server-IP erreichbar.
- Alte Links der Form
https://olddomain.de//werden aufhttps://.newdomain.de/weitergeleitet. Der Teilbleibt unverändert. - Die alte Nextcloud-URL
https://olddomain.de/nextcloudbleibt für Browser (Weiterleitung) und für DAV-Clients (WebDAV, CalDAV, CardDAV) nutzbar.
1.2 Komponenten der tatsächlichen Umgebung
| Komponente | Rolle | Details |
|---|---|---|
| Client/Browser | Ausgangspunkt der Anfrage | ruft z. B. https://myblog.newdomain.de/ auf |
| DNS (deSEC) | Namensauflösung der neuen Domain | newdomain.de: A-Record auf die öffentliche IP (automatisch aktualisiert); Wildcard-CNAME * → newdomain.de |
| DNS (alte Domain) | Namensauflösung der alten Domain | olddomain.de zeigt weiterhin auf die öffentliche IP (durch funktionierende Weiterleitungen bestätigt) |
| Internet-Router | Router/Internetzugang | 192.178.0.1; Portfreigaben 80/443 auf der Fritzbox für OPNsense |
| OPNsense | Firewall/DMZ-Gateway, VM auf einem Hypervisor | Hostname opnsense-proxy; WAN-Adresse im internen Netz 192.178.0.3; Netz zum Webserver 192.178.1.0/24 |
| Caddy (os-caddy) | Reverse Proxy, TLS-Terminierung | läuft direkt auf der OPNsense; HTTP/1.1 und HTTP/2 (HTTP/3 aus) |
| Apache | Webserver | VM webserver, 192.178.1.100, Port 80 (nur HTTP) |
| WordPress | Webanwendung | PHP/MySQL, je Site ein Verzeichnis unter /var/www/www. |
| Nextcloud | Webanwendung | /var/www/nextcloud |
1.3 Weg einer Anfrage
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Client / Browser URL: https://myblog.newdomain.de/category/smart-home/ ↓ DNS (deSEC) myblog.newdomain.de → *-CNAME → newdomain.de → öffentliche IP ↓ Router Portweiterleitung → OPNsense (WAN 192.178.0.3) ↓ OPNsense Firewall-Regeln, Zugriff auf Caddy (Port 443) ↓ Caddy TLS-Terminierung (Wildcard-Zertifikat), Auswahl des Backends ↓ anhand der Domain (Site-Block "myblog.newdomain.de") ↓ HTTP, Port 80, Host: myblog.newdomain.de, X-Forwarded-Proto: https Apache Auswahl des VirtualHosts anhand des Host-Header, übergeben vom Reverse Proxy oder direktem IP:Port/Pfad Aufruf ↓ VirtualHost ServerAlias/Servername "myblog.*" → DocumentRoot /var/www/www.myblog ↓ DocumentRoot .htaccess (mod_rewrite) → index.php ↓ WordPress liefert die Seite /category/smart-home/ |
1.4 Externe URL und interner Zielserver
Die extern sichtbare URL (https://myblog.newdomain.de/category/smart-home/) und die interne Adresse des Zielservers (http://192.178.1.100/) sind zwei getrennte Ebenen:
- Extern: Schema
https, Hostnamemyblog.newdomain.de, Pfad/category/smart-home/. TLS endet am Caddy. - Intern: Caddy öffnet eine neue HTTP-Verbindung zu
192.178.1.100:80und übergibt den ursprünglichen Hostnamen imHost-Header. Der Pfad bleibt unverändert. - Direktzugriff (ohne Reverse Proxy):
http://192.178.1.100/www.myblog/category/smart-home/. Der Host-Header lautet hier192.178.1.100; der Pfad enthält zusätzlich den Alias-Präfix/www.myblog.
1.5 Zuordnung der Sites
Alte URL (Domain olddomain.de) |
Neue URL über Reverse Proxy | Verzeichnis auf dem Webserver | Direktzugriff per IP/Pfad |
|---|---|---|---|
https://olddomain/myblog/ |
https://myblog.newdomain.de/ |
/var/www/www.myblog |
http://192.178.1.100/www.myblog/ |
https://olddomain/www.website1.de/ |
https://website1.newdomain.de/ |
/var/www/www.website1.de |
http://192.178.1.100/www.website1.de/ |
https://olddomain/www.website2.de/ |
https://website2.newdomain.de/ |
/var/www/www.website2.de |
http://192.178.1.100/www.website2.de/ |
https://olddomain/www.website3.de/ |
https://website3.newdomain.de/ |
/var/www/www.website3.de |
http://192.178.1.100/www.website3.de/ |
https://olddomain/www.website4.de/ |
https://website4.newdomain.de/ |
/var/www/www.website4.de |
http://192.178.1.100/www.website4.de/ |
https://olddomain/nextcloud |
https://nextcloud.newdomain.de/ |
/var/www/nextcloud |
http://192.178.1.100/nextcloud/ |
2. Apache-Webserver
2.1 Installation und Module
|
1 2 |
apt update apt install apache2 |
Die PHP-Pakete (WordPress, Nextcloud) und der Datenbankserver richten sich nach den Anforderungen der jeweiligen Anwendungen.
|
1 2 |
a2enmod macro rewrite headers remoteip env setenvif alias dir apache2ctl -M | grep -E 'macro|rewrite|headers|remoteip|env|setenvif|alias' |
| Modul | Verwendung | Folge bei Fehlen |
|---|---|---|
macro |
in 000-default.conf |
Apache startet nicht: AH00526: Invalid command 'Macro' |
rewrite |
WordPress-Permalinks über .htaccess |
Unterseiten liefern 404 |
headers |
Header always set Strict-Transport-Security in nextcloud.conf |
Konfigurationsfehler beim Start |
remoteip |
RemoteIPHeader/RemoteIPInternalProxy in nextcloud.conf |
Konfigurationsfehler beim Start |
env, setenvif |
SetEnv, SetEnvIf (Nextcloud, HTTPS-Erkennung) |
Konfigurationsfehler bzw. fehlende HTTPS-Erkennung |
alias |
Alias-Direktiven für den Direktzugriff |
Direktzugriff per IP nicht möglich |
Das Modul macro entfällt bei der Variante ohne Makro (Kapitel 2.4.1).
2.2 Verzeichnisstruktur
|
1 2 3 4 5 6 7 8 |
/var/www/ ├── html/ Standard-DocumentRoot des Default-VirtualHosts ├── nextcloud/ Nextcloud ├── www.myblog/ WordPress – myblog.newdomain.de ├── www.website1.de/ WordPress – website1.newdomain.de ├── www.website2.de/ WordPress – website2.newdomain.de ├── www.website3.de/ WordPress – website3.newdomain.de └── www.website4.de/ WordPress – website4.newdomain.de |
Die Verzeichnisse der WordPress-Sites beginnen einheitlich mit www.. Auf diesem Präfix beruht die gemeinsame <Directory "/var/www/www.*">-Freigabe in 000-default.conf. Ein Verzeichnis ohne dieses Präfix erhielte kein AllowOverride All; die .htaccess-Rewrite-Regeln von WordPress würden ignoriert.
2.3 Aufbau der Debian-Konfiguration
|
1 2 3 4 5 |
/etc/apache2/apache2.conf Hauptdatei; bindet mods-enabled, ports.conf, conf-enabled und sites-enabled ein /etc/apache2/ports.conf Listen-Direktiven /etc/apache2/mods-available/ verfügbare Module → a2enmod / a2dismod → mods-enabled/ (Symlinks) /etc/apache2/sites-available/ verfügbare VHosts → a2ensite / a2dissite → sites-enabled/ (Symlinks) /etc/apache2/conf-available/ verfügbare Zusatzkonfiguration → a2enconf / a2disconf → conf-enabled/ |
apache2.conf enthält am Ende IncludeOptional sites-enabled/*.conf. Die Dateien in sites-enabled/ werden in alphabetischer Reihenfolge eingelesen. Das ist die Grundlage für die Sonderstellung von 000-default.conf (Kapitel 2.5).
Die Datei apache2.conf enthält außerdem die globale Zugriffsbasis:
|
1 2 3 4 5 6 7 8 9 |
[...] Options FollowSymLinks AllowOverride None Require all denied [...] Options Indexes FollowSymLinks AllowOverride None Require all granted [...] |
Das Dateisystem ist standardmäßig gesperrt; /var/www/ ist freigegeben, jedoch ohne .htaccess-Auswertung (AllowOverride None). Die Freigabe der .htaccess-Auswertung erfolgt je Anwendung in den VirtualHost-Dateien.
2.4 Die Datei 000-default.conf
Die Konfiguration der einzelnen Hosts kann entweder zentral in der Datei /etc/apache2/sites-available/000-default.conf vorgenommen oder alternativ auf einzelne Konfigurationsdateien pro Host unter /etc/apache2/sites-available/.conf verteilt werden.
Der Vorteil der zentralen Konfiguration liegt in der Verwendung von Apache-Makros, mit denen gemeinsame Einstellungen für mehrere Websites zusammengefasst und wiederverwendet werden können. Nachteil ist, dass einzelne Websites nicht unabhängig über a2ensite bzw. a2dissite aktiviert oder deaktiviert werden können. Bei getrennten Dateien unter /etc/apache2/sites-available/.conf ist dies dagegen für jeden Host einzeln möglich.
In diesem Beispiel wird die gesamte Konfiguration der Dienste und Websites zentral über die Datei 000-default.conf vorgenommen. Dies hat den Vorteil, dass sich die Konfiguration mithilfe von Apache-Makros zusammenfassen und dadurch eine zentrale Verwaltung aller Websites und Dienste realisieren lässt. Website-spezifische Konfigurationsdateien unter /etc/apache2/sites-available/www..conf werden in diesem Aufbau nicht benötigt.
Pfad: /etc/apache2/sites-available/000-default.conf
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# /etc/apache2/sites-available/000-default.conf # Voraussetzung: a2enmod macro rewrite # Domainunabhaengig: Zuordnung per ServerAlias "<sub>.*" (Subdomain = erstes Label), Domain wird nirgends benoetigt. # Neue WordPress-Site = ein "Use WPSite"-Eintrag + ein Alias fuer den Direktzugriff. # Alle WordPress-Verzeichnisse (/var/www/www.*) <Directory "/var/www/www.*"> Options +FollowSymLinks AllowOverride All Require all granted </Directory> <Macro WPSite $sub $dir> <VirtualHost *:80> ServerName $sub.<newdomain> ServerAlias $sub.* DocumentRoot /var/www/$dir ErrorLog ${APACHE_LOG_DIR}/$sub-error.log CustomLog ${APACHE_LOG_DIR}/$sub-access.log combined </VirtualHost> </Macro> # Default-VHost (muss als erstes geladen werden): Direktzugriff per IP / Kurzname <VirtualHost *:80> DocumentRoot /var/www/html # Nextcloud (Direktzugriff ueber http://<ip>/nextcloud) Alias /nextcloud /var/www/nextcloud <Directory /var/www/nextcloud/> Options +FollowSymLinks AllowOverride All Require all granted <IfModule mod_dav.c> Dav off </IfModule> SetEnv HOME /var/www/nextcloud SetEnv HTTP_HOME /var/www/nextcloud </Directory> Alias /www.myblog /var/www/www.myblog Alias /www.website1.de /var/www/www.website1.de Alias /www.website2.de /var/www/www.website2.de Alias /www.website3.de /var/www/www.website3.de Alias /www.website4.de /var/www/www.website4.de # FQDN-Hosts ohne eigenen VHost (z.B. unbekannte Subdomains ueber den Wildcard-Handler) -> 403 <If "%{HTTP_HOST} !~ /^([0-9.]+|[^.:]+)(:[0-9]+)?$/"> Require all denied </If> ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> # Subdomain Verzeichnis Use WPSite myblog www.myblog Use WPSite website1 www.website1.de Use WPSite website2 www.website2.de Use WPSite website3 www.website3.de Use WPSite website4 www.website4.de |
Funktion der Datei: Die Datei ersetzt die fünf früheren Einzel-VirtualHosts (www.<websiteN>.conf) und den früheren Default-VirtualHost. Sie definiert (a) die Freigabe aller WordPress-Verzeichnisse, (b) ein Makro für namensbasierte WordPress-VirtualHosts, (c) den Default-VirtualHost für Direktzugriffe und (d) die fünf Site-Zuordnungen.
Erläuterung der einzelnen Bestandteile
| Bestandteil | Zweck | Folge ohne diese Einstellung |
|---|---|---|
<Directory "/var/www/www.*"> mit AllowOverride All |
Auswertung der WordPress-.htaccess für alle Verzeichnisse mit Präfix www. |
Permalinks liefern 404; nur index.php-URLs funktionieren |
<Macro WPSite $sub $dir> |
Ein VirtualHost-Muster für alle WordPress-Sites; Parameter: Subdomain und Verzeichnis | je Site eine eigene Datei mit Duplikaten |
ServerName $sub.<newdomain> |
Pflichtangabe; der Wert wird nie von Clients angefragt Definiert den primären Hostnamen des VirtualHosts. Auch bei einem Reverse Proxy wird dieser zur Zuordnung des Host-Headers verwendet. |
Apache vergibt einen Standardnamen, Warnungen im Log |
ServerAlias $sub.* |
Erlaubt zusätzliche Hostnamen für denselben VirtualHost. Dies kann sinnvoll sein, wenn die Domain flexibel gehalten oder später geändert werden soll. | Bei fest eingetragenem ServerName myblog.newdomain.de müsste jeder Domainwechsel in jeder VHost-Datei nachgezogen werden |
DocumentRoot /var/www/$dir |
Zuordnung Subdomain → Verzeichnis | falsche oder keine Site |
ErrorLog/CustomLog mit $sub |
getrennte Logs je Site (myblog-error.log, …) |
gemeinsame Logs, schwerere Diagnose |
Default-VirtualHost (erster Eintrag auf *:80 in alphabetischer Dateireihenfolge) |
fängt Anfragen ab, die zu keinem ServerName/ServerAlias passen, insbesondere Direktaufrufe über die IP |
Direktzugriff per IP würde einen der Site-VHosts treffen |
Alias /www. /var/www/www. |
Direktzugriff http://192.178.1.100/www./ |
Direktzugriff liefert 404 |
Alias /nextcloud mit , Dav off, SetEnv HOME |
Direktzugriff auf Nextcloud per IP | http://192.178.1.100/nextcloud liefert 404 (tatsächlich aufgetreten, Kapitel 7.11) |
<If … Require all denied> |
Hosts mit Punkt im Namen, die keinen eigenen VirtualHost haben (unbekannte Subdomains), erhalten 403; IP-Adressen und Kurznamen ohne Punkt bleiben zugelassen | Unbekannte Hostnamen erreichen die Alias-Pfade (/www.website2.de/ unter beliebigem Hostnamen) |
Reihenfolge innerhalb der Datei: Das Makro ist vor seiner Verwendung definiert; der Default-VirtualHost steht vor den Use WPSite-Zeilen. Maßgeblich für die Default-Eigenschaft ist jedoch die Reihenfolge der Dateien in sites-enabled/ (Kapitel 2.5), nicht die Reihenfolge innerhalb der Datei, solange keine weitere Datei einen *:80-VirtualHost enthält, der alphabetisch früher einsortiert wird. Apache vergleicht den Host-Header mit ServerName und mit ServerAlias, wobei der erste Treffer gewinnt. ServerName $sub. sollte daher auf die neue domain verweisen, da sonst nur der ServerAlias greift.
2.4.1 Variante ohne Makro
Die Datei 000-default.conf kann auch ohne mod_macro aufgebaut werden. In diesem Fall werden die fünf WordPress-VirtualHosts jeweils vollständig ausgeschrieben; mod_macro und a2enmod macro sind dann nicht erforderlich.
Pfad: /etc/apache2/sites-available/000-default.conf
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# /etc/apache2/sites-available/000-default.conf (Variante ohne Makro) # Voraussetzung: a2enmod rewrite # Domainunabhaengig: Zuordnung per ServerAlias "<sub>.*" (Subdomain = erstes Label), Domain wird nirgends benoetigt. # Neue WordPress-Site = ein weiterer VirtualHost-Block + ein Alias fuer den Direktzugriff. # Alle WordPress-Verzeichnisse (/var/www/www.*) <Directory "/var/www/www.*"> Options +FollowSymLinks AllowOverride All Require all granted </Directory> # Default-VHost (muss als erster VirtualHost geladen werden): Direktzugriff per IP / Kurzname <VirtualHost *:80> DocumentRoot /var/www/html # Nextcloud (Direktzugriff ueber http://<ip>/nextcloud) Alias /nextcloud /var/www/nextcloud <Directory /var/www/nextcloud/> Options +FollowSymLinks AllowOverride All Require all granted <IfModule mod_dav.c> Dav off </IfModule> SetEnv HOME /var/www/nextcloud SetEnv HTTP_HOME /var/www/nextcloud </Directory> Alias /www.myblog /var/www/www.myblog Alias /www.website1.de /var/www/www.website1.de Alias /www.website2.de /var/www/www.website2.de Alias /www.website3.de /var/www/www.website3.de Alias /www.website4.de /var/www/www.website4.de # FQDN-Hosts ohne eigenen VHost (z.B. unbekannte Subdomains ueber den Wildcard-Handler) -> 403 <If "%{HTTP_HOST} !~ /^([0-9.]+|[^.:]+)(:[0-9]+)?$/"> Require all denied </If> ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost> # WordPress-VirtualHosts (ein Block je Site) <VirtualHost *:80> ServerName myblog.<newdomain> ServerAlias myblog.* DocumentRoot /var/www/www.myblog ErrorLog ${APACHE_LOG_DIR}/myblog-error.log CustomLog ${APACHE_LOG_DIR}/myblog-access.log combined </VirtualHost> <VirtualHost *:80> ServerName website1.<newdomain> ServerAlias website1.* DocumentRoot /var/www/www.website1.de ErrorLog ${APACHE_LOG_DIR}/website1-error.log CustomLog ${APACHE_LOG_DIR}/website1-access.log combined </VirtualHost> <VirtualHost *:80> ServerName website2.<newdomain> ServerAlias website2.* DocumentRoot /var/www/www.website2.de ErrorLog ${APACHE_LOG_DIR}/website2-error.log CustomLog ${APACHE_LOG_DIR}/website2-access.log combined </VirtualHost> <VirtualHost *:80> ServerName website3.<newdomain> ServerAlias website3.* DocumentRoot /var/www/www.website3.de ErrorLog ${APACHE_LOG_DIR}/website3-error.log CustomLog ${APACHE_LOG_DIR}/website3-access.log combined </VirtualHost> <VirtualHost *:80> ServerName website4.<newdomain> ServerAlias website4.* DocumentRoot /var/www/www.website4.de ErrorLog ${APACHE_LOG_DIR}/website4-error.log CustomLog ${APACHE_LOG_DIR}/website4-access.log combined </VirtualHost> |
Unterschiede zur Makro-Variante
| Punkt | Mit Makro (Kapitel 2.4) | Ohne Makro |
|---|---|---|
| Apache-Modul | macro erforderlich |
nicht erforderlich |
| WordPress-VirtualHosts | eine Definition (<Macro WPSite $sub $dir>) und je Site eine Zeile Use WPSite <sub> <dir> |
je Site ein vollständiger <VirtualHost>-Block |
| Neue Site | eine Zeile Use WPSite … |
Block kopieren und vier Angaben anpassen: ServerName, ServerAlias, DocumentRoot, Dateinamen der Logs |
| Default-VirtualHost | erster <VirtualHost> der Datei |
unverändert; er muss auch hier vor den WordPress-VirtualHosts stehen, damit er der Default-Server bleibt (Kapitel 2.5.2) |
<Directory "/var/www/www.*">, Aliase, <If>-Regel |
unverändert | unverändert |
Einrichtung
|
1 2 3 4 |
a2enmod rewrite a2ensite 000-default apache2ctl configtest systemctl reload apache2 |
Einzeldateien statt einer Datei: Die WordPress-VirtualHost-Blöcke können alternativ in eigene Dateien /etc/apache2/sites-available/www..conf ausgelagert und mit a2ensite www. aktiviert werden. 000-default.conf behält <Directory "/var/www/www.*"> und den Default-VirtualHost. Da die Dateinamen www.* alphabetisch nach 000-default.conf einsortiert werden, bleibt der Default-VirtualHost der Default-Server. ServerAlias .* bleibt unverändert, die Konfiguration ist weiterhin domainunabhängig.
Neue Site ergänzen (Beispiel website5, Verzeichnis /var/www/www.website5.de)
- Verzeichnis
/var/www/www.website5.deanlegen bzw. die Site dorthin kopieren. - Im Default-VirtualHost den Alias ergänzen:
Alias /www.website5.de /var/www/www.website5.de. - Einen VirtualHost-Block für
website5nach dem Muster der vorhandenen Blöcke anfügen. - In der
wp-config.phpder Site$direct_pathauf'/www' . '.website5.de'setzen (Kapitel 5.3). - In Caddy die Subdomain
website5mit Handlerreverse_proxy 192.178.1.100anlegen (Kapitel 4.2) und innewdomain-redirect.confbei Bedarf eine Zeileimport legacy_site …ergänzen (Kapitel 4.5). apache2ctl configtest && systemctl reload apache2.
2.5 VirtualHosts: Auswahlverfahren und Sonderstellung von 000-default.conf
2.5.1 Wie Apache den VirtualHost auswählt
Apache ermittelt den passenden VirtualHost in zwei Schritten:
1. Adressebene (IP-Adresse und Port):
Zunächst wird anhand der lokalen IP-Adresse und des Ports bestimmt, welche VirtualHosts für die eingehende Verbindung infrage kommen. Bei *:80 lauschen die entsprechenden VirtualHosts auf allen lokalen IP-Adressen am Port 80 und gehören damit zur gleichen VirtualHost-Gruppe.
2. Namensebene (Host-Header):
Innerhalb dieser Gruppe vergleicht Apache den Host-Header der Reverse Proxy Anfrage mit den Angaben in ServerName und ServerAlias. Die VirtualHosts werden dabei in der Reihenfolge berücksichtigt, in der sie von Apache eingelesen wurden. Der erste passende VirtualHost wird verwendet.
In ServerAlias können auch Wildcards wie nextcloud.* verwendet werden. ServerName und ServerAlias sind daher auch bei einem Reverse Proxy relevant, da der von Caddy weitergereichte Host-Header zur Auswahl des passenden Apache-VirtualHosts verwendet wird.
Die tatsächliche Zuordnung zeigt:
|
1 |
apache2ctl -S |
Die Ausgabe nennt je *:80 den „default server“ (mit Datei und Zeile) sowie jeden namensbasierten VirtualHost mit Datei/Zeile.
2.5.2 Warum 000-default.conf besondere Bedeutung hat
sites-enabled/*.conf wird alphabetisch eingelesen. Das Präfix 000- sortiert die Datei vor alle anderen. Der erste *:80-VirtualHost ist damit der Default-VirtualHost. Das hat drei Folgen:
- Direktaufrufe über die IP (
Host: 192.178.1.100) passen zu keinemServerAlias(myblog.*usw. trifft keine IP-Adresse) und landen deshalb im Default-VirtualHost. Dort sind die Alias-Pfade (/www./,/nextcloud) definiert. - Unbekannte Hostnamen landen ebenfalls dort. Die
-Regel sperrt solche Anfragen (403), sofern der Host ein FQDN ist. - Würde eine andere Datei alphabetisch vor
000-default.confeinsortiert, würde deren VirtualHost zum Default und IP-Aufrufe lieferten dessen Inhalt.
2.5.3 Vergleich der VirtualHost-Arten
| Art | Beispiel in dieser Umgebung | Auswahlkriterium | Besonderheit |
|---|---|---|---|
| Default-VirtualHost | 000-default.conf, erster |
erster auf *:80; greift, wenn nichts anderes passt |
Direktzugriff über IP, Aliase |
| Normaler namensbasierter HTTP-VirtualHost | Makro WPSite (ServerAlias myblog.*), nextcloud.conf (ServerName nextcloud.newdomain.de) |
Port 80 und Host-Header | eigenes DocumentRoot je Site |
HTTPS-VirtualHost ( mit SSLEngine on, Zertifikat) |
in dieser Architektur nicht im Einsatz,SSL übernimmt der Reverse Proxy default-ssl.conf liegt in sites-available/, |
Port 443 und Host-Header (bzw. SNI) | Apache würde selbst TLS terminieren |
| Weitere namensbasierte VirtualHosts | weitere Use WPSite-Zeilen |
wie oben | ergänzen sich ohne neue Datei |
TLS endet in dieser Architektur am Caddy. Apache lauscht ausschließlich auf Port 80. Das Schema der ursprünglichen Anfrage wird über den Header X-Forwarded-Proto weitergegeben (Kapitel 3 und 5).
2.5.4 Mehrere Domains auf derselben IP
Mehrere Domains/Hostnamen auf derselben IP und demselben Port werden ausschließlich über den Host-Header getrennt (namensbasierte VirtualHosts). Im Fall der Reverse-Proxy-Nutzung sind alle Anfragen Verbindungen von Caddy zu 192.178.1.100:80; die Unterscheidung der Sites erfolgt allein über den weitergegebenen Hostnamen.
2.5.5 Warum ein Direktaufruf einen anderen VirtualHost erreicht als der Domainaufruf
| Aufruf | Host-Header bei Apache | Ergebnis |
|---|---|---|
https://myblog.newdomain.de/ über Caddy |
myblog.newdomain.de |
Treffer ServerAlias myblog.* → DocumentRoot /var/www/www.myblog |
http://192.178.1.100/www.myblog/ |
192.178.1.100 |
kein Treffer → Default-VirtualHost → Alias /www.myblog → dasselbe Verzeichnis |
http://192.178.1.100/ |
192.178.1.100 |
Default-VirtualHost → /var/www/html |
Das Zielverzeichnis ist in beiden Fällen identisch, der Weg dorthin unterscheidet sich: einmal über DocumentRoot eines namensbasierten VirtualHosts (URL-Pfad ab /), einmal über einen Alias im Default-VirtualHost (URL-Pfad ab /www.myblog/). Daraus folgt der Bedarf an der URL-Umschaltung in WordPress (Kapitel 5).
2.6 Die Datei nextcloud.conf
Pfad: /etc/apache2/sites-available/nextcloud.conf
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<VirtualHost *:80> ServerName nextcloud.newdomain.de DocumentRoot /var/www/nextcloud/ Alias /nextcloud "/var/www/nextcloud/" # Reverse proxy RemoteIPHeader X-Forwarded-For RemoteIPInternalProxy 192.178.1.1 SetEnvIf X-Forwarded-Proto "https" HTTPS=on Header always set Strict-Transport-Security "max-age=15552000" # Logging ErrorLog /var/log/apache2/nectcloud-error.log CustomLog /var/log/apache2/nextcloud-access.log combined <Directory /var/www/nextcloud/> Options +FollowSymlinks AllowOverride All <IfModule mod_dav.c> Dav off </IfModule> SetEnv HOME /var/www/nextcloud SetEnv HTTP_HOME /var/www/nextcloud </Directory> </VirtualHost> |
| Direktive | Zweck | Folge ohne diese Einstellung |
|---|---|---|
ServerName nextcloud.newdomain.de |
wählt diesen VirtualHost für den Host-Header nextcloud.newdomain.de. Diese Zeile ist domainabhängig und beim Domainumzug anzupassen. |
Anfragen landen im Default-VirtualHost |
Alias /nextcloud |
Zugriff unter dem Pfad /nextcloud an diesem Host. Der Alias bedient zugleich die alte URL olddomain.de/nextcloud/…: Caddy reicht DAV-/OCS-Pfade mit unverändertem Pfad (/nextcloud/remote.php/…) und umgeschriebenem Host nextcloud. an diesen VirtualHost durch. Nextcloud leitet seinen Webroot aus dem Skriptpfad ab; die Antworten enthalten dadurch /nextcloud/remote.php/dav/… – so, wie alte Clients es erwarten. |
Ohne den Alias findet Apache unter /nextcloud/… keine Datei (404); weder der Direktzugriff /nextcloud/ an diesem Host noch die Durchreichung der alten URL funktionieren. |
RemoteIPHeader X-Forwarded-For / RemoteIPInternalProxy 192.178.1.1 |
mod_remoteip setzt die Client-IP aus X-Forwarded-For, sofern die Verbindung von der Proxy-Adresse 192.178.1.1 stammt |
Logs und Nextcloud sehen nur die IP des Proxys |
SetEnvIf X-Forwarded-Proto "https" HTTPS=on |
Nextcloud erkennt, dass die ursprüngliche Anfrage per HTTPS erfolgte | Nextcloud erzeugt http://-URLs, Mixed Content/Redirect-Probleme |
Header always set Strict-Transport-Security … |
HSTS | – |
mit AllowOverride All |
Nextcloud-.htaccess |
Rewrite-Regeln wirkungslos |
Dav off |
deaktiviert mod_dav, damit Nextcloud WebDAV selbst abwickelt |
Konflikte mit mod_dav |
SetEnv HOME, HTTP_HOME |
Nextcloud-Home-Verzeichnis für PHP | – |
Hinweis: Domainabhängig bleiben ServerName nextcloud.newdomain.de und der Eintrag trusted_domains in der Nextcloud-config.php.
2.7 Sites aktivieren und deaktivieren
Die ursprünglichen Einzel-VirtualHosts (www.website1.de.conf, www.website3.de.conf, www.myblog.conf, www.website2.de.conf, www.website4.de.conf) sind durch das Makro in 000-default.conf ersetzt und müssen deaktiviert sein. Andernfalls existieren doppelte VirtualHosts für dieselben Namen. Bei der Variante ohne Makro mit Einzeldateien (Kapitel 2.4.1) sind die Dateien stattdessen aktiviert.
|
1 2 3 4 |
a2dissite www.website1.de www.website3.de www.myblog www.website2.de www.website4.de a2ensite 000-default nextcloud apache2ctl configtest systemctl reload apache2 |
Die Dateien selbst können in sites-available/ als Sicherung verbleiben.
2.8 Test der Apache-Konfiguration und Neustart
|
1 2 3 4 5 |
apache2ctl configtest # Syntaxprüfung ("Syntax OK") apache2ctl -S # VirtualHost-Zuordnung, Default-Server systemctl reload apache2 # Änderungen übernehmen (ohne Unterbrechung) systemctl restart apache2 # bei Moduländerungen (a2enmod/a2dismod) systemctl is-active apache2 |
2.9 Prüfung mit curl auf dem Webserver
Die Befehle sind auf dem Webserver selbst (webserver) auszuführen. Seiten URL ( myblog website1 …) und IP sind anzupassen. Ein Aufruf auf einem anderen Rechner liefert andere Ergebnisse (Kapitel 7.13).
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Namensbasierte VirtualHosts (Host-Header wird simuliert) for h in myblog website1 website2 website3 website4; do printf '%-14s' "$h"; curl -s -o /dev/null -w '%{http_code}\n' -H "Host: $h.newdomain.de" http://127.0.0.1/ done # Domainunabhängigkeit: beliebige Domain muss denselben VirtualHost treffen curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: myblog.example.org' http://127.0.0.1/ # Default-VirtualHost / Direktzugriff über Alias curl -s -o /dev/null -w '%{http_code}\n' http://192.178.1.100/www.myblog/ curl -s -o /dev/null -w '%{http_code}\n' http://192.178.1.100/nextcloud/ # unbekannter FQDN muss 403 liefern curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: foo.newdomain.de' http://127.0.0.1/www.website2.de/ |
3. Zusammenspiel zwischen Caddy und Apache
3.1 Zwei Entscheidungsebenen
Caddy entscheidet, an welchen Backend-Server eine Anfrage weitergeleitet wird. Apache entscheidet anschließend anhand des eingehenden Requests, insbesondere des Host-Headers und des Ports, welcher VirtualHost die Anfrage verarbeitet.
Beide Ebenen verwenden unabhängige Konfigurationen, die aufeinander abgestimmt sein müssen:
| Ebene | Eingabe | Entscheidung | Konfiguration |
|---|---|---|---|
| Caddy | TLS-SNI und Host-Header der Client-Anfrage | Site-Block (Domain) → Handler → Backend-Adresse (192.178.1.100) |
/usr/local/etc/caddy/Caddyfile, caddy.d/*.conf |
| Apache | lokale IP:Port und Host-Header der von Caddy gesendeten Anfrage | VirtualHost → DocumentRoot/Alias | /etc/apache2/sites-available/*.conf |
3.2 Ablauf im Detail
- Browser: ruft
https://myblog.newdomain.de/category/smart-home/auf. DNS liefert die öffentliche IP; TLS-SNI undHostlautenmyblog.newdomain.de. - Caddy – Site-Auswahl: Der Site-Block
myblog.newdomain.deim generierten Caddyfile passt auf SNI/Host. Das Wildcard-Zertifikat für*.newdomain.dewird für den TLS-Handshake verwendet. - Caddy – Backend-Auswahl: Der Handler
reverse_proxy 192.178.1.100legt das Ziel fest. Ein Port ist nicht angegeben, es gilt Port 80 (HTTP). - Neue Verbindung Caddy → Apache: Caddy öffnet eine eigene HTTP-Verbindung zu
192.178.1.100:80und sendet:
|
1 2 3 4 5 |
GET /category/smart-home/ HTTP/1.1 Host: myblog.newdomain.de X-Forwarded-For: X-Forwarded-Proto: https X-Forwarded-Host: myblog.newdomain.de |
- Apache – VirtualHost-Auswahl: Zieladresse
*:80;Host: myblog.newdomain.detrifftServerAlias myblog.*des MakrosWPSite→DocumentRoot /var/www/www.myblog. - Apache – Dateizuordnung:
/category/smart-home/existiert nicht als Datei; die.htaccess-RegelRewriteRule . index.php [L]übergibt an WordPress. - WordPress:
wp-config.phperkennt überX-Forwarded-ProtoHTTPS und setztWP_HOME/WP_SITEURLaufhttps://myblog.newdomain.de. WordPress erzeugt die Seite. - Rückweg: Apache → Caddy → Browser (TLS).
3.3 Weitergegebene Informationen
| Information | Verhalten bei reverse_proxy (Standardverhalten laut Caddy-Dokumentation) |
Verwendung |
|---|---|---|
| URI/Pfad und Query | unverändert | Auswahl von Dateien/Rewrites, WordPress-Routing |
Host |
wird unverändert mit dem ursprünglichen Hostnamen weitergegeben | Apache-VirtualHost-Auswahl |
X-Forwarded-For |
Client-IP | Logs; mod_remoteip (Nextcloud-VHost) |
X-Forwarded-Proto |
https |
HTTPS-Erkennung in WordPress (wp-config.php) und Nextcloud (SetEnvIf) |
X-Forwarded-Host |
ursprünglicher Hostname | derzeit nicht ausgewertet |
vom Client mitgesendete X-Forwarded-*-Header |
werden von Caddy überschrieben, sofern keine vertrauenswürdigen vorgeschalteten Proxys konfiguriert sind | verhindert gefälschte Weitergabeinformationen |
In den generierten Handlern (reverse_proxy 192.178.1.100 { }) sind keine zusätzlichen Header-Direktiven gesetzt; es gelten ausschließlich die Standardwerte. Dass X-Forwarded-Proto und Host tatsächlich bei Apache ankommen, ist durch die funktionierende HTTPS-Erkennung und die funktionierende VirtualHost-Auswahl belegt.
3.4 Warum der Apache-Pfad nicht dem externen Pfad entspricht
Der externe Pfad (/category/smart-home/) ist unabhängig von der Dateisystemstruktur. Die Zuordnung zu /var/www/www.myblog erfolgt ausschließlich durch den VirtualHost (DocumentRoot). Bei der Alt-Domain war das Verzeichnis dagegen Teil der URL (/myblog/…, /www.website4.de/…); der Präfix wird von Caddy entfernt (Kapitel 4.5). Beim Direktzugriff über die IP ist der Präfix wieder Teil des Pfads (/www.myblog/…, Alias). Aus diesen drei Pfadformen folgt, dass WordPress seine Basis-URL je nach Zugriffsart bestimmen muss (Kapitel 5).
3.5 Konkrete Beispiele für notwendige Abstimmung
Beispiel 1: neue Site shop. Erforderlich sind zwei zusammengehörige Änderungen:
- Caddy: Subdomain
shopmit Handlerreverse_proxy 192.178.1.100. - Apache:
Use WPSite shop www.shop.examplein000-default.confplus Verzeichnis/var/www/www.shop.example.
Fehlt der Caddy-Eintrag, trifft die Anfrage den Wildcard-Block ohne Handler und erreicht Apache nicht. Fehlt der Apache-Eintrag, erreicht die Anfrage den Default-VirtualHost und wird mit 403 abgewiesen (für unbekannte FQDN getestet).
Beispiel 2: Durchreichung /nextcloud (alte Domain). Der Handler enthält header_up Host nextcloud.{args[0]}. Ohne diese Zeile erhielte Apache Host: olddomain.de; kein VirtualHost passt, der Default-VirtualHost sperrt den FQDN mit 403, und Nextcloud würde den Host zudem als nicht vertrauenswürdig behandeln. Mit der Zeile wird der Nextcloud-VirtualHost nextcloud.newdomain.de ausgewählt.
3.6 Auswirkungen falscher Host- oder Pfadkonfiguration
| Fehler | Wirkung |
|---|---|
| Host-Header zeigt auf unbekannten Namen | Default-VirtualHost: 403 (FQDN) oder Inhalt von /var/www/html (IP) |
| Backend-Port/Adresse in Caddy falsch | 502/Timeout bei Caddy |
RewriteBase / oder absolute Substitution (/index.php) in .htaccess |
funktioniert nur bei DocumentRoot-Zugriff, nicht beim Alias-Direktzugriff (und umgekehrt) |
WP_HOME/WP_SITEURL passen nicht zur aufgerufenen URL |
fehlende CSS/JS, Redirect-Schleifen, falsche Links |
Fehlendes X-Forwarded-Proto-Handling |
WordPress/Nextcloud erzeugen http://-URLs hinter HTTPS |
4. OPNsense / Caddy
4.1 Dateien und Aufbau
| Datei/Verzeichnis | Funktion |
|---|---|
/usr/local/etc/caddy/Caddyfile |
automatisch von OPNsense generiert („DO NOT EDIT“); wird aus den Einträgen der GUI erzeugt (Reverse Proxy → Domänen, Subdomänen, Handler) |
/usr/local/etc/caddy/caddy.d/*.global |
benutzerdefinierte globale Optionen (in den globalen Block importiert) |
/usr/local/etc/caddy/caddy.d/*.conf |
benutzerdefinierte Site-Blöcke (am Ende des generierten Caddyfiles importiert) |
/usr/local/etc/caddy/certificates/.pem / .key |
Zertifikat und Schlüssel aus dem OPNsense-Trust-Store |
/var/log/caddy/caddy.log |
Caddy-Log |
Eigene Konfigurationen gehören ausschließlich nach caddy.d/. Änderungen am generierten Caddyfile gehen bei der nächsten GUI-Änderung verloren.
Nach Änderungen an der Caddy-Konfiguration muss der Caddy-Dienst neu geladen bzw. neu gestartet werden.
|
1 |
service caddy restart |
4.2 Generierter Caddyfile
Pfad: /usr/local/etc/caddy/Caddyfile. Zertifikatsdateinamen und die ACME-Kontakt-E-Mail sind durch Platzhalter ersetzt.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# DO NOT EDIT THIS FILE -- OPNsense auto-generated file # caddy_user=root # Global Options { log { output net unixgram//var/run/caddy/log.sock { } format json { time_format rfc3339 } } servers { protocols h1 h2 } email grace_period 10s skip_install_trust import /usr/local/etc/caddy/caddy.d/*.global } # Reverse Proxy Configuration *.newdomain.de { tls /usr/local/etc/caddy/certificates/.pem /usr/local/etc/caddy/certificates/.key { } } newdomain.de { tls /usr/local/etc/caddy/certificates/.pem /usr/local/etc/caddy/certificates/.key { } } nextcloud.newdomain.de { handle { reverse_proxy 192.178.1.100 { } } } website2.newdomain.de { handle { reverse_proxy 192.178.1.100 { } } } myblog.newdomain.de { handle { reverse_proxy 192.178.1.100 { } } } website4.newdomain.de { handle { reverse_proxy 192.178.1.100 { } } } website1.newdomain.de { handle { reverse_proxy 192.178.1.100 { } } } website3.newdomain.de { handle { reverse_proxy 192.178.1.100 { } } } import /usr/local/etc/caddy/caddy.d/*.conf |
Erläuterung
| Element | Funktion |
|---|---|
Globaler Block: servers { protocols h1 h2 } |
HTTP/1.1 und HTTP/2; HTTP/3 deaktiviert |
email, grace_period, skip_install_trust |
ACME-Kontakt, Wartezeit beim Beenden, keine Installation einer lokalen CA im System |
*.newdomain.de { tls … } und newdomain.de { tls … } |
laden das Wildcard-Zertifikat (SAN newdomain.de) aus dem Trust-Store. Die Blöcke enthalten bewusst keine Handler. Caddy verwendet das geladene Zertifikat für alle passenden SNI-Namen, auch für die einzelnen Subdomain-Blöcke ohne eigene tls-Direktive. |
Subdomain-Blöcke mit handle { reverse_proxy 192.178.1.100 } |
Backend-Auswahl für die Webserver-Sites (myblog, website1, website2, website3, website4, nextcloud) |
import …/caddy.d/*.conf (letzte Zeile) |
bindet newdomain-redirect.conf ein |
4.3 Zertifikate, ACME und DNS-01
- Das Zertifikat
*.newdomain.de(SANnewdomain.de) wird vom OPNsense-Plugin os-acme-client per DNS-01-Challenge über die deSEC-API (API-Token:) ausgestellt. - DNS-01 ist für Wildcard-Zertifikate zwingend erforderlich. Der Nachweis erfolgt über einen TXT-Eintrag unter
_acme-challenge.bei deSEC; der Webserver oder Port 80 müssen dafür nicht erreichbar sein. - Caddy bezieht das Zertifikat aus dem OPNsense-Trust-Store (
/usr/local/etc/caddy/certificates/.pem/.key) und erneuert es nicht selbst. Die Erneuerung übernimmt os-acme-client. - Nach einem Domainwechsel sind in os-acme-client neues Zertifikat (neue Domain, Wildcard + Basisdomain) auszustellen und in Caddy (Domain-Eintrag) auszuwählen.
-
Zertifikat der Alt-Domain
olddomain.de: Das vorhandene Wildcard-Zertifikat deckt diese Domain nicht ab. Für die Alt-Domain wird daher in der OPNsense-Caddy-Konfiguration ein eigenes SSL-Zertifikat verwendet. Über die Einstellung „Automatisches HTTPS“ kann Caddy das benötigte Zertifikat automatisch beziehen und verwalten. Dadurch kann die Alt-Domain weiterhin per HTTPS erreicht und anschließend auf die neue Domain weitergeleitet werden.
4.4 Reverse-Proxy-Ziele und Header
Alle Webserver-Handler verwenden den Standard von reverse_proxy (Kapitel 3.3): kein zusätzlicher Header, Host bleibt erhalten. Zusätzliche Header-Direktiven sind nur im Block der Alt-Domain gesetzt (header_up Host, Kapitel 4.5).
4.5 Die Datei newdomain-redirect.conf (Weiterleitung der alten Domain)
Pfad: /usr/local/etc/caddy/caddy.d/newdomain-redirect.conf
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# /usr/local/etc/caddy/caddy.d/newdomain-redirect.conf # Alte Domain -> neue Domain, Pfad + Query bleiben erhalten. # Neue Basisdomain + Webserver-Upstream stehen nur in der Zeile "import legacy_redirect " ganz unten. # Nextcloud-Client-Pfade (WebDAV/OCS) werden NICHT umgeleitet, sondern durchgereicht: bei einem Redirect auf einen anderen # Host verwerfen WebDAV-Clients den Authorization-Header. # args: 0 = Regex altes Pfadpraefix, 1 = Subdomain, 2 = neue Basisdomain (legacy_site) { @legacy_{args[1]} path_regexp ^/(?:{args[0]})(?:/|$) route @legacy_{args[1]} { uri path_regexp ^/(?:{args[0]})(?:/|$) / redir https://{args[1]}.{args[2]}{uri} 301 } } # args: 0 = neue Basisdomain, 1 = Webserver-Upstream (ip:port) (legacy_redirect) { route { @nextcloud_api path_regexp ^/nextcloud/(?:remote\.php|ocs|status\.php|public\.php|\.well-known)(?:/|$) route @nextcloud_api { reverse_proxy {args[1]} { header_up Host nextcloud.{args[0]} } } import legacy_site (?:www\.)?myblog myblog {args[0]} import legacy_site www\.website1\.de website1 {args[0]} import legacy_site www\.website2(?:\.de)? website2 {args[0]} import legacy_site www\.website3\.de website3 {args[0]} import legacy_site www\.website4\.de website4 {args[0]} import legacy_site nextcloud nextcloud {args[0]} redir https://myblog.{args[0]}{uri} 301 } } olddomain.de { import legacy_redirect newdomain.de 192.178.1.100:80 } |
Funktionsweise
| Element | Wirkung |
|---|---|
olddomain.de { import legacy_redirect newdomain.de 192.178.1.100:80 } |
einzige Stelle, an der die neue Domain und das Backend stehen. Bei einem weiteren Domainwechsel ist nur diese Zeile (und der Name des Site-Blocks, falls die Alt-Domain wechselt) anzupassen. |
Snippet (legacy_site) |
wiederverwendbare Regel: erkennt einen alten Pfadpräfix ({args[0]}, regulärer Ausdruck), entfernt ihn und leitet auf https://.? mit Status 301 weiter. {uri} enthält nach dem Umschreiben den Rest-Pfad inklusive Query. |
route @legacy_… { uri path_regexp …; redir … } |
route erzwingt die geschriebene Reihenfolge (zuerst uri, dann redir). Außerhalb von route gilt die feste Caddy-Direktivenreihenfolge, in der redir vor uri ausgeführt würde; der Präfix bliebe dann im Ziel-URL erhalten. |
Regex ^/(?:{args[0]})(?:/ |
$) |
@nextcloud_api … reverse_proxy {args[1]} { header_up Host nextcloud.{args[0]} } |
Nextcloud-Client-Pfade (remote.php, ocs, status.php, public.php, .well-known) unter /nextcloud/ werden nicht umgeleitet, sondern transparent an Apache durchgereicht. Der Pfad bleibt /nextcloud/…; der Host wird auf nextcloud. gesetzt, damit der Nextcloud-VirtualHost greift. Begründung: Bei einem Redirect auf einen anderen Host verwerfen WebDAV-Clients den Authorization-Header; zudem erwarten alte Clients Antworten mit dem Präfix /nextcloud/. |
import legacy_site nextcloud nextcloud … |
Browser-Pfade unter /nextcloud/ (z. B. /nextcloud/index.php/s/) werden auf https://nextcloud.newdomain.de/… umgeleitet. |
letzte Zeile redir https://myblog.{args[0]}{uri} 301 |
Auffangregel: alles Übrige wird mit unverändertem Pfad und Query auf die Blog-Subdomain umgeleitet; kein alter Link endet in einem 404 des Proxys. |
Die Reihenfolge der Zeilen ist relevant: Die erste zutreffende route beendet die Verarbeitung. Die spezifischen Regeln stehen deshalb vor der Auffangregel.
| Aufruf | Ergebnis |
|---|---|
/myblog/category/smart-home/ |
301 → https://myblog.newdomain.de/category/smart-home/ |
/myblog/?p=12&x=1 |
301 → https://myblog.newdomain.de/?p=12&x=1 |
/www.websiteN.de/index.php/gallery/category/item/ |
301 → https://websiteN.newdomain.de/index.php/gallery/category/item/ |
/www.websiteN.de/wp-admin_custom |
301 → https://websiteN.newdomain.de/wp-admin_custom |
/www.websiteN.de/wp-admin |
301 → https://websiteN.newdomain.de/wp-admin |
/www.websiteN.de/?s=a |
301 → https://websiteN.newdomain.de/?s=a |
/nextcloud/index.php/s/AbC123?dir=/x |
301 → https://nextcloud.newdomain.de/index.php/s/AbC123?dir=/x |
/nextcloud/remote.php/dav/… |
kein Redirect; durchgereicht an Apache (207 bei PROPFIND, Antworten mit /nextcloud/remote.php/dav/…-Pfaden) |
/index.php/s/abc (kein bekannter Präfix) |
301 → https://myblog.newdomain.de/index.php/s/abc |
Hinweis: Die Auffangregel leitet auch alte Nextcloud-Links ohne /nextcloud-Präfix auf die Blog-Subdomain um.
4.6 Temporärer Access-Log zur Diagnose
Zur Analyse, welche Pfade ein Client auf der Alt-Domain abfragt, wurde zeitweise ein Access-Log im Block der Alt-Domain aktiviert. Pfad: /usr/local/etc/caddy/Caddyfile:
|
1 2 3 4 5 6 7 |
olddomain.de { log { output file /var/log/caddy/newdomain-redirect-access.log format console } import legacy_redirect newdomain.de 192.178.1.100:80 } |
Caddy neu starten und lezten 20 Logs ausgeben:
|
1 2 |
service caddy restart tail -n 20 /var/log/caddy/newdomain-redirect-access.log |
Das Log enthält je Anfrage Methode, Pfad, Status und Antwort-Header. Nach der Diagnose ist der log-Block wieder zu entfernen, damit die Datei nicht unbegrenzt wächst.
4.7 Domainmigration
4.7.1 Gründe
Alte Links (Blog-Beiträge, Lesezeichen, externe Verweise, Nextcloud-Freigaben, DAV-Clients) bleiben dauerhaft im Umlauf. Alte Links dürfen nicht zu 404-Fehlern führen und müssen die Inhalte unter der neuen Adresse erreichen.
4.7.2 Zuständigkeit der Ebenen
| Ebene | Aufgabe | Im Projekt |
|---|---|---|
| DNS-Auflösung | Name → IP-Adresse. Verändert keine URL, entscheidet nur, welcher Server die Anfrage erhält. | olddomain.de und newdomain.de zeigen beide auf die öffentliche IP und damit auf dieselbe OPNsense |
| HTTP-Redirect | Der Server antwortet mit 301 und neuer URL; der Client stellt eine neue Anfrage. Die Adresszeile ändert sich. |
Caddy redir in newdomain-redirect.conf |
| Reverse Proxy | Der Server leitet die Anfrage serverseitig weiter; der Client bemerkt nichts, die URL bleibt. | Caddy reverse_proxy (Webserver-Sites; /nextcloud-DAV-Pfade der Alt-Domain) |
| URL-Rewriting | interne Umschreibung des Pfads ohne Wissen des Clients | Caddy uri path_regexp (Präfix entfernen); Apache mod_rewrite (index.php) |
| WordPress-interne URLs | Basis-URL, in der WordPress seine Links und Asset-Adressen erzeugt | WP_HOME/WP_SITEURL in wp-config.php; absolute URLs in Beiträgen (Datenbank) |
4.7.3 Erkennung alter URLs
Alte URLs werden am Präfix des Pfads erkannt (/myblog, /www.myblog, /www.website1.de, /www.website2.de, /www.website3.de, /www.website4.de, /nextcloud). Der Präfix bestimmt die Ziel-Subdomain; der Rest des Pfads und die Query werden übernommen.
4.7.4 Vermeidung von 404
- Jeder bekannte Präfix besitzt eine eigene Regel.
- Die Auffangregel leitet alles Übrige auf die Blog-Subdomain.
- Die Query wird bei jeder Regel mitgeführt (
{uri}). - Nextcloud-Client-Pfade werden durchgereicht statt umgeleitet (Authorization-Header).
4.7.5 Änderungsstellen bei einem weiteren Domainumzug
| Ort | Änderung |
|---|---|
| deSEC/DNS | neue Domain: A-Record und Wildcard-CNAME |
| os-acme-client | Zertifikat für *. und |
| OPNsense/Caddy GUI | Wildcard-Domain, Basisdomain und die Subdomain-Einträge |
newdomain-redirect.conf |
Zeile import legacy_redirect ; Name des Site-Blocks, falls auch die Alt-Domain wechselt |
nextcloud.conf |
ServerName nextcloud. |
Nextcloud config.php |
trusted_domains (und gegebenenfalls overwrite.cli.url) |
Apache-Makro, wp-config.php, .htaccess |
keine Änderung (domainunabhängig) |
Optional ließe sich die Anzahl der Subdomain-Einträge in der Caddy-GUI durch einen Handler am Wildcard-Eintrag *.newdomain.de reduzieren. Diese Variante wurde nicht umgesetzt und nicht im Produktivsystem getestet.
5. WordPress und URL-Struktur
5.1 Grundsatz: Zuständigkeit der URL-Ebenen
| Ebene | Verarbeitet | Nicht zuständig für |
|---|---|---|
| Caddy | Domain/Host, Zertifikat, Backend-Auswahl, Weiterleitung alter Domain und alter Pfadpräfixe | Inhalte, Permalinks, WordPress-Basis-URL |
| Apache | VirtualHost-Auswahl (Host-Header), Alias (Direktzugriff per IP), mod_rewrite (.htaccess: Umleitung nicht existierender Pfade auf index.php) |
Basis-URL der Anwendung, Inhalte |
| WordPress | Permalink-Auflösung, Erzeugung aller Links und Asset-Adressen aus WP_HOME/WP_SITEURL, kanonische Weiterleitung (redirect_canonical) auf die konfigurierte Basis-URL |
Auswahl des Backends, Auswahl des VirtualHosts |
Wird eine Anfrage von einer früheren Ebene beantwortet (z. B. Redirect durch Caddy), erreicht sie die folgenden Ebenen nicht.
5.2 wp-config.php
Pfad je Site: /var/www/www.<websiteN>/wp-config.php.
Die Datei enthält Datenbankzugangsdaten, Schlüssel/Salts sowie die Logik zur Bestimmung von WP_HOME und WP_SITEURL abhängig von der Zugriffsart. WordPress lädt sie bei jedem Request vor wp-settings.php. Die Datei wird nur von PHP/WordPress über Apache verwendet; Caddy und Apache lesen sie nicht als Konfigurationsdatei.
Zugangsdaten sind Platzhalter. Die Schlüssel und Salts sind Beispielwerte und müssen bei einer Migration unverändert aus der bestehenden Installation übernommen werden.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<?php define('WP_CACHE', true); // nur falls ein Cache-Plugin dies verlangt (Bestandteil der bestehenden Installation) /** MySQL settings */ define( 'DB_NAME', "<db_name>" ); define( 'DB_USER', "<db_user>" ); define( 'DB_PASSWORD', "<db_password>" ); define( 'DB_HOST', "localhost" ); define( 'DB_CHARSET', 'utf8mb4' ); define( 'DB_COLLATE', '' ); /** Authentication Unique Keys and Salts. */ // Beispielwerte; je Installation eigene Werte verwenden (bei einer Migration die Werte der bestehenden Datei unverändert übernehmen) define( 'AUTH_KEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ); define( 'SECURE_AUTH_KEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ); define( 'LOGGED_IN_KEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ); define( 'NONCE_KEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ); define( 'AUTH_SALT', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ); define( 'SECURE_AUTH_SALT', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ); define( 'LOGGED_IN_SALT', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ); define( 'NONCE_SALT', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' ); $table_prefix = 'wp_'; define('WP_DEBUG', false); /** Absolute path to the WordPress directory. */ if (!defined('ABSPATH')) { define('ABSPATH', dirname(__FILE__) . '/'); } /* ===== URL-Block (Kapitel 5.3) ===== */ /** Sets up WordPress vars and included files. */ require_once(ABSPATH . 'wp-settings.php'); |
Der URL-Block steht nach der ABSPATH-Definition und vor require_once(ABSPATH . 'wp-settings.php');. Nach dem Laden von wp-settings.php wäre es für define('WP_HOME', …) zu spät.
Frühere Debug-Zeilen der Form file_put_contents('/tmp/req.log', …) am Dateianfang werden für den Betrieb nicht benötigt und sind zu entfernen (Kapitel 7.8).
5.3 URL-Block
Der URL-Block legt fest, unter welcher öffentlichen Adresse WordPress betrieben wird. Er sorgt dafür, dass WordPress auch hinter einem Reverse Proxy die korrekte Domain und das richtige HTTPS-Schema verwendet und nicht auf interne Serveradressen oder alte Domains verweist.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* ------------------------------------------------------------------ * URL-Handling: Reverse Proxy (beliebige Domain) oder Direktzugriff per IP/Kurzname * Pro Site nur $direct_path anpassen (Alias-Pfad im Default-VHost). Domain wird nicht benoetigt. * Einfuegen vor: require_once(ABSPATH . 'wp-settings.php'); * ------------------------------------------------------------------ */ // Alias-Pfad des Default-VHosts; absichtlich zerlegt, damit Copy&Paste daraus keinen Link erzeugt $direct_path = '/www' . '.websiteN'; if (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https') { $_SERVER['HTTPS'] = 'on'; } $wp_host = $_SERVER['HTTP_HOST'] ?? ''; if ($wp_host !== '') { // nicht bei WP-CLI/Cron ohne HTTP-Kontext $wp_name = preg_replace('/:\d+$/', '', $wp_host); $wp_direct = filter_var($wp_name, FILTER_VALIDATE_IP) || strpos($wp_name, '.') === false; $wp_scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://'; $wp_url = $wp_scheme . $wp_host . ($wp_direct ? $direct_path : ''); define('WP_HOME', $wp_url); define('WP_SITEURL', $wp_url); } |
Einzige site-spezifische Angabe ist $direct_path (Alias-Pfad aus 000-default.conf):
| Bestandteil | Funktion | Folge ohne diese Einstellung |
|---|---|---|
HTTP_X_FORWARDED_PROTO === 'https' → $_SERVER['HTTPS'] = 'on' |
HTTPS-Erkennung hinter dem Proxy. Apache sieht nur eine HTTP-Verbindung von Caddy; die ursprüngliche Verschlüsselung ist ausschließlich im Header erkennbar. | WordPress erzeugt http://-URLs; Mixed Content, Weiterleitungen auf http:// |
Host aus HTTP_HOST |
Der vom Client (bzw. von Caddy durchgereichte) Host bestimmt die Basis-URL. Der Hostname wird nicht in der Datei hinterlegt. | Domainwechsel erfordert Änderung jeder wp-config.php |
$wp_direct (IP-Adresse oder Name ohne Punkt) |
Unterscheidung Direktzugriff ↔ Proxy-Zugriff. Bei Direktzugriff wird $direct_path angehängt. |
Beim Direktzugriff fehlen CSS und Links (Basis-URL ohne Alias-Pfad) |
WP_HOME, WP_SITEURL |
überschreiben die in der Datenbank gespeicherten Optionen home und siteurl zur Laufzeit |
WordPress verwendet die Datenbankwerte (Alt-Domain) und leitet auf diese um |
if ($wp_host !== '') |
kein HTTP_HOST bei WP-CLI oder Cron |
Warnungen/Fehler in CLI-Kontexten; ohne Host bleiben die Datenbankwerte wirksam |
Syntaxprüfung und Kontrolle
|
1 2 3 |
php -l /var/www/www.websiteN/wp-config.php curl -s -H 'Host: websiteN.newdomain.de' -H 'X-Forwarded-Proto: https' http://127.0.0.1/ | grep -o 'href="[^"]*"' | head -5 curl -s http://192.178.1.100/www.websiteN/ | grep -o 'href="[^"]*"' | head -5 |
Die ersten Links müssen mit https://websiteN.newdomain.de/ bzw. http://192.178.1.100/www.websiteN/ beginnen.
5.4 Permalinks und .htaccess
Pfad je Site: /var/www/www.websiteN/.htaccess
Funktion: mod_rewrite leitet alle Pfade, die weder Datei noch Verzeichnis sind, an index.php; WordPress löst den Permalink auf. Voraussetzung: AllowOverride All (Kapitel 2.4) und aktiviertes Modul rewrite.
Einheitlicher Block für alle Sites (Subdomain-Zugriff und Alias-Direktzugriff). Der Inhalt vorhandener .htaccess-Dateien ist durch diesen Block zu ersetzen oder bei vorhandener weiterer Konfiguration entsprechend anzupassen. Eventuell von Plugins ergänzte Abschnitte außerhalb von # BEGIN WordPress … # END WordPress bleiben unberührt.
|
1 2 3 4 5 6 7 8 9 10 |
# BEGIN WordPress <IfModule mod_rewrite.c> RewriteEngine On RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . index.php [L] </IfModule> # END WordPress |
Warum kein RewriteBase und keine absolute Substitution:
| Variante | Subdomain (DocumentRoot /) |
Alias-Direktzugriff (/www.websiteN/) |
|---|---|---|
RewriteBase / + RewriteRule . /index.php (WordPress-Standard) |
funktioniert | falsch: /index.php liegt im Default-DocumentRoot, nicht im Alias-Verzeichnis |
RewriteBase /www.website3.de/ + RewriteRule . /www.websiteN/index.php |
falsch: dieser Pfad existiert im DocumentRoot der Subdomain nicht (404) | funktioniert |
RewriteRule . index.php ohne RewriteBase (relative Substitution) |
funktioniert | funktioniert |
Bei relativer Substitution ergänzt Apache das jeweilige URL-Präfix des Verzeichnisses selbst. Dadurch ist keine Angabe eines Hosts oder Pfads in der Datei erforderlich. Der frühere Sonderfall RewriteCond %{HTTP_HOST} !^websiteN\.<domain>$ (domainabhängig) entfällt vollständig.
Kontrolle:
|
1 2 3 4 |
# über Subdomain (Host-Header) curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: myblog.newdomain.de' http://127.0.0.1/category/smart-home/ # über Alias-Direktzugriff curl -s -o /dev/null -w '%{http_code}\n' http://192.178.1.100/www.myblog/category/smart-home/ |
Beide Aufrufe müssen den Status 200 (Permalink existiert) liefern; ein 404 kommt dann von WordPress selbst (Permalink existiert nicht), nicht von Apache.
5.5 Weiterleitungen innerhalb von WordPress
WordPress leitet Anfragen auf die kanonische URL um, wenn Host oder Pfad von WP_HOME abweichen (redirect_canonical). Mit dem URL-Block aus Kapitel 5.3 entspricht WP_HOME immer dem aufgerufenen Host; WordPress löst deshalb keine Weiterleitung auf die Alt-Domain mehr aus. Ohne den Block (Datenbankwerte = Alt-Domain) würde jede Anfrage über die neue Subdomain zurück auf die Alt-Domain umgeleitet und dort von Caddy erneut auf die neue Domain (Kapitel 7.2).
5.6 Alte Domainnamen in der Datenbank
- Die Optionen
homeundsiteurl(Tabellewp_options) enthalten in der Regel die Alt-Domain. SolangeWP_HOME/WP_SITEURLdefiniert sind, werden diese Werte zur Laufzeit überschrieben; eine Änderung in der Datenbank ist für die Funktion nicht erforderlich. - Absolute URLs in Beitragsinhalten, Widget-/Theme-Optionen und Plugin-Einstellungen bleiben unverändert in der Datenbank. Solche Links werden durch die Caddy-Weiterleitung (Kapitel 4.5) auf die neue Adresse umgeleitet, solange Pfadpräfix und Rest-Pfad mit den Regeln übereinstimmen.
Lesende Kontrolle (Zugangsdaten aus wp-config.php):
|
1 2 |
mysql -u <db_user> -p <db_name> -e "SELECT COUNT(*) FROM wp_posts WHERE post_content LIKE '%olddomain.de%';" mysql -u <db_user> -p <db_name> -e "SELECT option_name FROM wp_options WHERE option_value LIKE '%olddomain.de%' LIMIT 20;" |
Serialisierte Werte in der Datenbank dürfen nicht per einfachem SQL-REPLACE geändert werden (gespeicherte Zeichenlängen würden ungültig). Eine Ersetzung ist, falls erforderlich, mit einem serialisierungsfähigen Werkzeug (z. B. WP-CLI search-replace) und vorheriger Sicherung vorzunehmen.
5.7 Statische Verweise in Dateien
Verweise auf die Alt-Domain können in Theme- und Plugin-Dateien stehen (z. B. fest eingetragene URLs in Templates).
|
1 |
grep -rIl 'olddomain.de' /var/www/www.websiteN/wp-content 2>/dev/null |
5.8 Redirection-Plugin und alte Weiterleitungsregeln
- Es ist im WordPress-Backend zu prüfen, ob auf einer der Websites ein Weiterleitungs-Plugin (z. B. „Redirection“) aktiviert ist und Weiterleitungsregeln enthält. Dazu sind die aktiven Plugins sowie die darin hinterlegten Weiterleitungsregeln zu kontrollieren.
- Doppelte oder nicht mehr benötigte Redirects: Weiterleitungen der Alt-Domain gehören ausschließlich in
newdomain-redirect.conf(Caddy). Weder Apache noch WordPress enthalten Regeln, die dieselbe Weiterleitung ein zweites Mal durchführen.
Kontrolle: Weiterleitungsregeln in .htaccess-Dateien und Apache-Konfiguration:
|
1 |
grep -nEi 'Redirect|RewriteRule.*\[R' /var/www/www.*/.htaccess /etc/apache2/sites-available/*.conf |
Erwartetes Ergebnis: Der Befehl liefert keine Ausgabe. Weiterleitungen von alten auf neue Domains werden ausschließlich in Caddy konfiguriert. Treffer außerhalb der Nextcloud-Dateien (/.well-known/carddav, /.well-known/caldav in der Nextcloud-.htaccess) sind einzeln zu bewerten.
6. Alte URLs und URL-Remapping
6.1 Reihenfolge der Ebenen
Eine Anfrage durchläuft die Ebenen in fester Reihenfolge. Die erste Ebene, die antwortet, beendet die Verarbeitung:
|
1 2 3 |
1. Caddy Redirect (redir) oder Weiterreichen (reverse_proxy) 2. Apache VirtualHost → Alias/DocumentRoot → mod_rewrite (.htaccess) 3. WordPress Rewrite-Auflösung (Permalinks) → kanonische Weiterleitung → Plugin-Weiterleitungen → Ausgabe |
Datenbankeinträge und statische Verweise sind keine Verarbeitungsebenen; sie liefern lediglich die URLs, die in Antworten stehen und vom Client später erneut aufgerufen werden.
6.2 Übersicht
| Nr. | Ebene | Einsatz | Konfigurationsort | Priorität | Risiko bei doppelter Weiterleitung |
|---|---|---|---|---|---|
| 1 | HTTP-Redirect | alte Domain/Pfadpräfix → neue Domain | Caddy: caddy.d/newdomain-redirect.conf |
höchste (vor Apache und WordPress) | Schleife, wenn eine spätere Ebene zurück auf die Alt-Domain leitet |
| 2 | Reverse Proxy | Weiterleiten ohne sichtbare URL-Änderung (Webserver-Sites; /nextcloud-DAV-Pfade der Alt-Domain) |
Caddy: generierter Caddyfile, newdomain-redirect.conf |
wie 1 | falscher Host-Header ⇒ falscher VirtualHost (Kapitel 3.5) |
| 3 | Apache Rewrite | nicht existierende Pfade → index.php |
/var/www//.htaccess |
nach VirtualHost-Auswahl, vor WordPress | absolute Substitution/RewriteBase ⇒ 404 bei Alias oder Subdomain (Kapitel 5.4) |
| 4 | WordPress Rewrite | Permalink → Beitrag/Seite | WordPress (Einstellungen → Permalinks) | nach Apache | bei fehlerhafter .htaccess nicht erreichbar |
| 5 | Plugin-Redirect | einzelne Pfade → andere Pfade | Plugin-Konfiguration im WordPress-Backend | innerhalb WordPress, vor der Ausgabe | Schleifen durch Regeln, die auf die Alt-Domain zeigen [Einsatz: zu ergänzen] |
| 6 | Datenbankeinträge | absolute URLs in Inhalten/Optionen | MySQL (wp_options, wp_posts, …) |
– | alte Links erzeugen Aufrufe der Alt-Domain (werden von Ebene 1 abgefangen) |
| 7 | Statische Verweise | URLs in Theme-/Plugin-Dateien | /var/www//wp-content/… |
– | wie 6 |
6.3 Regeln gegen Konflikte
- Eine Weiterleitung, eine Ebene. Alt-Domain → Neu-Domain wird ausschließlich in Caddy umgesetzt.
- WordPress erhält die aufgerufene Domain als Basis-URL (Kapitel 5.3). Dadurch erzeugt WordPress keine Weiterleitung zurück auf die Alt-Domain.
- Apache enthält keine Host- oder Domainangaben in Rewrite-Regeln (Kapitel 5.4).
- Nextcloud-Client-Pfade werden nicht umgeleitet, sondern durchgereicht (Kapitel 4.5).
6.4 Problem: mehrere Ebenen leiten dieselbe URL um
| Konstellation | Wirkung |
|---|---|
| Caddy leitet Alt → Neu, WordPress leitet Neu → Alt (Datenbankwert als Basis-URL) | Endlosschleife; Browser meldet „zu viele Weiterleitungen“ |
| Caddy und ein Plugin leiten dieselbe Alt-URL auf unterschiedliche Ziele | Ergebnis abhängig davon, welche Ebene zuerst antwortet (immer Caddy); die Plugin-Regel bleibt wirkungslos |
| Caddy-Weiterleitung ohne Query | Parameter (?p=, ?s=) gehen verloren (Kapitel 7.4) |
| Weiterleitung auf anderen Host für WebDAV | Clients verlieren Authentifizierung (Kapitel 7.9) |
Kontrolle auf Schleifen
|
1 |
curl -sIL --max-redirs 5 https://olddomain.de/myblog/category/smart-home/ | grep -iE '^(HTTP|location)' |
Erwartet wird genau ein 301 und danach 200. Mehrere aufeinanderfolgende 301/302 oder der Abbruch durch --max-redirs zeigen eine Konfliktkonstellation.
7. Fehleranalyse und typische Probleme
Die folgenden Probleme können bei der Einrichtung auftreten. Jedes Problem ist nach dem Schema Ursache → Diagnose → Lösung → Kontrolle beschrieben. Soweit eine Ursache aus der Konfigurationsanalyse abgeleitet und nicht separat beobachtet wurde, ist dies vermerkt. Redirect-Schleifen wurden im Betrieb nicht protokolliert; die Konstellation ist in Kapitel 6.4 beschrieben.
7.1 Hartkodierte Domainnamen in Konfigurationsdateien
- Ursache: Apache-VirtualHost-Dateien mit
ServerName .,wp-config.phpmitif ($host === '.')und.htaccessmitRewriteCond %{HTTP_HOST} !^\.$. Bei jedem Domainwechsel wären alle Dateien zu ändern. - Diagnose:
1grep -rnE 'newdomain\.de|olddomain\.de' /etc/apache2/sites-available /var/www/www.*/wp-config.php /var/www/www.*/.htaccess - Lösung: Makro mit
ServerAlias .*(Kapitel 2.4), dynamischer URL-Block (Kapitel 5.3), einheitlicher.htaccess-Block (Kapitel 5.4). - Kontrolle: Aufruf mit beliebiger Domain im Host-Header liefert
200:
1curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: myblog.example.org' http://127.0.0.1/
7.2 wp-config.php ohne URL-Logik
- Ursache: Die Datei enthält weder die HTTPS-Erkennung noch
WP_HOME/WP_SITEURL. WordPress verwendet dann die Datenbankwerte (Alt-Domain) und leitet kanonisch dorthin um; Caddy leitet von dort wieder auf die neue Domain. - Diagnose:
12grep -n 'WP_HOME\|X_FORWARDED_PROTO' /var/www/www.websiteN.de/wp-config.phpcurl -sI https://website3.newdomain.de/ | grep -i '^location'
Kein Treffer im ersten Befehl bzw. ein Location auf die Alt-Domain im zweiten zeigt den Fehler.
- Lösung: URL-Block mit
$direct_path = '/www' . '.websiteN.de'einfügen. - Kontrolle:
curl -sIL --max-redirs 5 https://websiteN.newdomain.de/liefert direkt200.
7.3 .htaccess: RewriteBase und absolute Substitution
- Ursache: Der WordPress-Standardblock (
RewriteBase /,RewriteRule . /index.php) funktioniert nur bei Zugriff über das DocumentRoot. Bei einer Site warRewriteBase /www.websiteN.de/mit absoluter Substitution gesetzt (funktioniert nur beim Alias-Direktzugriff); bei einer weiteren Site sorgte eine Host-Bedingung für den Direktzugriff. - Diagnose: Permalink-Aufruf liefert
404oder den Inhalt eines falschen Verzeichnisses:
12curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: websiteN.newdomain.de' http://127.0.0.1/some/permalink/curl -s -o /dev/null -w '%{http_code}\n' http://192.178.1.100/www.websiteN.de/some/permalink/
- Lösung: einheitlicher Block aus Kapitel 5.2 in allen Sites (Website Pfad anpassen => $direct_path = ‘/www’ . ‘.websiteN’;)
- Kontrolle: beide Aufrufe liefern denselben Status (
200bzw. von WordPress ausgegebenes404).
7.4 Query-String geht bei der Weiterleitung verloren
- Ursache (aus der Konfigurationsanalyse abgeleitet): Die frühere Regel für den Blog verwendete als Ziel nur den Pfad (
{re..1}) ohne?{query}. Parameter wie?p=12oder?s=suchefielen weg. - Diagnose:
1curl -sI 'https://olddomain.de/myblog/?p=12&x=1' | grep -i '^location'
- Lösung:
(legacy_site)-Snippet mit{uri}(enthält Pfad und Query) statt{re..1}. - Kontrolle:
Location: https://myblog.newdomain.de/?p=12&x=1.
7.5 Caddy: Präfix bleibt im Ziel-URL
- Ursache: Caddy führt Direktiven nicht in Schreibreihenfolge aus, sondern in fester Reihenfolge;
redirwird voruriausgeführt. Ohnerouteentstünde ein Ziel mit dem alten Präfix (…/myblog/category/…). - Diagnose:
Location-Header enthält den alten Präfix. - Lösung:
route @legacy_ { uri path_regexp … ; redir … }(Kapitel 4.5).
7.6 Fehlende Apache-Module
- Ursache: Das Modul
macrowar nicht aktiviert. - Diagnose:
1apache2ctl configtest
Ausgabe: AH00526: Syntax error on line 13 of /etc/apache2/sites-enabled/000-default.conf: Invalid command '<Macro', perhaps misspelled or defined by a module not included in the server configuration
- Lösung:
12a2enmod macro rewriteapache2ctl configtest && systemctl restart apache2 - Kontrolle:
apache2ctl -M | grep -E 'macro|rewrite'listet beide Module;systemctl is-active apache2meldetactive.
7.7 Doppeltes www. im Direktzugriffspfad (Layout zerstört, 404)
- Ursache: Der Wert von
$direct_pathin derwp-config.phpeiner Site (website2) war fehlerhaft (/www.www.websiteN.de). WordPress erzeugt alle URLs (Stylesheets, Skripte, Links) aus diesem Pfad. Die erste Seite lädt über den korrekten Alias; alle erzeugten Adressen laufen ins Leere. - Diagnose:
12grep -nE 'direct_path|WP_HOME|WP_SITEURL|site_url' /var/www/www.websiteN/wp-config.phpcurl -s http://192.178.1.100/www.websiteN/ | grep -o 'href="[^"]*"' | head -5 - Lösung:
12sed -i -E 's|^\$direct_path = .*|$direct_path = \x27/www\x27 . \x27.websiteN.de\x27;|' /var/www/www.websiteN/wp-config.phpphp -l /var/www/www.websiteN/wp-config.php
- Kontrolle: Links beginnen mit
http://192.178.1.100/www.websiteN/; Seite im Browser mit hartem Neuladen (Strg+F5) prüfen.
7.8 Fehlerhafte oder unvollständige wp-config.php
- Ursache: Die Datei der Blog-Site enthielt eine Debug-Zeile
file_put_contents('/tmp/req.log', …am Anfang, die in der vorliegenden Fassung unvollständig war (Syntaxfehler, falls tatsächlich so in der Datei), sowie einen durch Markdown-Umwandlung beschädigten Alias-Pfad (Kapitel 7.12). Ob die Zeile im Dateisystem oder nur in der Terminalanzeige abgeschnitten war, wurde nicht abschließend geklärt. - Diagnose:
1php -l /var/www/www.myblog/wp-config.php grep -n '](http' /var/www/www.myblog/wp-config.php
- Lösung: Ersatz durch eine vollständige
wp-config.phpnach dem Gerüst aus Kapitel 5.2 ohne Debug-Zeile. - Kontrolle:
php -lmeldetNo syntax errors detected; die Site ist per Subdomain und IP erreichbar (bestätigt).
7.9 Alte WebDAV-/CalDAV-/CardDAV-URLs nach Redirect ohne Funktion
- Ursache: Ein HTTP-Redirect auf einen anderen Host führt bei DAV-Clients üblicherweise dazu, dass die Authentifizierung nicht mit übernommen wird bzw. die Anfrage nicht erneut mit gleicher Methode gestellt wird. Ein Browser folgt dem Redirect, ein DAV-Client in der Regel nicht zuverlässig.
- Diagnose: Access-Log auf der Alt-Domain (Kapitel 4.6) zeigt die Pfade, Methoden und Status des Clients:
1tail -n 20 /var/log/caddy/newdomain-redirect-access.log - Lösung: Client-Pfade (
remote.php,ocs,status.php,public.php,.well-known) unter/nextcloud/werden von Caddy mit unverändertem Pfad undHost: nextcloud.durchgereicht; der Alias/nextclouddes Nextcloud-VirtualHosts (Kapitel 2.6) ordnet den Pfad dem Nextcloud-Verzeichnis zu (Kapitel 4.5). - Kontrolle:
|
1 |
curl -si -u ':' -X PROPFIND -H 'Depth: 1' https://olddomain.de/nextcloud/remote.php/dav/ | grep -E '^HTTP|' | head -5 |
Erwartet werden HTTP/2 207 und /nextcloud/remote.php/dav/…. Beides ist im Projekt bestätigt. Im Caddy-Log erscheinen Anfragen eines CardDAV-Clients (Thunderbird) auf /nextcloud/remote.php/dav/addressbooks/users//… mit Status 207.
7.10 Nextcloud: Login-Schleife bei Direktzugriff über IP
- Beobachtung: Beim Aufruf von
http://192.178.1.100/nextcloud/…erscheint nach der Anmeldung erneut das Anmeldefenster. Überhttps://nextcloud.newdomain.detritt der Fehler nicht auf. Naheliegend ist eine Einstellung in der Nextcloud-config.php, die HTTPS erzwingt (z. B.overwriteprotocol); Cookies mit dem AttributSecurewerden dann über HTTP nicht zurückgesendet. - Diagnose:
12grep -nE "overwrite|trusted_domains|trusted_proxies|forwarded|cookie|htaccess.RewriteBase" /var/www/nextcloud/config/config.phpsed -n '/trusted_domains/,/),/p' /var/www/nextcloud/config/config.php
- Lösung: Die Adresse des Direktzugriffs muss in
trusted_domainsstehen. - Kontrolle: Anmeldung über
http://192.178.1.100/nextcloud/bleibt angemeldet.
7.11 Nextcloud-Direktzugriff per IP liefert 404
- Ursache: Beim Zusammenführen der VirtualHosts in
000-default.conffehlte zunächst derAlias /nextcloudsamt-Block des früheren Default-VirtualHosts. - Diagnose:
|
1 2 |
curl -sI http://192.178.1.100/nextcloud/ | head -1 grep -n 'Alias /nextcloud' /etc/apache2/sites-available/000-default.conf |
- Lösung: Block aus Kapitel 2.4 (Alias,
mitAllowOverride All,Dav off,SetEnv HOME,SetEnv HTTP_HOME) im Default-VirtualHost ergänzen;apache2ctl configtest && systemctl reload apache2. - Kontrolle: Anmeldeseite erreichbar (Status
200/302). Das Verhalten nach der Anmeldung betrifft 7.10.
7.12 Markdown-Links in kopierten Konfigurationsdateien
- Ursache: Beim Kopieren aus gerenderten Chat- oder Webansichten wurden Zeichenfolgen wie
www.myblogoder/var/www/www.*in Markdown-Links umgewandelt ([www.myblog](https://www.myblog)). Apache startet nicht oder liest Pfade mit Linksyntax; in einerwp-config.phpentsteht ein unbrauchbarer Alias-Pfad. - Diagnose:
|
1 |
grep -rn '](http' /etc/apache2/ /var/www/www.*/wp-config.php /var/www/www.*/.htaccess 2>/dev/null |
- Lösung:
|
1 |
sed -i -E 's/\[([^]]+)\]\(https?:\/\/[^)]*\)/\1/g' /etc/apache2/sites-available/000-default.conf /var/www/www.myblog/wp-config.php |
Dauerhaft: Dateien per scp oder Download übertragen, nicht aus der Ansicht kopieren; in wp-config.php den Alias-Pfad zerlegt schreiben ('/www' . '.myblog').
- Kontrolle:
grep -c '](http'liefert0;apache2ctl configtestmeldetSyntax OK.
7.13 Tests auf dem falschen Rechner
- Ursache: Befehle mit
127.0.0.1oder Log-Pfaden (/var/log/apache2/…) wurden auf der Arbeitsstation statt auf dem Webserver ausgeführt. Ergebnis:Failed to connect to 127.0.0.1 port 80bzw. leere Log-Auswertung. - Diagnose: Der Shell-Prompt (
user@workstationstattuser@webserver) bzw.
|
1 |
hostname |
- Lösung: Befehle mit
127.0.0.1und Apache-Logs auf dem Webserver, Caddy-Logs auf der OPNsense, externe Tests von einem Rechner außerhalb des Netzes ausführen. - Kontrolle:
curl -sI -H 'Host: myblog.newdomain.de' http://127.0.0.1/liefert auf dem Webserver eine HTTP-Antwort.
7.14 401 bei DAV-Abfragen mit Platzhalter-Zugangsdaten
- Ursache: Der Test wurde mit den Platzhaltern der Anleitung (
USER:APP-PASSWORT) oder mit einem anderen Benutzernamen als dem DAV-Benutzer ausgeführt (z. B.userstattuser1). - Diagnose:
-izeigtHTTP/… 401undWWW-Authenticate: Basic realm="Nextcloud". Die Antwort stammt dennoch von Apache/Nextcloud (HeaderServer: Apache/…); der Pfad wird also korrekt zugestellt. - Lösung: echten Benutzernamen und ein App-Passwort verwenden (in Nextcloud unter den Sicherheitseinstellungen erzeugbar). Zugangsdaten in Befehlszeilen verbleiben in der Shell-History; App-Passwörter nach dem Test widerrufen.
- Kontrolle:
HTTP/2 207.
7.15 404 für Adressbücher/Kalender nach der Migration
Siehe Kapitel 8 (abweichende Namen der Sammlungen).
8. CardDAV und Kalender
8.1 Technischer Zusammenhang
Nextcloud verwendet für Kalender und Adressbücher WebDAV-Erweiterungen (CalDAV, CardDAV). Kalender oder Adressbuch haben dabei einen eigenen URL-Pfad, der aus Benutzername und einem Kalender bzw. Adressbuchnamen (URI/„Slug“) besteht:
|
1 2 |
/remote.php/dav/calendars/<user>/<kalender-uri>/ /remote.php/dav/addressbooks/users/<user>/<adressbuch-uri>/ |
Der Name im Pfad ist nicht der Anzeigename, sondern ein beim Anlegen erzeugter Kurzname. Beobachtungen:
- Standard-Sammlungen tragen feste Namen (
personalfür den Standardkalender,contactsfür das Standard-Adressbuch). - Für selbst angelegte Sammlungen wird der Anzeigename in Kleinbuchstaben mit Bindestrichen umgesetzt; Sonderzeichen wie Umlaute werden entfernt (Beispiel: Anzeigename „Geschäftlich“ ⇒ Pfadname
geschftlich). - Geteilte Sammlungen erhalten Pfadnamen nach dem Muster
_shared_by_.
DAV-Clients (Thunderbird, DAVx5, iOS/macOS u. a.) speichern pro Sammlung den vollständigen Pfad. Ruft ein Client nach der Migration einen Pfad auf, den die neue Installation nicht kennt, antwortet Nextcloud mit 404. Ein Proxy-Fehler liegt dann nicht vor: Die Antwort trägt Nextcloud-Header, z. B. X-User-Id, und die Anfrage wurde korrekt zugestellt.
Beobachtung im Caddy-Log der Alt-Domain (Client Thunderbird, Basis-URL https://olddomain.de/nextcloud/remote.php/dav/; Namen anonymisiert):
| Anfrage | Status | Bedeutung |
|---|---|---|
REPORT /nextcloud/remote.php/dav/addressbooks/users/user1/addressbook-a/ |
401, danach 207 |
erste Anfrage ohne Authorization wird abgelehnt (normale Challenge); die Wiederholung mit Authorization ist erfolgreich |
REPORT /nextcloud/remote.php/dav/addressbooks/users/user1/old-contacts/ |
404 (Nextcloud-Antwort) |
Sammlung mit diesem Namen existiert in der neuen Installation nicht |
REPORT /nextcloud/remote.php/dav/addressbooks/users/user1/old-contacts_shared_by_/ |
404 (Nextcloud-Antwort) |
wie oben, geteilte Sammlung |
PROPFIND /nextcloud/remote.php/dav/ |
207 |
Basis-URL erreichbar |
Der Zugriff über die alte URL /nextcloud/… und über die neue Domain (https://nextcloud.newdomain.de/remote.php/dav/…) liefert für identische Pfade identische Ergebniss
8.2 Prüfung der vorhandenen Kontakte und Kalender
Auflistung der Kontakte und Kalender (Pfadnamen) einer Installation; je Installation (alt und neu) auszuführen:
|
1 2 3 4 5 6 7 |
# Adressbücher curl -s -u '<user>:<app-password>' -X PROPFIND -H 'Depth: 1' \ 'https://<host>/<präfix>remote.php/dav/addressbooks/users/<user>/' | grep -o '<d:href>[^<]*' # Kalender curl -s -u '<user>:<app-password>' -X PROPFIND -H 'Depth: 1' \ 'https://<host>/<präfix>remote.php/dav/calendars/<user>/' | grep -o '<d:href>[^<]*' |
- Alt-Domain:
=olddomain.de,<präfix>=nextcloud/ - Neue Domain:
=nextcloud.newdomain.de,<präfix>leer
8.3 Vorgehen bei künftigen Nextcloud-Migrationen
- Vor der Migration die Pfadnamen aller Kalender und Adressbücher aller Benutzer in der alten Installation erfassen (Kapitel 8.2) und sichern.
- Benutzernamen identisch halten. Der Benutzername ist Teil des Pfads (
…//…). - Sammlungsnamen identisch anlegen. In der neuen Installation Kalender und Adressbücher mit demselben Anzeigenamen anlegen, bevor Daten importiert werden, damit derselbe Pfadname entsteht. Danach Inhalte in die gleichnamigen Sammlungen importieren. Der Pfadname wird beim Anlegen erzeugt; eine spätere Umbenennung des Anzeigenamens ändert ihn nach bisherigem Kenntnisstand nicht (nicht eigens getestet).
- Standard-Sammlungen (
personal,contacts) nicht umbenennen. - Geteilte Sammlungen (
…_shared_by_) erneut freigeben; ihre Pfadnamen hängen vom Besitzer und dem Namen der Freigabe ab. - Nach der Migration die Auflistung (Kapitel 8.2) auf beiden Installationen vergleichen: Die Pfadnamen der Sammlungen müssen übereinstimmen.
- Clients mit bestehender Einrichtung (Alt-URL
…/nextcloud/…) funktionieren nur bei übereinstimmenden Pfadnamen. Für Sammlungen ohne Entsprechung sind die Einträge im Client zu entfernen und neu hinzuzufügen. - Zugriff über die alte Domain bleibt über die Durchreichung der
/nextcloud/…-Pfade (Kapitel 4.5) undAlias /nextcloud(Kapitel 2.6) möglich. Der Zugriff nur über die Domain ohne Pfad (Erkennung per/.well-known/caldavbzw./.well-known/carddavim Root der Alt-Domain) ist im Projekt nicht eingerichtet, da die beobachteten Clients den vollständigen Pfad/nextcloud/remote.php/dav/verwenden.
9. Vollständige Konfigurationsübersicht
9.1 Webserver (Debian, Apache)
|
1 2 3 4 5 6 7 8 9 10 11 12 |
/etc/apache2/ ├── apache2.conf Hauptdatei ├── ports.conf Listen 80 ├── mods-available/ → mods-enabled/ (a2enmod / a2dismod) ├── conf-available/ → conf-enabled/ (a2enconf / a2disconf) └── sites-available/ → sites-enabled/ (a2ensite / a2dissite) ├── 000-default.conf Default-VirtualHost, WordPress-Makro, Aliase ├── nextcloud.conf VirtualHost nextcloud.newdomain.de ├── default-ssl.conf Apache-Standarddatei (nicht Teil der Architektur) └── www.*.conf VirtualHost je WordPress-Site (aktiviert; nur bei Variante mit Einzeldateien, Kapitel 2.4.1) /var/log/apache2/ Logdateien /var/www/ Webanwendungen |
| Pfad | Zweck | Relevante Konfiguration | Abhängigkeiten | Verwendet von |
|---|---|---|---|---|
/etc/apache2/apache2.conf |
Hauptkonfiguration; bindet mods-enabled, ports.conf, conf-enabled, sites-enabled ein |
mit Require all granted, AllowOverride None; IncludeOptional sites-enabled/*.conf (alphabetisch) |
– | Apache |
/etc/apache2/ports.conf |
lauscht auf Port 80 | Listen 80 |
– | Apache |
/etc/apache2/mods-enabled/ |
aktivierte Module | macro, rewrite, headers, remoteip, env, setenvif, alias, … (Kapitel 2.1) |
a2enmod |
Apache |
/etc/apache2/sites-available/000-default.conf |
Default-VirtualHost (Direktzugriff per IP), Makro WPSite, Aliase |
Kapitel 2.4 | Module macro, rewrite, alias, env; Verzeichnisse unter /var/www/ |
Apache; indirekt Caddy (Host-Header) und direkte Clients |
/etc/apache2/sites-available/nextcloud.conf |
VirtualHost für Nextcloud | Kapitel 2.6 | Module headers, remoteip, setenvif, env; /var/www/nextcloud |
Apache; Caddy (nextcloud.newdomain.de, /nextcloud-Durchreichung) |
/etc/apache2/sites-enabled/ |
Symlinks der aktiven Sites | mindestens 000-default.conf und nextcloud.conf |
a2ensite/a2dissite |
Apache |
/etc/apache2/sites-available/default-ssl.conf |
Apache-Standarddatei für HTTPS | Nicht benötigt, da SSL über Reverse Proxy verwaltet wird. | – | – |
/var/log/apache2/error.log, access.log |
Logs des Default-VirtualHosts | – | – | Apache |
/var/log/apache2/-error.log, -access.log |
Logs je WordPress-Site (Makro) | z. B. myblog-error.log |
– | Apache |
/var/log/apache2/yourdomain-error.log, …-access.log |
Logs des Nextcloud-VirtualHosts | Namen aus nextcloud.conf |
– | Apache |
9.2 Webanwendungen
|
1 2 3 4 5 6 7 8 9 10 11 |
/var/www/ ├── html/ Default-DocumentRoot ├── nextcloud/ Nextcloud │ └── config/config.php Nextcloud-Konfiguration ├── www.myblog/ WordPress → myblog.newdomain.de ├── www.website1.de/ WordPress → website1.newdomain.de ├── www.website2.de/ WordPress → website2.newdomain.de ├── www.website3.de/ WordPress → website3.newdomain.de └── www.website4.de/ WordPress → website4.newdomain.de ├── wp-config.php └── .htaccess |
| Pfad | Zweck | Relevante Konfiguration | Abhängigkeiten | Verwendet von |
|---|---|---|---|---|
/var/www/www./wp-config.php |
Datenbankzugang, URL-Block | Kapitel 5.2, ; Wert $direct_path je Site |
MySQL/MariaDB; Alias in 000-default.conf |
PHP/WordPress |
/var/www/www./.htaccess |
Permalink-Rewrite | Kapitel 5.3 | Modul rewrite; AllowOverride All |
Apache |
/var/www/www./wp-content/ |
Themes, Plugins, Uploads | Prüfung auf alte URLs (Kapitel 5.7) | – | WordPress |
/var/www/nextcloud/config/config.php |
Nextcloud-Konfiguration | trusted_domains, overwrite*, htaccess.RewriteBase, trusted_proxies: Werte [zu ergänzen] |
nextcloud.conf |
Nextcloud |
/var/www/nextcloud/.htaccess |
Nextcloud-Rewrite-Regeln (u. a. /.well-known/carddav, /.well-known/caldav) |
von Nextcloud verwaltet | Modul rewrite |
Apache |
9.3 OPNsense / Caddy
| Pfad/Ort | Zweck | Relevante Konfiguration | Abhängigkeiten | Verwendet von |
|---|---|---|---|---|
/usr/local/etc/caddy/Caddyfile |
automatisch generierter Caddyfile | Kapitel 4.2 | os-caddy-Einträge in der GUI | Caddy |
/usr/local/etc/caddy/caddy.d/newdomain-redirect.conf |
Weiterleitung und Durchreichung der Alt-Domain | Kapitel 4.5 | Backend 192.178.1.100:80; Apache-Alias /nextcloud |
Caddy (Import am Ende des Caddyfiles) |
/usr/local/etc/caddy/caddy.d/*.global |
globale Zusatzoptionen | derzeit nicht verwendet | – | Caddy |
/usr/local/etc/caddy/certificates/.pem, .key |
Wildcard-Zertifikat aus dem Trust-Store | – | os-acme-client, deSEC-API | Caddy |
/var/log/caddy/caddy.log |
Caddy-Log | – | – | Caddy |
/var/log/caddy/newdomain-redirect-access.log |
temporärer Access-Log der Alt-Domain (Kapitel 4.6) | – | log-Block in newdomain-redirect.conf |
Caddy |
| OPNsense-GUI: os-caddy | Domänen, Subdomänen, Handler | Kapitel 4.2 | – | erzeugt den Caddyfile |
| OPNsense-GUI: os-acme-client | Zertifikatsausstellung (DNS-01, deSEC) | Kapitel 4.3 | deSEC-API-Token | erzeugt das Zertifikat im Trust-Store |
| Firewall/NAT der OPNsense | Zugriff auf Caddy (80/443) und auf die Backends | Regeln: siehe Tutorial Netzwerkisolation mit OPNsense auf Proxmox | – | OPNsense |
9.4 DNS und Router
| Ort | Zweck | Konfiguration |
|---|---|---|
deSEC (newdomain.de) |
DNS der neuen Domain | A-Record newdomain.de → öffentliche IP (automatisch aktualisiert); Wildcard-CNAME * → newdomain.de |
DNS-Anbieter (olddomain.de) |
DNS der Alt-Domain | zeigt auf die öffentliche IP |
| Internet-Router | Portweiterleitung 80/443 auf die OPNsense | https://www.youtube.com/watch?v=6dHoFVyHinU |
10. Prüf- und Testverfahren
Alle Tests sind so gewählt, dass erkennbar ist, welche Komponente die Anfrage tatsächlich beantwortet:
| Merkmal in der Antwort | Bedeutung |
|---|---|
Server: Apache/… |
Apache hat geantwortet |
Via: 1.1 Caddy |
die Antwort lief über Caddy (Reverse Proxy) |
Server: Caddy |
Caddy hat selbst geantwortet (z. B. Redirect; Standardverhalten von Caddy) |
X-Powered-By/WordPress-spezifische Header, Link: <…/wp-json/> |
WordPress hat die Seite erzeugt |
X-User-Id, Content-Security-Policy: default-src 'none' bei DAV-Antworten |
Nextcloud hat geantwortet |
Ausführungsort beachten (Kapitel 7.13): Befehle mit 127.0.0.1 und Apache-Logs auf dem Webserver, Caddy-Logs auf der OPNsense, externe Tests außerhalb des Netzes.
10.1 Apache (auf dem Webserver)
|
1 2 3 4 5 6 7 8 9 |
apache2ctl configtest apache2ctl -S apache2ctl -M | grep -E 'macro|rewrite|headers|remoteip|env|setenvif|alias' systemctl is-active apache2 curl -I http://127.0.0.1/ curl -I -H "Host: myblog.newdomain.de" http://127.0.0.1/ for h in myblog website1 website2 website3 website4; do printf '%-10s' "$h"; curl -s -o /dev/null -w '%{http_code}\n' -H "Host: $h.newdomain.de" http://127.0.0.1/; done curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: myblog.example.org' http://127.0.0.1/ curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: unknown.newdomain.de' http://127.0.0.1/www.website2.de/ |
Erwartet: Syntax OK; apache2ctl -S nennt den Default-Server (000-default.conf) und alle Sites; 200 für die bekannten Subdomains und für myblog.example.org (Domainunabhängigkeit); 403 für unbekannte FQDN.
10.2 Direkte IP-Aufrufe
|
1 2 3 4 |
curl -s -o /dev/null -w '%{http_code}\n' http://192.178.1.100/www.myblog/ curl -s -o /dev/null -w '%{http_code}\n' http://192.178.1.100/www.website1.de/ curl -s -o /dev/null -w '%{http_code}\n' http://192.178.1.100/nextcloud/ curl -s http://192.178.1.100/www.website2.de/ | grep -o 'href="[^"]*"' | head -3 |
Erwartet: 200/302; die Links beginnen mit http://192.178.1.100/www.website2.de/ (WordPress-Basis-URL, Kapitel 5.3). Zeigt der Link /www.www.…, liegt der Fehler aus Kapitel 7.7 vor.
10.3 Caddy (auf der OPNsense)
|
1 2 |
service caddy restart tail -n 30 /var/log/caddy/caddy.log |
Eine Syntaxprüfung der Konfiguration ist zusätzlich möglich, sofern die caddy-Binärdatei auf dem System zur Verfügung steht:
|
1 |
caddy validate --config /usr/local/etc/caddy/Caddyfile --adapter caddyfile |
Im Projekt wurde die Datei newdomain-redirect.conf zusammen mit dem generierten Caddyfile in einer Testumgebung mit caddy validate geprüft (Ergebnis: Valid configuration); auf der OPNsense erfolgte die Kontrolle über den Neustart und das Log. Ob caddy auf der OPNsense im Suchpfad liegt: [zu ergänzen].
10.4 HTTPS und Zertifikat (neue Domain)
|
1 2 |
curl -vI https://myblog.newdomain.de/ 2>&1 | grep -E 'subject|issuer|expire|HTTP/|server:|via:' echo | openssl s_client -connect myblog.newdomain.de:443 -servername myblog.newdomain.de 2>/dev/null | openssl x509 -noout -subject -dates |
Erwartet: Zertifikat für *.newdomain.de; HTTP/2 200; via: 1.1 Caddy und server: Apache/….
10.5 Alte Domain und Redirects
|
1 2 3 4 5 6 |
curl -sI https://olddomain.de/myblog/category/smart-home/ | grep -iE '^(HTTP|location|server)' curl -sI 'https://olddomain.de/myblog/?p=12&x=1' | grep -i '^location' curl -sI https://olddomain.de/www.website4.de/index.php/gallery/category/item/ | grep -i '^location' curl -sI 'https://olddomain.de/nextcloud/index.php/s/AbC123?dir=/x' | grep -i '^location' curl -sI https://olddomain.de/unknown/path | grep -i '^location' curl -sIL --max-redirs 5 https://olddomain.de/myblog/category/smart-home/ | grep -iE '^(HTTP|location)' |
Erwartet: 301 mit Location: https://myblog.newdomain.de/category/smart-home/, Query und Rest-Pfad unverändert, Server: Caddy (Redirect durch Caddy; Apache ist nicht beteiligt). Der letzte Befehl muss genau ein 301 und danach 200 zeigen.
Weitere erwartete Ergebnisse: Kapitel 4.5, Tabelle „Getestete Zuordnungen“.
10.6 WordPress
|
1 2 3 |
php -l /var/www/www.website1.de/wp-config.php curl -s -H 'Host: website1.newdomain.de' -H 'X-Forwarded-Proto: https' http://127.0.0.1/ | grep -o 'href="[^"]*"' | head -5 curl -s https://website1.newdomain.de/ | grep -c 'olddomain.de' |
Erwartet: No syntax errors detected; Links beginnen mit https://website1.newdomain.de/; der Zähler zeigt die Anzahl noch eingebetteter Alt-Domain-Verweise (0 ist der Zielwert, Kapitel 5.6/5.7).
10.7 Nextcloud, CardDAV und Kalender
|
1 2 3 4 5 6 7 8 9 10 11 12 |
# Basis erreichbar, ohne Zugangsdaten (Status 401 + WWW-Authenticate) curl -si -H 'Host: nextcloud.newdomain.de' http://127.0.0.1/nextcloud/remote.php/dav/ | head -5 curl -s -H 'Host: nextcloud.newdomain.de' http://127.0.0.1/nextcloud/status.php # DAV über die neue und die alte Domain (App-Passwort verwenden) curl -si -u '<user>:<app-password>' -X PROPFIND -H 'Depth: 0' https://nextcloud.newdomain.de/remote.php/dav/ | head -3 curl -si -u '<user>:<app-password>' -X PROPFIND -H 'Depth: 0' https://olddomain.de/nextcloud/remote.php/dav/ | head -3 # Namen der Sammlungen vergleichen (Kapitel 8.2) curl -s -u '<user>:<app-password>' -X PROPFIND -H 'Depth: 1' https://nextcloud.newdomain.de/remote.php/dav/addressbooks/users/<user>/ | grep -o '<d:href>[^<]*' curl -s -u '<user>:<app-password>' -X PROPFIND -H 'Depth: 1' https://nextcloud.newdomain.de/remote.php/dav/calendars/<user>/ | grep -o '<d:href>[^<]*' curl -s -u '<user>:<app-password>' -X PROPFIND -H 'Depth: 1' https://olddomain.de/nextcloud/remote.php/dav/ | grep -o '<d:href>[^<]*' | head -5 |
Erwartet: 401 mit WWW-Authenticate: Basic realm="Nextcloud" ohne Zugangsdaten; 207 mit Zugangsdaten; bei der Alt-Domain beginnen die href-Werte mit /nextcloud/remote.php/dav/. Die Pfadnamen der Sammlungen müssen mit den im Client gespeicherten Pfaden übereinstimmen.
Auf dem Webserver zeigt das Nextcloud-Access-Log, welche Pfade ein Client tatsächlich abfragt:
|
1 |
grep -E 'well-known|remote.php|nextcloud' /var/log/apache2/yourdomain-access.log | tail -n 20 | awk '{print $1, $6, $7, $9}' |
Auf der OPNsense zeigt das Access-Log der Alt-Domain (Kapitel 4.6), ob die Anfrage Caddy erreicht und welchen Status der Client erhält.
10.8 Externe Erreichbarkeit
Von einem Rechner außerhalb des Heimnetzes (z. B. Mobilfunk):
|
1 2 3 4 |
dig +short myblog.newdomain.de dig +short olddomain.de curl -sI https://myblog.newdomain.de/ | head -3 curl -sI https://olddomain.de/myblog/ | head -3 |
Erwartet: beide Namen lösen auf dieselbe öffentliche IP auf; die Aufrufe liefern 200 bzw. 301. Schlägt ein Aufruf von außen fehl, funktioniert aber von innen, liegt der Fehler bei Portweiterleitung oder Firewall (Regeln: siehe Tutorial Netzwerkisolation mit OPNsense auf Proxmox).
11. Ablauf einer Anfrage
11.1 Neue Domain: https://myblog.newdomain.de/category/smart-home/
| Schritt | Komponente | Vorgang |
|---|---|---|
| 1 | DNS | Der Browser löst myblog.newdomain.de auf. Der Wildcard-CNAME * zeigt auf newdomain.de; der A-Record liefert die öffentliche IP. |
| 2 | Router / OPNsense | Die Verbindung zu Port 443 wird an die OPNsense weitergeleitet. Die Firewall-Regeln lassen sie zu Caddy durch. |
| 3 | Caddy | Caddy wählt den Site-Block myblog.newdomain.de anhand von SNI und Host-Header. |
| 4 | TLS | Caddy beendet TLS mit dem Wildcard-Zertifikat *.newdomain.de (aus dem Trust-Store). |
| 5 | Reverse Proxy | Der Handler reverse_proxy 192.178.1.100 öffnet eine neue HTTP-Verbindung zu 192.178.1.100:80. |
| 6 | HTTP-Request zum Apache | GET /category/smart-home/ mit Host: myblog.newdomain.de, X-Forwarded-For: , X-Forwarded-Proto: https, X-Forwarded-Host: myblog.newdomain.de. |
| 7 | Host-Header | Apache liest Host: myblog.newdomain.de aus dem Request. |
| 8 | VirtualHost-Auswahl | Auf *:80 trifft ServerAlias myblog.* des Makros WPSite. Der Default-VirtualHost wird nicht verwendet. |
| 9 | DocumentRoot / Alias | DocumentRoot /var/www/www.myblog. /category/smart-home/ existiert nicht als Datei; .htaccess leitet per RewriteRule . index.php [L] an WordPress. |
| 10 | WordPress | wp-config.php setzt wegen X-Forwarded-Proto: https HTTPS=on und WP_HOME/WP_SITEURL auf https://myblog.newdomain.de. WordPress löst den Permalink auf und erzeugt die Seite mit URLs dieser Basis. |
| 11 | Rückgabe an Apache | PHP übergibt die Antwort an Apache (Server: Apache/…). |
| 12 | Rückgabe an Caddy | Apache sendet die Antwort an Caddy, das den Header Via: 1.1 Caddy ergänzt. |
| 13 | Rückgabe an den Browser | Caddy sendet die Antwort über die TLS-Verbindung an den Browser. |
11.2 Alte Domain: https://olddomain.de/myblog/category/smart-home/
| Schritt | Komponente | Vorgang |
|---|---|---|
| 1 | DNS | olddomain.de löst auf dieselbe öffentliche IP auf. |
| 2 | Router / OPNsense | wie bei der neuen Domain |
| 3 | Caddy | Der Site-Block olddomain.de aus newdomain-redirect.conf wird gewählt (SNI/Host). |
| 4 | TLS | Caddy beendet TLS mit dem Zertifikat dieses Site-Blocks (Quelle: Kapitel 4.3) |
| 5 | Regelauswertung | legacy_redirect prüft in fester Reihenfolge: /nextcloud/…-Client-Pfade (kein Treffer), dann legacy_site (?:www\.)?myblog (Treffer: Pfad beginnt mit /myblog/). |
| 6 | Umschreiben | uri path_regexp entfernt den Präfix; {uri} lautet nun /category/smart-home/ (Query bleibt erhalten). |
| 7 | HTTP-Redirect | Caddy antwortet selbst mit 301 und Location: https://myblog.newdomain.de/category/smart-home/. Apache und WordPress sind nicht beteiligt. |
| 8 | Browser | Der Browser stellt eine neue Anfrage an die neue URL; ab hier gilt der Ablauf aus Kapitel 11.1. Die Adresszeile zeigt die neue Domain. |
An dieser Stelle (Schritt 7) erfolgt die Weiterleitung auf die neue Domain. Der Pfad nach dem Präfix und die Query bleiben unverändert; unbekannte Pfade der Alt-Domain werden durch die Auffangregel auf die Blog-Subdomain umgeleitet.
11.3 Alte Nextcloud-Client-Anfrage: https://olddomain.de/nextcloud/remote.php/dav/…
| Schritt | Komponente | Vorgang |
|---|---|---|
| 1–4 | DNS, OPNsense, Caddy, TLS | wie in Kapitel 11.2 |
| 5 | Regelauswertung | Die erste Regel (@nextcloud_api) trifft: Pfad beginnt mit /nextcloud/remote.php/. |
| 6 | Reverse Proxy (kein Redirect) | Caddy leitet an 192.178.1.100:80 weiter, Pfad unverändert (/nextcloud/remote.php/dav/…), Host: nextcloud.newdomain.de (header_up). |
| 7 | VirtualHost-Auswahl | Apache wählt anhand von Host: nextcloud.newdomain.de den VirtualHost aus nextcloud.conf. |
| 8 | Alias | Alias /nextcloud "/var/www/nextcloud/" ordnet den Pfad dem Nextcloud-Verzeichnis zu. |
| 9 | Nextcloud | Nextcloud leitet seinen Webroot (/nextcloud) aus dem Skriptpfad ab und authentifiziert den Client. Antworten enthalten Pfade mit dem Präfix /nextcloud/remote.php/dav/…. |
| 10 | Rückgabe | Apache → Caddy → Client; der Client bemerkt keinen Hostwechsel. |
Tutorials, Konfigurationen und Open-Source-Projekte sind mit regelmäßigem Aufwand für Tests, Pflege und Dokumentation verbunden. Wer unsere Arbeit unterstützen möchte, trägt dazu bei, dass neue Inhalte entstehen. Wir danken dafür.