Blog

  • Remote WordPress sicher hosten: Zero-Exposure via Tailscale & Traefik Reverse Proxy

    Wer Webdienste wie WordPress an entfernten Standorten (z. B. auf einem Home-Server, Proxmox VE oder DietPi beim Kollegen) betreiben möchte, steht meist vor den üblichen Hürden: IPv6-only/DS-Lite-Anschlüsse, dynamische IP-Adressen und das Sicherheitsrisiko offener Router-Ports.

    In diesem Setup lösen wir das Problem elegant: Ein öffentlich erreichbarer vServer (1blu) terminiert SSL über Traefik und leitet Anfragen über ein verschlüsseltes Tailscale-Mesh-Netzwerk direkt an die WordPress-Instanz am Remote-Standort weiter.

    Vorteile:

    • Keine offenen Ports oder Portweiterleitungen am Remote-Standort nötig.
    • Automatisches Let’s Encrypt SSL-Management auf dem VPS.
    • Vollständig verschlüsselter Transportweg via WireGuard.

    1. Die Architektur

    Plaintext

    [ Browser / Internet ]
             │ (HTTPS / 443)
             ▼
    [ 1blu vServer ] 
      ├── Traefik v3 (SSL-Terminierung & Dynamic Routing)
      └── Tailscale Container (network_mode: host)
             │ 
             ▼ (Tailscale Mesh / WireGuard UDP via DERP/P2P)
    [ Remote Proxmox / DietPi ]
      ├── Tailscale Client (100.104.85.99)
      └── Webserver / WordPress (Port 80)
    

    2. Server-Setup (1blu VPS)

    Schritt 2.1: IP-Forwarding aktivieren

    Damit Docker-Container Pakete in das Tailscale-Netzwerk weiterleiten dürfen:

    Bash

    echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
    sudo sysctl -p /etc/sysctl.d/99-tailscale.conf
    

    Schritt 2.2: Tailscale Client als Host-Service im Docker

    Der entscheidende Punkt: Tailscale muss mit network_mode: host laufen, damit das tailscale0-Interface direkt auf dem Host-Kernel existiert.

    Datei: ~/tailscale/compose.yml

    YAML

    services:
      tailscale:
        image: tailscale/tailscale:latest
        container_name: tailscale
        hostname: v31480-relay
        network_mode: host
        environment:
          - TS_STATE_DIR=/var/lib/tailscale
          - TS_USERSPACE=false
          - TS_ACCEPT_DNS=true
          - TS_EXTRA_ARGS=--reset
        volumes:
          - ./tailscale-state:/var/lib/tailscale
          - /dev/net/tun:/dev/net/tun
          - /var/run/tailscale:/var/run/tailscale
        cap_add:
          - NET_ADMIN
          - NET_RAW
          - SYS_MODULE
        restart: unless-stopped
    

    Starten und Verifizieren:

    Bash

    cd ~/tailscale
    docker compose up -d
    docker exec tailscale tailscale ping 100.104.85.99
    

    Schritt 2.3: Traefik Docker-Compose erweitern

    Traefik muss ein Verzeichnis für dynamische Datei-Konfigurationen (dynamic/) überwachen, um externe IP-Backends ansteuern zu können.

    Auszug aus ~/traefik/docker-compose.yml:

    YAML

    services:
      traefik:
        image: traefik:v3.0
        container_name: traefik
        restart: always
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - /var/run/docker.sock:/var/run/docker.sock:ro
          - ./acme.json:/letsencrypt/acme.json
          - ./dynamic:/dynamic:ro # Dynamische Konfigurationsdateien
        command:
          - --api.dashboard=true
          - --providers.docker=true
          - --providers.docker.exposedbydefault=false
          - --providers.file.directory=/dynamic # File-Provider aktivieren
          - --providers.file.watch=true
          - --entrypoints.web.address=:80
          - --entrypoints.websecure.address=:443
          - --entrypoints.web.http.redirections.entrypoint.to=websecure
          - --entrypoints.web.http.redirections.entrypoint.scheme=https
          - --certificatesresolvers.letsencrypt.acme.tlschallenge=true
          - --certificatesresolvers.letsencrypt.acme.email=webmaster@traunrocks.de
          - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
        networks:
          - traefik-net
    
    networks:
      traefik-net:
        driver: bridge
    

    Schritt 2.4: Traefik Dynamic Route für WordPress anlegen

    Verzeichnis anlegen und Konfigurationsdatei mit vi erstellen:

    Bash

    mkdir -p ~/traefik/dynamic
    vi ~/traefik/dynamic/wordpress.yml
    

    Inhalt von ~/traefik/dynamic/wordpress.yml:

    YAML

    http:
      routers:
        wordpress:
          rule: "Host(`wp.minirocks.dynipv6.de`)"
          entryPoints:
            - websecure
          service: wordpress-service
          tls:
            certResolver: letsencrypt
    
      services:
        wordpress-service:
          loadBalancer:
            servers:
              - url: "http://100.104.85.99:80" # Tailscale-IP der DietPi-Instanz
            passHostHeader: true
    

    Danach Traefik neu starten:

    Bash

    cd ~/traefik
    docker compose up -d
    

    3. Remote-Setup: Was auf der WordPress-Instanz noch fehlt

    Da Traefik die SSL-Verschlüsselung übernimmt (SSL-Offloading) und die Anfrage intern per HTTP über Tailscale weiterleitet, weiß WordPress standardmäßig nicht, dass der Besucher HTTPS verwendet. Ohne Anpassung führt dies zu Redirect-Loops (ERR_TOO_MANY_REDIRECTS) oder Mixed-Content-Fehlern.

    Der Reverse-Proxy Snippet-Block für wp-config.php

    Öffne auf dem Remote-Server (DietPi) die Konfigurationsdatei:

    Bash

    vi /var/www/wordpress/wp-config.php
    

    Füge diesen Block ganz oben, direkt nach dem öffnenden <?php, ein:

    PHP

    /**
     * Reverse Proxy & SSL Offloading Konfiguration für Traefik über Tailscale
     */
    if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
        $_SERVER['HTTPS'] = 'on';
    }
    
    // Host-Header explizit weiterreichen (verhindert interne IP-Verlinkungen)
    if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
        $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
    }
    

    4. Wichtigste Troubleshooting-Takeaways

    • Host vs. Bridge bei Tailscale-Containern: Läuft Tailscale in einem Standard-Docker-Bridge-Netzwerk, sieht der Host das tailscale0-Interface nicht. Pings und Routen vom Host auf 100.x.y.z scheitern dann. Lösung: Immer network_mode: host für den Tailscale-Container nutzen.
    • Crash-Loop changing settings requires mentioning all non-default flags: Tritt auf, wenn im State-Ordner alte Flags gespeichert sind. Abhilfe schafft die Umgebungsvariable TS_EXTRA_ARGS=--reset in der compose.yml.
    • URLs in WordPress Allgemein: Beide Einträge (WordPress-Adresse und Website-Adresse) müssen zwingend auf die öffentliche Domain mit https:// zeigen (z. B. [https://wp.minirocks.dynipv6.de](https://wp.minirocks.dynipv6.de)).