Nginx-Proxy-Server für IPv4-Anbindung von IPv6-only-Servern: Anleitung

etron770

Member
Da ich keine neuen, teuren IPv4-Adressen kaufen wollte oder möglicherweise einmal keine mehr bekommen würde, habe ich Folgendes konfiguriert, um reine IPv6-LXC-Container oder VMs mit IPv4 versorgen zu können. Es ist zwar meine Idee, aber nicht ganz allein meine Arbeit. ChatGPT hat mir dabei geholfen. Die Zusammenfassung hat mir auch ChatGPT erstellt. Es funktioniert, die Zugriffe sind drastisch schneller geworden, weil nun ein Nginx Proxy davor ist, der die Architektur in mehreren Punkten optimiert. Allerdings sind jedoch im DNS einige VMs/LXC Container unter einer IPV4 und einer IPV6 zusammengefasst. Ob man einen Mailserver auch einbindet, sollte man sich gut überlegen. Spamt ein Container, ist auch der Mailserver auf der Blacklist, weil er dieselbe IP hat.
Ich hoffe, es sind alle Schritte zusammengefasst.
Dies ist der aktuelle Status:
(Englisch unten)

Technische Systembeschreibung – Edge-Proxy und WireGuard unter Debian 12 mit ISPConfig​

1. Systemübersicht​

Dieses Setup beschreibt eine Debian-12-Installation mit ISPConfig, die als kombinierter Reverse-Proxy (TLS-Passthrough) und WireGuard-Server für IPv4-Egress dient. „Edge-Proxy“ = öffentlicher Dual-Stack-Server, „Client 1“ = internes IPv6-only-System mit IPv4-Tunnel.

2. Paketinstallation​

Code:
apt update
apt install nginx-full libnginx-mod-stream wireguard iptables

Falls Apache durch ISPConfig aktiv ist, sicherstellen, dass dieser nicht auf den Ports 80/443 lauscht, um Portkonflikte zu vermeiden.

3. Netzwerkkonzept​

Der Edge-Proxy ist Dual-Stack-fähig mit öffentlicher IPv4- und IPv6-Adresse. Backends (inklusive Client 1) nutzen native IPv6; IPv4-Datenverkehr wird über WireGuard getunnelt. DNS-A/AAAA-Einträge verweisen auf den Edge-Proxy; SSH erfolgt direkt über IPv6.

4. Nginx-Reverse-Proxy-Konfiguration​

Datei: /etc/nginx/maps/host_upstreams.inc (Webseitenzurodnung)
Code:
site1.example.net [IPv6-Backend1];
site2.example.net [IPv6-Backend2];
default [IPv6-Default]:443;
Datei: /etc/nginx/stream-enabled/443-sni-map.conf (SNI-Mapping und TLS-Passthrough)

Code:
# TLS SNI → Zielbackend:443, basierend auf /etc/nginx/maps/host_upstreams.inc (ohne Port)

# 1) SNI → IP (ohne Port) laden
map $ssl_preread_server_name $upstream_ip_v6 {
    hostnames;
    # zuordnung von url zu server IP für 443 und 80
    include /etc/nginx/maps/host_upstreams.inc;
}

# 2) IP → IP:443 zusammensetzen
map $upstream_ip_v6 $sni_upstream {
    "~^(.*)$" $1:443;
}

server {
    listen 0.0.0.0:443;
    listen [::]:443;

    ssl_preread on;
    proxy_protocol on;

    proxy_pass $sni_upstream;
    proxy_connect_timeout 10s;
    proxy_timeout 180s;

    access_log /var/log/nginx/stream_access.log stream_fmt;
    error_log  /var/log/nginx/stream_error.log  warn;
}

Datei: /etc/nginx/conf.d/80-acme-proxy.conf (HTTP-ACME-Challenge-Proxy und Redirect)

Code:
# 1) Host → IP (ohne Port) aus gemeinsamer Include-Datei
map $host $upstream_ip_v6 {
    hostnames;
    #Zuordnung für 80 und 443
    include /etc/nginx/maps/host_upstreams.inc;
}
server {
    listen 0.0.0.0:80 default_server;
    listen [::]:80 default_server;
    server_name _;

    # 2) ACME-Challenges zum Zielserver auf Port 80
    location ^~ /.well-known/acme-challenge/ {
        proxy_pass http://$upstream_ip_v6:80;

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto http;

        proxy_redirect off;
        proxy_connect_timeout 5s;
        proxy_send_timeout 10s;
        proxy_read_timeout 10s;
    }

    # 3) Alles andere auf HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

Änderung in Apache-Konfiguration (Datei: /etc/apache2/conf-available/10-remoteip-proxyproto.conf):

Code:
RemoteIPProxyProtocol On
RemoteIPTrustedProxy <EDGE_PROXY_IPV4>
RemoteIPTrustedProxy <EDGE_PROXY_IPV6>
LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined_realip
CustomLog ${APACHE_LOG_DIR}/access.log combined_realip

Hinweise:
- <EDGE_PROXY_IPV4> und <EDGE_PROXY_IPV6> mit tatsächlichen Adressen des Edge-Proxy ersetzen.
- Der Edge-Proxy muss das PROXY-Protokoll aktiv übergeben.
- Keine gleichzeitige Verwendung von RemoteIPHeader.
- Aktivierung: a2enmod remoteip && a2enconf 10-remoteip-proxyproto && systemctl reload apache2.

5. WireGuard-Konfiguration​

Schlüsselgenerierung (pro System eindeutige Dateinamen verwenden, z. B. für Edge-Proxy und jeden Client separat):

Code:
# Edge-Proxy
umask 077
wg genkey > /etc/wireguard/edgeproxy.key
wg pubkey < /etc/wireguard/edgeproxy.key > /etc/wireguard/edgeproxy.key.pub

# Client 1
umask 077
wg genkey > /etc/wireguard/client1.key
wg pubkey < /etc/wireguard/client1.key > /etc/wireguard/client1.key.pub

# Für weitere Clients jeweils eindeutige Namen verwenden (client2.key, client3.key, ...)
wg genkey > /etc/wireguard/client2.key
wg pubkey < /etc/wireguard/client2.key > /etc/wireguard/client2.key.pub

Konfigurationsdatei Edge-Proxy: /etc/wireguard/wg0.conf

Code:
[Interface]
Address = 10.10.10.1/24
PrivateKey = <EdgeProxyPrivateKey>
ListenPort = 51820
PostUp = iptables -t nat -A POSTROUTING -s 10.10.10.0/24 -o <PublicInterface> -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.10.10.0/24 -o <PublicInterface> -j MASQUERADE

[Peer]
PublicKey = <Client1PublicKey>
AllowedIPs = 10.10.10.2/32

# Beispiel: zweiter Peer (Client 2)
[Peer]
PublicKey = <Client2PublicKey>
AllowedIPs = 10.10.10.3/32

Konfigurationsdatei Client 1: /etc/wireguard/wg0.conf

Code:
[Interface]
Address = 10.10.10.2/32
PrivateKey = <Client1PrivateKey>
MTU = 1420

[Peer]
PublicKey = <EdgeProxyPublicKey>
Endpoint = [IPv6-Adresse-EdgeProxy]:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

# Optional: zweiter Peer (z. B. Backup-Edge-Proxy)
[Peer]
PublicKey = <EdgeProxyBackupPublicKey>
Endpoint = [IPv6-Adresse-EdgeProxy-Backup]:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25



# Client 1
umask 077
wg genkey > /etc/wireguard/client1.key
wg pubkey < /etc/wireguard/client1.key > /etc/wireguard/client1.key.pub

# For additional clients use unique names (client2.key, client3.key, ...)
wg genkey > /etc/wireguard/client2.key
wg pubkey < /etc/wireguard/client2.key > /etc/wireguard/client2.key.pub
[/Code]

Edge Proxy configuration file: /etc/wireguard/wg0.conf

Code:
[Interface]
Address = 10.10.10.1/24
PrivateKey = <EdgeProxyPrivateKey>
ListenPort = 51820
PostUp = iptables -t nat -A POSTROUTING -s 10.10.10.0/24 -o <PublicInterface> -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.10.10.0/24 -o <PublicInterface> -j MASQUERADE

[Peer]
PublicKey = <Client1PublicKey>
AllowedIPs = 10.10.10.2/32

# Example: second peer (Client 2)
[Peer]
PublicKey = <Client2PublicKey>
AllowedIPs = 10.10.10.3/32

Client 1 configuration file: /etc/wireguard/wg0.conf

Code:
[Interface]
Address = 10.10.10.2/32
PrivateKey = <Client1PrivateKey>
MTU = 1420

[Peer]
PublicKey = <EdgeProxyPublicKey>
Endpoint = [IPv6-of-EdgeProxy]:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

# Optional: second peer (e.g., backup Edge Proxy)
[Peer]
PublicKey = <EdgeProxyBackupPublicKey>
Endpoint = [IPv6-of-EdgeProxy-Backup]:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
 
Zuletzt bearbeitet:

etron770

Member

Aufgabe: Zertifikate auch bei​

1. Ursprüngliche Konfiguration: WireGuard (IPv4-Gateway)​

Basierend auf der ersten Anleitung war die Ausgangslage eine reine Netzwerkkonfiguration.

  • Server Edge-Proxy (Dual-Stack): Fungierte als WireGuard-Server. Seine Aufgabe war es, IPv4-Traffic von reinen IPv6-Clients anzunehmen und per NAT (Masquerade) ins öffentliche IPv4-Internet weiterzuleiten.
  • Client (IPv6-only): War als WireGuard-Client konfiguriert, um IPv4-Konnektivität zu erhalten. Der Client verband sich über die IPv6-Adresse des Edge-Proxys.
  • Routing auf dem Client: Durch die Einstellung AllowedIPs = 0.0.0.0/0 leitete der Client seinen gesamten ausgehenden IPv4-Verkehr durch den Tunnel zum Edge-Proxy.
In dieser Phase ging es ausschließlich um die Bereitstellung von IPv4-Netzwerkzugang für den Client.


2. Änderung: Edge-Proxy als Reverse Proxy und der PROXY-Konflikt​

In einer späteren Phase wurde die Rolle des Edge-Proxys erweitert. Er dient nun zusätzlich als Reverse Proxy (z. B. Nginx) für den Apache-Webserver auf dem Client.

Dies führte zu einem technischen Konflikt:

  1. Port 443 (Webseiten): Der Edge-Proxy leitet SSL-Verkehr (TLS) als TCP-Passthrough an den Client weiter. Um Apache auf dem Client die echte IP-Adresse des Besuchers mitzuteilen (statt der IP des Edge-Proxys), wird das PROXY-Protokoll verwendet. Apache auf dem Client muss so konfiguriert sein, dass er dieses Protokoll erwartet.
  2. Port 80 (ACME-Validierung): Der Edge-Proxy leitet die ACME-HTTP-01-Challenges (Anfragen an /.well-known/acme-challenge/) ebenfalls an den Client weiter. Diese Weiterleitung erfolgt jedoch als Standard-HTTP ohne das PROXY-Protokoll.
Das Problem: Wenn Apache auf dem Client global so konfiguriert ist, dass er das PROXY-Protokoll erwartet (um Port 443 korrekt zu bedienen), schlagen alle Anfragen auf Port 80 fehl, da sie ohne dieses Protokoll ankommen. Dies blockiert die Erneuerung von Let's Encrypt-Zertifikaten.


3. Jetziger Stand: Workaround mittels Pre/Post-Skripten​

Um den Konflikt aus Punkt 2 zu lösen, ist der jetzige, funktionierende Stand ein Workaround, der den Apache-Server auf dem Client temporär umschaltet.

Dieser Workaround nutzt zwei Skripte, die vom ACME-Client (z. B. acme.sh) automatisch vor und nach der Validierung ausgeführt werden.

A. Vor der ACME-Validierung (pre_acme_challenge.sh)​

/usr/local/bin/pre_achme_challenge.sh
Code:
#!/bin/bash
#Script to prepare the IPv6-only web server so that certificates can be created
#The ipv6-only web server can then issue certificates, but communication via the ipv4/ipv6 proxy is interrupted
echo "######################   pre Challenge ###############################"
a2disconf 10-remoteip-proxyproto.conf
a2enconf 10-remoteip-proxyproto-off.conf
systemctl reload apache2
/usr/local/bin/post_achme_challenge.sh
Code:
#!/bin/bash
#Script to reactivate the ipv6-only web server after calling acme or certbot, to communicate via the ipv4/ipv6 proxy
a2disconf 10-remoteip-proxyproto-off.conf
a2enconf 10-remoteip-proxyproto.conf
#Since Apache is only restarted by ISPConfig when the creation of the certificates is successful, we must always restart it as a precaution.
systemctl reload apache2
/etc/apache2/conf-available/10-remoteip-proxyproto-off.conf
Code:
# PROXY protocol ausgeschaltet (global)
<IfModule remoteip_module>
    RemoteIPTrustedProxy aaa:bbb:ccc:ddd::111
    RemoteIPTrustedProxy aa.bb.cc.32
    RemoteIPProxyProtocol Off
</IfModule>
/etc/apache2/conf-available/10-remoteip-proxyproto.conf
Code:
<IfModule remoteip_module>
    # PROXY protocol nur von 'Proxyserver' akzeptieren (sicher!):
    RemoteIPTrustedProxy aaa:bbb:ccc:ddd::111
    # falls Proxyserver ggf. auch von aa.bb.cc.32 (IPv4) kommt, zusätzlich:
     RemoteIPTrustedProxy aa.bb.cc.32
    #
    # PROXY protocol global zulassen (gilt nur für TrustedProxy-Quellen)
    RemoteIPProxyProtocol On
</IfModule>

Das pre_achme_challenge.sh Skript bereitet den Client auf die Validierung vor, die ohne PROXY-Protokoll kommt:
  1. a2disconf 10-remoteip-proxyproto.conf: Die Apache-Konfiguration, die das PROXY Protocol global erzwingt, wird deaktiviert.
  2. a2enconf 10-remoteip-proxyproto-off.conf: Eine alternative Konfiguration, die kein PROXY-Protokoll erwartet, wird aktiviert.
  3. systemctl reload apache2: Apache wird neu geladen.
Ergebnis: Apache erwartet nun kein PROXY Protocol mehr. Die ACME-Validierung auf Port 80 funktioniert.

B. Nach der ACME-Validierung (post_acme_challenge.sh)​

Das post_achme_challenge.sh Skrip stellt den Normalzustand für den Webseiten-Betrieb wieder her:
  1. a2disconf 10-remoteip-proxyproto-off.conf: Die "Off"-Konfiguration wird wieder deaktiviert.
  2. a2enconf 10-remoteip-proxyproto.conf: Die ursprüngliche Konfiguration, die das PROXY Protocol erzwingt, wird reaktiviert.
  3. systemctl reload apache2: Apache wird neu geladen.
Ergebnis: Apache erwartet nun wieder das PROXY-Protokoll, und der normale Webseiten-Betrieb
Siehe auch Kommentar Cronjob:

4. ISPConfig:
ISPConfig hat eine eigene Logik, die vor der Ausführung von Certbot oder Acme überprüft, ob die Validierung möglich ist. Hier gibt es nur die Möglichkeit, das letsencrypt.inc.php Script abzuändern:
Hier ist eine Zusammenfassung des Patch-Files für ISPConfig Version 3.3.0p3 (letsencrypt.inc.php.patch):
/usr/local/ispconfig/server/cli/modules/letsencrypt.inc.php

Der vorherige Patch bedurfte einer Änderung.
Hier ein Link für ein Gitea Repository:
Grund: Im Forum sind die Zeichen auf 10.000 beschränkt, ich bekomme damit den neuesten Patch hier nicht mehr veröffentlicht. Außerdem befürchte ich, dass ich für jede ISPConfig Version einen neuen Patch erstellen muss.
 
Zuletzt bearbeitet:

etron770

Member
Der Cronjob müsste mit einem Wrapperscript versehen werden.
Es geht auch kürzer, aber damit kann man das Wrapperscript auch zum manuellen Stop und Start benutzen, um eventuell manuelle Zertifikatserstellungen zu starten.
Code:
#!/bin/bash

# --- FÜHRT DIE BEFEHLE VOR DER ACME-ERSTELLUNG AUS (Proxy OFF) ---
stop_challenge() {
    echo "Stopping normal proxy operation and preparing for ACME challenge (Proxy OFF)..."
    a2disconf 10-remoteip-proxyproto.conf
    a2enconf 10-remoteip-proxyproto-off.conf
    systemctl reload apache2
    echo "Preparation complete."
}

# --- FÜHRT DIE BEFEHLE NACH DER ACME-ERSTELLUNG AUS (Proxy ON) ---
start_normal() {
    echo "Starting normal proxy operation after ACME challenge (Proxy ON)..."
    a2disconf 10-remoteip-proxyproto-off.conf
    a2enconf 10-remoteip-proxyproto.conf
    systemctl reload apache2
    echo "Normal operation restored."
}

case "$1" in
    stop)
        # Führt die Befehle VOR der Zertifikatserstellung aus
        stop_challenge
        ;;
    start)
        # Führt die Befehle NACH der Zertifikatserstellung aus
        start_normal
        ;;
    *)
        # Fallback: Führt das komplette Skript wie ursprünglich aus
        echo "Usage: $0 {stop|start|full}"
        echo "Defaulting to full run for compatibility."
        stop_challenge
        
        echo ""
        echo "--- Executing ACME Certificate Renewal ---"
        /usr/bin/certbot renew --no-self-upgrade
        /root/.acme.sh/acme.sh --cron --force --home /root/.acme.sh
        
        start_normal
        ;;
esac


#a2disconf 10-remoteip-proxyproto.conf
#a2enconf 10-remoteip-proxyproto-off.conf
#systemctl reload apache2

#ausführen der acme Zertifikat erstellung
#/usr/bin/certbot renew --no-self-upgrade
#/root/.acme.sh/acme.sh --cron --force --home /root/.acme.sh
#> /dev/null

#a2disconf 10-remoteip-proxyproto-off.conf
#a2enconf 10-remoteip-proxyproto.conf
#systemctl reload apache2
 

Werbung

Top