Nginx-LetsEncrypt Reverse Proxy in der Praxis

 

Um auf einem Server mehrere Webservices mit entsprechenden SSL-Zertifikaten zu betreiben, bietet sich neben Traefik, das Projekt Nginx Proxy Automation an. Bei dem Setup handelt es sich um mehrere Docker-Container die zusammen einen Nginx-Reverse-Proxy inklusive Zertifikats-Verwaltung und SSL-Offloading zur Verfügung stellen. Sind die Container über Docker-Compose einmal gestartet, kümmern diese sich um die Bereitstellung mehrerer Websites über eine gemeinsame IP und um dessen Zertifikats-Verwaltung: für das Ausstellen der Zertifikate für neue Container und das Erneuern dieser für bestehende Container: alles im laufenden Betrieb und innerhalb weniger Sekunden. Alternativ zu Nginx Proxy Automation können auch die bekannteren Reverse-Proxy Ngnix Proxy Manager oder Traefik verwendet werden.

Schematische Darstellung

Hier schematisch dargestellt, anhand der Seiten www.libe.net und www.script-example.com:

NGINX Proxy Automation horcht nach dem Starten der Container auf Port 80 und 443. Die beiden Ports müssen im Internet unter einer öffentlichen IP verfügbar sein:

Als Webserver kann ein virtueller oder Cloud-Server eines shared Hosters verwendet werden, oder der eigene PC zu Hause mit einer eingerichteten Port-Weiterleitung am Router.

D. h. wer im Besitz einer Domäne ist, kann z. B. einen A-Record der Domain oder einer Subdomain auf die öffentliche IP des Servers oder PCs zeigen lassen. Alternativ können auch Dienste wie DynDNS dazu verwendet werden. Siehe: free DynDNS Service - Zugriff bei wechselnder öffentlicher IP. Im Falle eines PCs, der zu Hause steht, kann – falls es der Provider zulässt – die öffentliche IP des Providers für den DNS Eintrag verwendet und am Router ein Port-Forwarding des Ports 443 auf die IP des PCs erstellt werden, siehe: port-forwarding.

Installation – Nginx Let’s Encrypt Proxy

Docker Basics

Docker ermöglicht es, Services oder Applikationen per Befehl in einem sogenannten Container zu starten.
Ein Container ist eine vom Betriebssystem (OS) unabhängige isolierte Umgebung:
Beim ersten Start eines Containers, lädt Docker selbstständig alle notwendigen Quellen
aus dem Internet.
Docker kann unter Windows, macOS oder einer Linux-Distribution installiert werden,
siehe auch: Docker
Als Voraussetzung für das Setup wird natürlich Docker benötigt, siehe: Docker Installation Linux oder Docker Installation Windows.

FĂĽr einen schnellen Start kann folgendes Repo geklont werden: github.com/evertramos/nginx-proxy-automation. Die aktuelle Version des Projektes ist:

SoftwareNginx-proxy-automation
GitHubhttps://github.com/evertramos/nginx-proxy-automation
aktuelle Version 0.6
gefunden30.09.2021

Konkret werden für die Inbetriebnahme folgenden Befehle benötigt:

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install git 
cd /var
mkdir docker
git clone --recurse-submodules https://github.com/evertramos/nginx-proxy-automation.git webproxy 
cd webproxy/bin && ./fresh-start.sh --yes -e your_email@domain --skip-docker-image-check

Als Netzwerk für den Proxy habe ich „webproxy“ im Wizard angegeben.

Nach dem Start können zusätzlich beliebig viele Websites gestartet werden. Der nginx-Proxy passt seine Konfiguration automatisch anhand der Eigenschaften der Container an. Damit die Webserver für dessen Domains auch ein Zertifikat bekommen, bzw. aus dem Internet erreichbar sind, reicht es als Eigenschaften für die Container folgende Werte zu hinterlegen:

  • -e VIRTUAL_HOST=???.domain.tld
  • -e LETSENCRYPT_HOST=???.domain.tld
  • -e LETSENCRYPT_EMAIL=admin@domain.tld
  • --network=webproxy

Die Variable „VIRTUAL_HOST“ gibt vor, auf welchen Container der Reverse-Proxy den Traffic für diese Domäne durchstellen soll. „LETSENCRYPT_HOST“ ist der DNS-Name für das Lets Encrypt-Zertifikat (sollte gleich „VIRTUAL_HOST“ sein) und „LETSENCRYPT_EMAIL“ die Mail-Adresse die beim Ausstellen des Zertifikats angegeben wird. Das Netzwerk „webproxy“ wurde als Backend-Netzwerkname in der zuvor heruntergeladenen Docker-Compose-Konfig verwendet. Nachdem die Domains im nginx-Proxy bekannt gemacht wurden, werden die Anfragen auf den Container über das Netzwerk webproxy des Reverse-Proxys geleitet.

Beispiel: Start einer Website

Bevor wir die erste Website starten, muss ein A-Record im DNS hinterlegt werden, welcher auf die öffentliche IP-Adresse des Webservers zeigt. Im Anschluss kann ein einfacher Webserver mit folgenden Befehlen im Internet zur Verfügung gestellt werden, inkl. SSL-Zertifikat:

docker run -d -e VIRTUAL_HOST=???.domain.tld \
              -e LETSENCRYPT_HOST=???.domain.tld \
              -e LETSENCRYPT_EMAIL=admin@domain.tld \
              --network=webproxy \
              --name ??? \
              -v /var/www/???/httpdocs:/var/www \
              httpd:alpine

Was macht der NGINX Proxy Automation genau?

NGINX Proxy Automation verwendet die Eigenschaften anderer Container und trägt diese als Upstream-Server ein. Er fungiert als Reverse-Proxy und leitet sämtliche Anfragen weiter. Für die Weiterleitung kann Port 80 verwendet werden, für den Zugriff auf die gehosteten Seiten 80 oder 443. Für https, Port 443 wird das SSL-Zertifikat vom Reverse-Proxy verwaltet und verlängert: SSL-Offloading. 

Zertifikat ausstellen und verlängern

Die Zertifikats-Verwaltung läuft mit diesem Setup automatisch. Ein Blick in die Logs verrät, was im Hintergrund passiert: Der Container nginx-letsencrypt kontrolliert beim Start und alle 60 Minuten die Zertifikate. Sollte ein neues Zertifikat notwendig sein, wird dieses angefordert und verwendet:

nginx-letsencrypt    | /etc/nginx/certs/www.libe.net /app
nginx-letsencrypt    | Creating/renewal www.libe.net certificates... (www.libe.net)
nginx-letsencrypt    | 2021-03-14 11:09:24,871:INFO:simp_le:1414: Generating new certificate private key
nginx-web            | www.libe.net 18.196.96.172 - - [14/Mar/2021:11:09:26 +0000] "GET /.well-known/acme-challenge/??? HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
nginx-web            | www.libe.net 3.128.26.105 - - [14/Mar/2021:11:09:26 +0000] "GET /.well-known/acme-challenge/??? HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
nginx-web            | www.libe.net 34.211.6.84 - - [14/Mar/2021:11:09:26 +0000] "GET /.well-known/acme-challenge/??? HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
nginx-web            | www.libe.net 66.133.109.36 - - [14/Mar/2021:11:09:26 +0000] "GET /.well-known/acme-challenge/??? HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
nginx-letsencrypt    | 2021-03-14 11:09:29,190:INFO:simp_le:396: Saving key.pem
nginx-letsencrypt    | 2021-03-14 11:09:29,191:INFO:simp_le:396: Saving chain.pem
nginx-letsencrypt    | 2021-03-14 11:09:29,193:INFO:simp_le:396: Saving fullchain.pem
nginx-letsencrypt    | 2021-03-14 11:09:29,193:INFO:simp_le:396: Saving cert.pem

Rate-Limit: here were too many requests of a given type :: Error creating new order

Sollte zum Beispiel der DNS noch nicht auf die öffentliche IP-Adresse zeigen und der Container dennoch bereits laufen, ist etwas Geduld gefragt. Ich hatte an dieser Stelle nicht so viel Geduld und NGINX Proxy Automation mehrfach neu gestartet, woraufhin dieser bei jedem Start um ein Zertifkat angefragt hat, welches aufgrund des DNS-Eintrags aber nicht ausgestellt werden konnte. An dieser Stelle habe ich gelernt, dass pro Stunde nur 5 Anfragen für ein neues Zertifikat erlaubt sind, in den Logs spiegelt sich das wie folgt, wider:

Attaching to nginx-letsencrypt, nginx-gen, nginx-web
...
nginx-gen            | 2021/03/14 10:09:12 Generated '/etc/nginx/conf.d/default.conf' from 12 containers
...
nginx-gen            | 2021/03/14 10:09:12 Contents of /etc/nginx/conf.d/default.conf did not change. Skipping notification ''
...
nginx-letsencrypt    | Creating/renewal www.script-example.com certificates... (www.script-example.com)
nginx-letsencrypt    | 2021-03-14 10:09:19,372:INFO:simp_le:1546: Certificates already exist and renewal is not necessary, exiting with status code 1.
...
nginx-letsencrypt    | Creating/renewal www.libe.net certificates... (www.libe.net)
nginx-letsencrypt    | 2021-03-14 10:09:17,961:INFO:simp_le:1414: Generating new certificate private key
nginx-letsencrypt    | ACME server returned an error: urn:ietf:params:acme:error:rateLimited :: There were too many requests of a given type :: Error creating new order :: too many failed authorizations recently: see https://letsencrypt.org/docs/rate-limits/
...
nginx-letsencrypt    | Sleep for 3600s

Die Lösung: eine Stunde warten. NGINX Proxy Automation versucht es in der nächsten Stunde wieder und wenn der DNS dann passt, wird auch ein Zertifikat ausgestellt und verwendet. 

Mehrere Subdomains (SAN)

Let's Encrypt-Zertifikate können auf mehrere Subdomains ausgestellt werden, ein einfaches gängiges Beispiel wäre die Root-Domäne und die Subdomain mit einem www-Prefix: https://libe.net und https://www.libe.net. Als Beispiel können mit folgendem Docker-Aufruf Zertifikate für beide URLs angefordert werden:

docker run -d -e VIRTUAL_HOST=www.libe.net,libe.net \
              -e LETSENCRYPT_HOST=www.libe.net,libe.net \
              -e LETSENCRYPT_EMAIL=admin@domain.tld \
              --network=webproxy \
              --name ??? \
              -v /var/www/???/httpdocs:/var/www \
              httpd:alpine

Als Voraussetzung müssen natürlich alle DNS-Einträge der Subdomains vorhanden sein und auf den Server zeigen.

In den Eigenschaften des Zertifikats spiegeln sich zusätzliche Subdomains als „Alternativer Antragstellername“  (SAN: Subject Alternative Name) wider:

Mit diesem Setup sollte die Website auf allen Domains erreichbar sein, meist soll der Internetauftritt aber primär eine URL verwenden: z.B. „www.libe.net“. Der Aufruf über „libe.net“ sollte dann auf „www.libe.net“ umgeleitet werden:

URLs umleiten: Nicht-www auf www

Wer mehrere Subdomains beim Starten des Containers angegeben hat (SAN), kann fĂĽr bestimmte Subdomains eine 301 Umleitung auf eine andere Subdomain erstellen.

Damit jetzt die domain.tld auf www.domain.tld umgeleitet wird, kann unter /data/vhost.d eine Datei mit dem exakten Namen der Domain (domain.tld) angelegt und innerhalb der Datei die Umleitung platziert werden:

if ($request_uri !~ "^/.well-known/acme-challenge")
{
  return 301 https://www.domain.tld$request_uri;
}

Reload nginx

Mit folgendem Befehl kann die nginx-Konfiguration neu geladen werden:

docker exec -it proxy-web-auto nginx -s reload

Logs

Details zum Ausstellen und Verlängern der Zertifikate können über folgendes Log angezeigt werden:

docker logs -f letsencrypt-auto

Update von Version 0.4

Ich setzte NGINX Proxy Automation bereits seit Version 0.4 ein, siehe github.com/evertramos/nginx-proxy-automation/tree/v0.4. Da sich mit Version 0.5 an den Skripts einiges geändert hat, muss das Update laut Anleitung durchgeführt werden, zum Beispiel auf die Version 0.6:  github.com/evertramos/nginx-proxy-automation/blob/master/docs/upgrade-guide.md. Bei dem Update ist etwas Geduld gefragt, da die Zertifikate dabei neu erstellt werden, was je nach Anzahl der Webservices die ein oder andere Minute dauern kann.

413 Request Entity too Large

Wer beim Setup die Option „Use NGINX Conf Files“ nicht ausgewählt hat, kann dies wie folgt nachholen:

Dazu muss in der Datei „.env“ das Verarbeiten der Conf-Files folgende Zeile hinzugefügt werden:

USE_NGINX_CONF_FILES=true

Im Anschluss können im Ordner data/conf.d/ zusätzliche Konfigurations-Files hinterlegt werden, als Beispiel das Anpassen der „client_max_body_size“ um die Uploadgrößen zu erhöhen und somit den Fehler 413 Request Entity too Large zu umgehen:

Datei: /data/conf.d/uploadsize.conf

client_max_body_size 100m;

Webservices

Die beschriebene Lösung kann dann für den Betrieb anderer Webservices verwendet werden:

Auch wenn ich die gelisteten Websites mittlerweile auf den Traefik-Reverse-Proxy umgestellt habe, könnten diese auch mit dem hier vorgestellten Let´s Encrypt-nginx-proxy-companion von Evert Ramos betrieben werden. Anstelle von Umgebungsvariablen müssen dazu Labels in der Docker-Compose-Konfiguration verwendet werden.

Wer diesen Reverse Proxy anstelle von Traefik verwenden will, sollte am einfachsten die Umgebungsvariablen gegen Labels austauschen.
Hier ein Vergleich der docker-compose.yml Settings beider Proxys: NGINX Proxy Automation vs. Traefik; Environment vs. Labels:

NGINX Proxy Automation Traefik-Reverse-Proxy
version: "3"
services:
  webserver:
    image: httpd:alpine
    container_name: webserver
    environment:
      VIRTUAL_HOST: 'webserver.domain.tld'
      VIRTUAL_PORT: '80'
      LETSENCRYPT_HOST: 'webserver.domain.tld'
      LETSENCRYPT_EMAIL: 'admin@domain.tld'
    restart: always
version: "3"
services:
  webserver:
    image: httpd:alpine
    container_name: webserver
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.webservice1.rule=Host(`webserver.domain.tld`)"      
      - "traefik.http.routers.webservice1.entrypoints=web"
      - "traefik.http.routers.webservice1.entrypoints=websecure"
      - "traefik.http.routers.webservice1.tls.certresolver=myresolver"
      - "traefik.http.services.webservice1.loadbalancer.server.port=80"
    restart: always

 

positive Bewertung({{pro_count}})
Beitrag bewerten:
{{percentage}} % positiv
negative Bewertung({{con_count}})

DANKE fĂĽr deine Bewertung!

Fragen / Kommentare


Durch die weitere Nutzung der Seite stimmst du der Verwendung von Cookies zu Mehr Details