2. Reverse Proxy¶
Installation¶
Zunächst wird nginx auf dem System installiert.
sudo apt install -y nginx-full
Für die Installation von acme.sh wird zunächst in den root-Kontext gewechselt. Dies ist notwendig, weil acme.sh in das Nutzerverzeichnis installiert wird.
sudo -s
# acme.sh installieren und default ca auf Let's Encrypt setzen
curl https://get.acme.sh | sh -s email=acme@domain.de
ln -s /root/.acme.sh/acme.sh /usr/bin/acme.sh
acme.sh --install-cronjob
acme.sh --server "https://acme-v02.api.letsencrypt.org/directory" --set-default-ca
Anschließend kann die Datei /etc/nginx/sites-available/default durch folgenden Codeblock ersetzt werden.
server {
listen 0.0.0.0:80;
listen [::]:80;
http2 on;
location / {
return 301 https://$host$request_uri;
}
}
Sofern geplant ist, jedem Virtual Host eine eigene IPv6 Adresse zu geben empfiehlt sich den nginx systemd-Service um einige Sekunden zu verzögern, sodass sichergestellt werden kann, dass das System die IPv6 Adressen der Netzwerkschnittstelle bereits hinzugefügt hat.

Dazu muss in der Datei /lib/systemd/system/nginx.service vor der ersten ExecStartPre Zeile folgendes hinzugefügt werden:
# Make sure the additional ipv6 addresses (which have been added with post-up)
# are already on the interface (only required for enabled nginx service on system boot)
ExecStartPre=/bin/sleep 5
Zunächst wird die Containerdefinition im Verzeichnis /home/admin/traefik/docker-compose.yml angelegt:
services:
traefik:
image: traefik:v2.9
restart: always
command:
- "--api.insecure=true"
- "--metrics.prometheus=true"
#- "--log.level=DEBUG"
- "--accesslog=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--providers.file.directory=/configs/"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.websecure.address=:443"
#- "--entrypoints.websecure.http.middlewares=mw_hsts@file,mw_compress@file"
- "--entryPoints.websecure.http.tls=true"
- "--entryPoints.websecure.http.tls.certresolver=myresolver"
- "--entryPoints.websecure.http.tls.domains[0].main=domain.de"
- "--entryPoints.websecure.http.tls.domains[0].sans=*.domain.de"
- "--certificatesresolvers.myresolver.acme.dnschallenge=true"
- "--certificatesresolvers.myresolver.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.myresolver.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
- "--certificatesresolvers.myresolver.acme.dnschallenge.delayBeforeCheck=10"
- "--certificatesresolvers.myresolver.acme.email=admin@domain.de"
- "--certificatesresolvers.myresolver.acme.storage=/acme/acme.json"
#- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
labels:
- "traefik.enable=true"
- "traefik.http.services.srv_traefik.loadbalancer.server.port=8080"
- "traefik.http.routers.r_traefik.rule=Host(`traefik.domain.de`)"
- "traefik.http.routers.r_traefik.entrypoints=websecure"
env_file: .traefik.env
ports:
- "80:80"
- "443:443"
volumes:
- "/srv/traefik/acme:/acme"
- "/srv/traefik/dynamic.yml:/configs/dynamic.yml"
- "/srv/traefik/middlewares.yml:/configs/middlewares.yml"
- "/etc/localtime:/etc/localtime:ro"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
networks:
- "proxy"
static:
image: nginx:stable-alpine
restart: always
labels:
# BASIC CONFIGURATION
- "traefik.enable=true"
- "traefik.http.services.srv_static.loadbalancer.server.port=80"
# ERROR PAGES
# you can use my error_pages: https://github.com/felbinger/AdminGuide/tree/master/error_pages
- "traefik.http.middlewares.error40x.errors.status=403-404"
- "traefik.http.middlewares.error40x.errors.service=srv_static"
- "traefik.http.middlewares.error40x.errors.query=/error/{status}.html"
- "traefik.http.middlewares.error30x.errors.status=300-308"
- "traefik.http.middlewares.error30x.errors.service=srv_static"
- "traefik.http.middlewares.error30x.errors.query=/error/30x.html"
# DOMAIN ROOT CONTENT
- "traefik.http.routers.r_static_root.rule=HostRegexp(`domain.de`, `{subdomain:[a-z0-9]+}.domain.de`)"
- "traefik.http.routers.r_static_root.entrypoints=websecure"
- "traefik.http.routers.r_static_root.priority=10"
- "traefik.http.middlewares.mw_static_root.addprefix.prefix=/domain_root/"
- "traefik.http.routers.r_static_root.middlewares=mw_static_root@docker,error40x@docker,error30x@docker"
volumes:
- "/srv/static/webroot:/usr/share/nginx/html/"
networks:
- "proxy"
networks:
proxy:
external: true
Wird Traefik hinter einem IPv4-to-IPv6 Proxy eingesetzt sollte dieser lediglich auf IPv6 exposed werden.
services:
traefik:
# ...
ports:
- "[::]:80:80"
- "[::]:443:443"
Nun müssen noch einige Konfigurationen angelegt werden:
# /srv/traefik/middlewares.yml
http:
middlewares:
mw_compress:
compress: true
mw_hsts:
headers:
contentTypeNosniff: true
browserXssFilter: true
forceSTSHeader: true
sslRedirect: true
stsPreload: true
stsSeconds: 315360000
stsIncludeSubdomains: true
customResponseHeaders:
X-Forwarded-Proto: https
X-Frame-Options: sameorigin
# /srv/traefik/dynamic.yml
tls:
options:
default:
minVersion: VersionTLS13
sniStrict: true
cipherSuites:
# TLS 1.3
- TLS_AES_256_GCM_SHA384
- TLS_CHACHA20_POLY1305_SHA256
- TLS_AES_128_GCM_SHA256
Anschließend wird das proxy Docker Netzwerk angelegt und Traefik gestartet.
docker network create proxy
docker compose up -d
Konfiguration für neue Dienste¶
Für das einbinden eines webbasierten Dienstes in nginx sind eine Reihe von Schritten erforderlich.
Zunächst muss die Containerdefinition des Dienstes um ein entsprechendes Port-Bindung erweitert werden. Durch dieses wird der Port aus dem Container an das Hostsystem exposiert wird und lokal angesprochen werden kann. Dabei darf natürlich nur die linke Seite (hier 8081) verändert werden.
Tipp
Schon verwendete Ports lassen sich mit folgendem Befehl ermitteln:
grep -oP "(?<=proxy_pass)[^;]*" /etc/nginx/sites-enabled/* | sed "s/ /\t/" | expand -t 30
Zur leichteren Verwendung empfielt sich das hinzufügen folgender Funktion in der Datei ~/.bashrc:
function searchport {
grep -oP "(?<=proxy_pass)[^;]*" /etc/nginx/sites-enabled/* | sed "s/ /\t/" | expand -t 30 | grep ${1:-.}
}
searchport aufzulisten, ergibt sich die Möglichkeit den ersetzen Parameter zu setzen (searchport 8081) um den zu einem Port gehörenden Domainnamen anzuzeigen.
In der docker-compose.yaml des jeweiligen Dienstes muss demnach folgendes hinzugefügt werden:
ports:
- "[::1]:8081:80"
Anschließend muss ein TLS Zertifikat für die gewünschte Domain auf der, der Dienst erreichbar sein soll angefordert werden.
# Beispielkonfiguration für Cloudflare DNS API
CF_Token=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX acme.sh --issue --keylength ec-384 --dns dns_cf -d service.domain.de
Optional kann nun eine eigene IPv6 Adresse für diesen virtual Host konfiguriert werden:
# /etc/network/interfaces
# ...
iface eth0 inet6 static
# ipv6 address of the host
address 2001:db8:1234:5678::1/64
gateway 2001:db8::1
# service.domain.de
post-up ip -6 a add 2001:db8:1234:5678:5eca:dc9d:fd4e:6564/64 dev $IFACE
pre-down ip -6 a del 2001:db8:1234:5678:5eca:dc9d:fd4e:6564/64 dev $IFACE
Da Ubuntu netplan zum Konfigurieren der Netzwerkeschnittstellen verwendet, muss die entsprechende Konfiguration im
Verzeichnis /etc/netplan angepasst werden.
Die Konfigurationsdatei sollte ungefähr wie folgt aussehen:
network:
version: 2
renderer: networkd
ethernets:
enp1s0:
addresses:
- 10.10.10.2/24
- 2001:db8::5/64
dhcp4: no
routes:
- to: 0.0.0.0/0
via: 10.10.10.1
- to: ::/0
via: 2001:db8::1
nameservers:
addresses: [10.10.10.1, 1.1.1.1, 2001:470:20::2]
addresses Abschnitt die neue IPv6 Adresse wie
folgt hinzu:
addresses:
...
- 2001:db8:4a:90a:d8d5:dbf4:fd80:8f80
Nun kann der V-Host unter dem Pfad /etc/nginx/sites-available/service.domain.de erstellt werden:
Note
Standardmäßig wird der nginx auf beiden Adressfamilien exposiert.
Ist lediglich IPv6 (z. B. für die Verwendung eines Proxy Servers) erwünscht,
müssen die listen Direktiven im Serverblock wie folgt angepasst werden:
listen [::]:80;
Ist weitergehend die Verwendung einer eigenen IPv6 Adresse pro Service erwünscht, sollte diese anstelle von :: eingefügt werden:
listen [2001:db8::dead]:80;
# https://ssl-config.mozilla.org/#server=nginx&version=1.27.3&config=modern&openssl=3.4.0&ocsp=false&guideline=5.7
server {
server_name service.domain.de; # <---
listen 0.0.0.0:443 ssl;
listen [::]:443 ssl;
ssl_certificate /root/.acme.sh/service.domain.de_ecc/fullchain.cer;
ssl_certificate_key /root/.acme.sh/service.domain.de_ecc/service.domain.de.key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
# modern configuration
ssl_protocols TLSv1.3;
ssl_ecdh_curve X25519:prime256v1:secp384r1;
ssl_prefer_server_ciphers off;
# HSTS (ngx_http_headers_module is required) (63072000 seconds)
add_header Strict-Transport-Security "max-age=63072000" always;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
location / {
proxy_pass http://[::1]:8000/; # <---
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header X-Real-IP $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Zum Abschluss kann die Konfiguration aktiviert, getestet und angewandt werden.
ln -s /etc/nginx/sites-available/service.domain.de \
/etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Für das einbinden eines webbasierten Dienstes in Traefik sind lediglich zwei Schritte notwendig.
Zunächst muss das proxy-Netzwerk dem Container hinzugefügt werden. Dabei ist zu beachten, dass
- sofern dieser mit anderen Containern in der gleichen Containerdefinition - interagieren muss,
ebenfalls das default-Netzwerk benötigt, welches der Standardwert für Container ohne explizite
Netzwerkkonfiguration ist:
networks:
- "proxy"
#- "default"
Außerdem müssen die Docker Labels für das HTTP Routing gesetzt werden:
labels:
- "traefik.enable=true"
- "traefik.http.services.srv_service-name.loadbalancer.server.port=80"
- "traefik.http.routers.r_service-name.rule=Host(`service.domain.de`)"
- "traefik.http.routers.r_service-name.entrypoints=websecure"
Warning
Hierbei sollte unbedingt darauf geachtet werden, dass weder service (Präfix srv_),
noch router-Bezeichnungen (Präfix r_) doppelt verwendet werden, da dies zu schwer
bemerkbaren Fehlern führen kann.
Außerdem sollte auf die korrekte Konfiguration des Service Ports geachtet werden (hier 80).