So richten Sie Ihre Website für dieses süße, süße HTTPS mit Docker, Nginx und letsencrypt ein

Ich habe in der Vergangenheit letsencrypt für kostenlose Zertifikate verwendet. Ich habe es nicht erfolgreich genutzt, seit ich zu Docker / Turmfalke / Nginx gewechselt bin. Das hat sich heute geändert, und ich hatte verdammt viel Zeit damit, herauszufinden, was ich tat, um es zum Laufen zu bringen.

Dieses ganze Unix-, Docker-, Nginx-, Zeug ist (für mich) ziemlich neu, also ist es vielleicht nur etwas Einfaches, das mir die ganze Zeit gefehlt hat. Trotzdem hoffe ich, dass dies jemand anderem oder mir einige Monate später hilft, wenn ich mich entscheide, es erneut zu tun.

Ursprüngliches Setup

Ich habe eine .net-Kernwebsite, die über Turmfalke gehostet wird, auf Docker ausgeführt wird und über einen Reverse-Proxy über Nginx verfügt. Bisher funktionierte das Reverse-Proxy von Nginx nur über http / port 80. Ich weiß nicht viel über Reverse-Proxys. Vom Klang her kann es Anfragen aufnehmen und sie im Namen des Anforderers an einen bestimmten Ort weiterleiten. In meinem Fall empfängt der Nginx-Container http-Anforderungen, und Nginx leitet diese Anforderung an meine von Turmfalke gehostete .net-Core-Site weiter. Ist das richtig? Hoffnungsvoll!

Wie bereits erwähnt, arbeitete der Nginx nur mit http-Verkehr. Ich hatte große Probleme, es mit https zum Laufen zu bringen. Die ursprüngliche Konfiguration lautet wie folgt:

Docker-Compose:

version: '3.6'services: kritner-website-web: image: ${DOCKER_REGISTRY}/kritnerwebsite expose: - "5000" networks: - frontend restart: always container_name: kritnerwebsite_web kritner-website-nginx: image: nginx:latest ports: - "80:80" volumes: - ../src/nginx/nginx.conf:/etc/nginx/nginx.conf depends_on: - kritner-website-web networks: - frontend restart: always container_name: kritnerwebsite_nginx
networks: frontend:

In der Docker-Compose-Datei verwende ich zwei separate Container - die Website, auf der Port 5000 verfügbar gemacht wird (im Docker-Netzwerk nicht öffentlich), und Nginx, das auf Port 80 ausgeführt wird.

nginx.conf

worker_processes 4; events { worker_connections 1024; } http { sendfile on; upstream app_servers { server kritner-website-web:5000; } server { listen 80; location / { proxy_pass //app_servers; proxy_redirect off; 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-Host $server_name; } }}

In der Konfigurationsdatei richten wir einen Upstream-Server mit demselben Namen ein, den wir unseren Containerdienst aus der Docker-Compose-Datei aufrufen kritner-website-web:5000.

Beachten Sie, dass alle oben genannten Punkte an diesem Festschreibungspunkt im Repository meiner Website zu finden sind.

Geben Sie HTTPS ein

Letsencrypt ist eine Zertifizierungsstelle, die kostenlose Zertifikate zur Sicherung Ihrer Website anbietet. Warum ist HTTPS über TLS wichtig? Nun, das ist eine ganze Menge und wie es funktioniert. Die Grundidee ist, dass der Datenverkehr des Benutzers an beiden Enden verschlüsselt wird, bevor er an das andere Ende gesendet wird. Dies bedeutet, wenn Sie über öffentliches WLAN und über https verfügen, würde jemand, der sozusagen „am Kabel schnüffelt“, feststellen, dass Datenverkehr stattfindet, nicht jedoch der Inhalt dieses Datenverkehrs. Da beide Enden den Verkehr mit demselben Verschlüsselungsschlüssel verschlüsseln / entschlüsseln.

Wenn Sie sich auf einer http-Site befinden, wird dieser Datenverkehr im Klartext hin und her gesendet. Dies bedeutet, dass Ihre Daten in Gefahr sind, abgehört zu werden! Vielleicht schreibe ich irgendwann etwas mehr über Verschlüsselung. (* Notiz an mich selbst *) Zumal es etwas ist, was ich als meine tägliche Arbeit mache!

letsencrypt ist ein Dienst, den ich zuvor verwendet habe. Es gibt verschiedene Implementierungen, um die Verwendung so einfach wie möglich zu gestalten. Durch Recherchen für diesen Beitrag bin ich darauf gestoßen.

Obwohl ich diese Seite bis jetzt nicht gefunden hatte, wäre sie vor Beginn meines Abenteuers nützlich gewesen. Ich wollte letsencrypt zusammen mit meiner Docker-Container-Website und nginx mit so wenig Wartung wie möglich verwenden. letsencrypt-Zertifikate sind nur 90 Tage gültig.

Bei meinen Recherchen bin ich auf einen Docker-Image-Linuxserver / Letsencrypt gestoßen, der verspricht, Nginx, Letsencrypt-Zertifikatgenerierung UND automatische Erneuerung zu verwenden. Klingt großartig! Während die Dokumentation des Bildes größtenteils angemessen erscheint - für jemanden, der sich in all diesen Prozessen gut auskennt. Ich fand es fehlend. Der gesamte Einrichtungsprozess hat einige Zeit in Anspruch genommen, um dies herauszufinden. Daher dieser Beitrag, um hoffentlich der nächsten Person oder mir in Zukunft wieder zu helfen!

Kämpfe

Die Dinge, mit denen ich am meisten zu kämpfen hatte, als ich dieses Linuxserver / Letsencrypt-Image zum Laufen brachte und funktionierte, waren:

  • Wie Docker-Volumes funktionieren und in welcher Beziehung sie zu diesem Container stehen
  • So richten Sie Volumes für die Verwendung meiner Konfiguration ein (im Zusammenhang mit dem obigen Punkt) - Ich hatte anfangs große Probleme herauszufinden, warum meine Einstellungen am Container beim erneuten Laden des Containers wieder geändert wurden (weil sie genau das sind) tun soll)
  • So richten Sie die richtige Nginx-Konfiguration ein - wo und wo sie abgelegt werden soll.

Docker-Volumes

Docker-Volumes (doc):

Volumes sind der bevorzugte Mechanismus zum Speichern von Daten, die von Docker-Containern generiert und verwendet werden. Während Bindungs-Mounts von der Verzeichnisstruktur des Host-Computers abhängen, werden Volumes vollständig von Docker verwaltet. Volumes haben mehrere Vorteile gegenüber Bindemontagen

letsencrypt hat eine Menge Konfiguration, die damit einhergeht. Es dauerte eine Weile, bis mir klar wurde, aber ich brauchte ein Volume, das von einem Verzeichnis auf dem Docker-Host einem bestimmten Verzeichnis auf dem letsencrypt-Image zugeordnet wurde. Ich habe dies schließlich in der Erstellungsdatei wie folgt erreicht:

volumes: - ${DOCKER_KRITNER_NGINX}:/config - ./nginx.conf:/config/nginx/site-confs/default

Das erste Element in array ( ${DOCKER_KRITNER_NGINX}:/config) verwendet eine neue Umgebungsvariable, die das in der Variablen definierte /configHostverzeichnis dem Docker-Container selbst zuordnet. Dies bedeutet, dass der Docker-Host (im Pfad env var) dieselbe Konfiguration enthält wie der Docker-Container im sekundären Teil der Volume-Zuordnung ( /config)

Das zweite Element ( ./nginx.conf:/config/nginx/site-confs/default) ordnet meine lokale Repositorys-Datei nginx.conf (die Datei, in der ich den Reverse-Proxy eingerichtet habe) zu, um die /config/nginx/site-confs/defaultDatei auf dem Docker-Host und -Container zu überschreiben .

Die vollständige Liste der Dateien, die ich für meine spezielle Situation ändern musste, war:

  • /config/dns-conf/dnsimple.ini
  • /config/nginx/site-confs/default

Die dnsimple.iniKonfiguration war mein API-Schlüssel hinzufügen, und die …/defaultHäuser die Nginx-Konfiguration.

Die endgültige defaultKonfiguration, mit der ich endete, ist:

upstream app_servers { server kritnerwebsite:5000;}
## Version 2018/09/12 - Changelog: //github.com/linuxserver/docker-letsencrypt/commits/master/root/defaults/default
# listening on port 80 disabled by default, remove the "#" signs to enable# redirect all traffic to httpsserver { listen 80; server_name kritnerwebsite; return 301 //$host$request_uri;}
# main server blockserver { listen 443 ssl;
# enable subfolder method reverse proxy confs include /config/nginx/proxy-confs/*.subfolder.conf;
# all ssl related config moved to ssl.conf include /config/nginx/ssl.conf; # enable for ldap auth #include /config/nginx/ldap.conf;
client_max_body_size 0;
location / { proxy_pass //app_servers; proxy_redirect off; 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-Host $server_name; }
}
# enable subdomain method reverse proxy confsinclude /config/nginx/proxy-confs/*.subdomain.conf;# enable proxy cache for authproxy_cache_path cache/ keys_zone=auth_cache:10m;

Es gibt einige Änderungen gegenüber der Standardeinstellung, die ich als nächstes hervorheben werde.

upstream app_servers { server kritnerwebsite:5000;}

Oben ist ziemlich cool, da Docker ein eigenes internes DNS hat (denke ich?). Sie können einen Upstream-Server anhand des Containernamens einrichten, in meinem Fall "kritnerwebsite". (Hinweis: Ich habe es von früher in dem Beitrag geändert, der "kritner-website-web" war.)

# listening on port 80 disabled by default, remove the "#" signs to enable# redirect all traffic to httpsserver { listen 80; server_name kritnerwebsite; return 301 //$host$request_uri;}

Uncommented out this section from the default, applied my server_name of “kritnerwebsite”

# main server blockserver { listen 443 ssl;
# enable subfolder method reverse proxy confs include /config/nginx/proxy-confs/*.subfolder.conf;
# all ssl related config moved to ssl.conf include /config/nginx/ssl.conf; # enable for ldap auth #include /config/nginx/ldap.conf;
client_max_body_size 0;
location / { proxy_pass //app_servers; proxy_redirect off; 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-Host $server_name; }
}

In the above, it’s mostly from the “default” save for “location” and everything within that object. Here, we’re setting up the reverse proxy to forward requests to “/” (anything) to our //app_servers (kritnerwebsite as per our upstream).

docker-compose.yml

Our docker compose file didn’t change a *whole* lot from the initial. There were a few notable changes, which I’ll also get into describing:

version: '3.6'services: nginx: image: linuxserver/letsencrypt ports: - "80:80" - "443:443" volumes: - ${DOCKER_KRITNER_NGINX}:/config - ./nginx.conf:/config/nginx/site-confs/default depends_on: - kritnerwebsite networks: - frontend container_name: nginx environment: - PUID=1001 # get on dockerhost through command "id "" - PGID=1001 - [email protected] - URL=kritner.com - SUBDOMAINS=www - TZ=America/NewYork - VALIDATION=dns # using dns validation - DNSPLUGIN=dnsimple # via dnsimple, note there is additional configuration require separate from this file # - STAGING=true # this should be uncommented when testing for initial success, to avoid some rate limiting
kritnerwebsite: image: ${DOCKER_REGISTRY}/kritnerwebsite networks: - frontend expose: - "5000" restart: always container_name: kritnerwebsite networks: frontend:

for the new parts:

nginx: image: linuxserver/letsencrypt

Using a different image — linuxserver/letsencrypt instead of nginx. This image has nginx included, but also certbot, along with a cronjob to run certbot at application start.

ports: - "80:80" - "443:443"

Now we’re using both http and https ports (though note, we’re redirecting http calls to https via the nginx config).

volumes: - ${DOCKER_KRITNER_NGINX}:/config - ./nginx.conf:/config/nginx/site-confs/default

Already discussed earlier in the post, we’re using these volumes to properly set up the nginx configuration, with our dnsimple api key, as well as our reverse proxying to the kritnerwebsite.

environment: - PUID=1001 # get on dockerhost through command "id " - PGID=1001 - [email protected] - URL=kritner.com - SUBDOMAINS=www - TZ=America/NewYork - VALIDATION=dns # using dns validation - DNSPLUGIN=dnsimple # via dnsimple, note there is additional configuration require separate from this file # - STAGING=true # this should be uncommented when testing for initial success, to avoid some rate limiting

Environment variables needed as per the letsencrypt documentation can be found here.

  • PUID/PGID — get on dockerhost through command “id ”
  • Email — well, your email (used for cert expiration emails apparently)
  • URL — the main domain URL
  • subdomains — any subdomains to the URL to be certified
  • TZ — timezone
  • Validation — the type of validation to do — I’m using DNSimple, so i needed DNS in this field. Other options are html, tls-sni
  • dnsplugin — dnsimple — other options are cloudflare, cloudxns, digitalocean, dnsmadeeasy, google, luadns, nsone, rfc2136 and route53 as per the letsencrypt documentation
  • Staging=true — I used this for testing out all my various attempts prior to getting it working. letsencrypt has rate limiting when not running in staging mode (or at least in staging it’s harder to run up against).

All the above changes, experimenting, failing, and then finally succeeding can be found in this pull request.

The final result?

and from //www.ssllabs.com/ —

Not an “A+”, but really not bad for using one pre-built docker image for my HTTPs needs!

Related:

  • Going from an “A” to an “A+” on ssllabs.com