Ich verwende hier als Beispiel-Domain gnulinux.ch. Die Subdomains, die ich verwende, werden zum Zeitpunkt, an dem dieser Artikel entsteht, nicht verwendet. Ich verwende keine unbenutzte Adresse, da es ja möglich ist, dass diese irgendwann doch vergeben wird.
Bei der Konzeption des Servers lege ich besonderen Wert auf Wiederherstellbarkeit. Ich möchte sehr einfach auch nur den Teil eines Dienstes aus einem Backup wieder herstellen können oder einen Dienst auf einem anderen Host aus dem Backup aufsetzen. Deshalb bin ich vorsichtig mit Tools, mit denen man mehrere Docker Stacks aufsetzten kann oder so etwas. Bei meiner Lösung kann man die Verzeichnisse der Container einfach sichern und wieder zurückkopieren. Bei Diensten ohne persistente Daten ist dies natürlich nicht so wichtig.
Bei den Docker-Images verwende ich hier die :latest-Versionen, was ich bei wichtigen Diensten bei mir nie mache. Ich gebe immer eine konkrete Version an, damit ich den gleichen Zustand wieder herstellen kann.
Ich habe schon sehr lange einen eigenen Server, viel länger als es Docker oder NPM gibt. Ich versuche, mit der Zeit zu gehen. Dabei habe ich aber immer die obige Backup-Strategie im Hinterkopf. Die ist mir noch wichtiger, als die neuesten Tools und Features. Ich hoffe, Euch kommt der Artikel trotzdem nicht vor wie von Eurem Opa.
Kommunikationsweg
Im Titelbild habe ich versucht, alle Beteiligten einer https-Anfrage zu veranschaulichen:
- Nutzer gibt https://ich-mag.gnulinux.ch im Browser ein
- Browser fragt DNS-Server nach der IP-Adresse
- Vorher hat der Domain-Hoster den DNS-Eintrag auf den DNS-Server verteilt
- Jetzt macht der Browser eine Verbindung zur IP-Adresse (des Routers)
- Der Router hat ein Port-Forwarding konfiguriert und leitet die Anfrage an den Reverse-Proxy weiter
- Der Reverse-Proxy hat vorher ein SSL-Zertifikat bei let’s encrypt erzeugt
- Der Reverse-Proxy leitet das SSL-Zertifikat an den Browser
- Der Browser prüft das Zertifikat
- Der Reverse-Proxy hat für die URL ich-mag.gnulinux.ch eine Weiterleitung an den Port von Server2 (in der Regel nicht Port 80), der von Docker1 als http-Port exportiert wird
- In Docker1 wird der Port auf den Standard Http-Port 80 weitergeleitet
- Jetzt ist die Anfrage beim Service1 und wird bearbeitet
Komponenten
Router
Am Router sollten für IPv4 zwei Ports weitergeleitet werden: Port 80 (http) und 443 (https). Diese sollten am besten auf die gleichen Ports von Server1 zeigen. Auf diesem werden dann wiederum die gleichen Ports des Reverse Proxy Docker-Containers gemappt (kommt unten).
Für IPv6 müssen die genannten Ports geöffnet werden (was auch oft weitergeleitet genannt wird). Dies ist für alle nötig, die keine richtige IPv4 Adresse mehr haben. Weitere Infos dazu in meinem letzten Artikel.
Domain-Hoster
Für den Eintrag der IP beim DNS gibt es zwei Möglichkeiten:
Dynamic DNS
Hier gibt es viele Dienstleister. Falls die IP nicht fest ist, muss sie regelmäßig aktualisiert werden. Das kann entweder im Router passierten (Fritz!Box kann das mit einigen Anbietern) oder mit einem Service am Server. no-ip.com bietet da z.B. fertige Skripte für systemd an. Mit dieser Lösung hat man natürlich keine eigene Domain, es sollte aber fürs erst funktionieren.
Eigene Domain
Wer eine eigene Domain will, muss sich an einen Domain-Anbieter wenden. Bei lima-city.de z.B. kostet das keine 10 € im Jahr. In der Domain-Verwaltung des Hosters muss dann noch ein Eintrag gemacht werden, abhängig von fester IP oder nicht:
Feste IP
Für IPv4 muss ein A-Record angelegt werden:
- Name: *.gnulinux.ch, Inhalt: IP-Adresse des Routers.
Für IPv6 muss ein AAAA-Record angelegt werden:
- Name: *.gnulinux.ch, Inhalt: IP-Adresse des Servers.
Dynamische IP
Es muss ein CNAME-Record angelegt werden:
- Name *.gnulinux.ch, Inhalt: Dynamische Adresse.
Dieser zeigt zum Beispiel auf einen Eintrag bei no-ip.com, wo wiederum Einträge für IPv4 und/oder IPv6 konfiguriert sind.
Reverse-Proxy
Der Reverse Proxy ist die nächste wichtige Komponente. Ich verwende Nginx-Proxy-Manager (NPM). Alternativen sind Traefik oder Caddy. Da kann ich aber noch gar nichts dazu sagen.
Ich kann aber sagen, dass auch bei dieser Komponente das Backup wichtig ist. Bei manchen Diensten ist die Nginx Konfiguration ziemlich hakelig, man bastelt einige Zeit hin, bis alles funktioniert. Wenn man dann aus einem Backup das System schnell wieder aufsetzen will, ist es ärgerlich, wenn man dann wieder alles herausfinden muss. Bei NPM gibt es zwar (bisher) keine Backup-Funktion, aber im Ordner ./data sind die Konfigurationen für alle Hosts gespeichert.
Aufgaben
Der Reverse-Proxy hat (bei mir) folgende Aufgaben:
- Komplettes SSL Handling. Er holt die Zertifikate von Let’s Encrypt und liefert diese an den Browser aus.
- Weiterleitung einer angefragten URL an den richtigen Server und Port.
Installation
Es kann ein beliebiger Rechner verwendet werden, der vom Router erreichbar ist und der auf die Server zugreifen kann.
Ich installiere den NPM wie fast alles andere über docker-compose. Dazu muss docker-compose natürlich installiert sein. Um docker/docker-compose ohne Root-Rechte benutzen zu können, muss der verwendete User in der Regel in der Gruppe docker sein.
Man erstellt dann ein Verzeichnis z.B. proxy und legt darin die Datei docker-compose.yaml mit dem Inhalt aus der Dokumentation an.
version: '3.8'
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
- '80:80' # Public HTTP Port
- '443:443' # Public HTTPS Port
- '81:81' # Admin Web Port
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
Die Ports würde ich so lassen, damit der NPM auch lokal verwendet werden kann. Die Verzeichnisse ./data und ./letsencrypt müssen noch angelegt werden. Dort werden die Nginx-Konfigurationen bzw. die SSL-Zertifikate gespeichert. Diese kann man dann auch in andere Docker-Container einbinden, z.B. für einen IMAP-Server.
Dann wird der Container gestartet:
docker-compose up -d
Das -d kann man am Anfang auch weglassen, dann sieht man sofort das Log und kann den Container einfach mit ^C
beenden. Man kann sich das Log aber auch anschauen, wenn der Dienst im Hintergrund läuft:
docker-compose logs -f --tail 10
Zum Anhalten des Containers sollte
docker-compose down
verwendet werden.
Falls man sich in den Container einloggen will, geht das so
docker-compose exec app sh
Alle Docker-Container, die mit docker-compose erstellt wurden, sollten bei einem Neustart automatisch neu gestartet werden. Es kann aber sein, dass die Reihenfolge nicht passt. Das lässt sich nicht ganz einfach vermeiden, wenn die Stacks unabhängig bleiben sollen. Also nach einem Neustart immer mal schauen, ob alles läuft.
Einrichtung
Die Web-GUI des NPM wird mit http://Server1:81 aufgerufen. Dort muss man sich anmelden und dann mit Hosts->Add Proxy Host einen neuen Eintrag erzeugen.
Details:
- Domain Names: ich-mag.gnulinux.ch
- Schema: http
- Forward Hostname / IP: Server2
- Forward Port: 81 (das muss dann im docker-compose.yaml so konfiguriert werden)
- Der Rest ist optional
SSL:
Bei vielen Diensten ist es von Vorteil oder sogar notwendig, SSL/https zu verwenden. Dazu muss bei SSL Certificate einfach Request a new SSL Certificate gewählt werden. Dann muss man natürlich noch seine E-Mail angeben und den Terms of service zustimmen. Der Rest ist wieder optional.
Zugriff Einschränken:
Es gibt dann noch die Access Lists. Damit kann man den Zugriff einschränken. Dies ist vor allem bei Diensten nützlich, die SSL zwingend voraussetzen. Wenn man auf diese über einen Intranet-Namen zugreift, ist das Zertifikat ungültig oder man muss sich mit selbst signierten Zertifikaten herumärgern. Beim Zugriff über den externen Namen, für den NPM ein Zertifikat geholt hat, ist das kein Problem. Und der Zugriff ist dann trotzdem nicht von außen möglich.
Ich habe mir eine Access List private only angelegt:
Details
Name: private only
Access
- allow: meine feste IP Adresse
- allow: 192.168.0.0/16 (mein Intranet)
- allow: 10.0.0.0/8 (mein VPN)
- deny: all
Bei dem Proxy Host kann dann unter Details->Access Lists die neue Liste private only ausgewählt werden.
Um die Adressen, die nicht geblockt werden sollen, zu bestimmen, kann man auch erst mal alles sperren und im Log schauen, was geblockt wurde.
Server-Dienst
So, jetzt kommen wir zum eigentlichen Ziel: der Web-Dienst. Auf Server2 wird ein Verzeichnis angelegt. Da ich einige Dienste laufen habe, habe ich ein Verzeichnis docker und darunter lege ich dann Service1 an. Hier wird für einen simplen Web-Server die Datei docker-compose.yaml mit folgendem Inhalt angelegt:
version: "3"
services:
app:
image: nginx
ports:
- 81:80 # 81 ist der Port, der in NPM angegeben wurde
volumes:
- ./html:/usr/share/nginx/html
Jetzt muss das Verzeichnis ./html und dort die Datei index.html angelegt werden mit beliebigem Inhalt, z.B.:
Gnulinux.ch ist toll.
Und schon kann der Container gestartet werden:
docker-compose up -d
Das war’s schon. Jetzt sollte unter http[s]://ich-mag.gnulinux.ch die neue Seite angezeigt werden.
Hardware
Hier noch ein paar Worte zu meinen Erfahrungen mit der Hardware:
Womit ich angefangen habe, kann ich gar nicht mehr sagen. Irgendwann bin ich dann beim Raspberry Pi 2 Modell B oder so gelandet. Ich hatte insgesamt 4 Stück, die alle sehr oft abgestürzt sind. Ich bin dann auf einen Intel NUC umgestiegen: Intel Celeron J3160 (Quad-Core 4x 1.6-2.24GHz, 4 Threads, 2 MB Cache, 6W TDP). Mit der Nextcloud hat der dann aber auch irgendwann geschwächelt. Habe dann noch einen zweiten NUC angeschafft: Intel Core i5-9300H (4x 2.40-4.10 GHz Quad-Core, 8 Threads, 8 MB Cache, 45 W TDP). Der hat ein paar Jahre ganz gute Dienste geleistet. Die system-load ist meistens unter 1 geblieben. Irgendwann ist sie aber immer öfter darüber gestiegen. Und wenn sie über 4 stieg (quad-core) dann ist nicht mehr viel gegangen. Die Last ist dann oft dauerhaft über 10 geblieben.
Sehr interessant ist aber, dass die Last jetzt wieder meistens unter 1 liegt. Und zwar ziemlich genau seit dem Zeitpunkt, als ich gelesen habe, dass wieder mal ein großes Botnetz hochgenommen wurde. Was da auf jeden Fall helfen sollte, ist fail2ban (siehe unten).
Inzwischen denke ich, dass die Taktfrequenz der CPU nicht so entscheidend ist, wie man erst mal denkt, sondern die TDP (6W bzw. 45W). Wenn die Last steigt, sinkt sofort auch die Frequenz der CPU, wenn diese die Wärme nicht abführen kann, was wieder zu einer höheren Last führt. Ich habe den NUC dann hochkant aufgestellt, damit durch Konvektion die Belüftung besser wird. Das hat schon etwas gebracht. Ich hatte schon überlegt, wieder mal etwas Schnelleres zu kaufen, bis auf einmal alles gut war. Momentan bin ich wieder sehr zufrieden.
Man sieht also, die Performance des Servers hängt nur zum Teil an der Hardware, ein nicht zu unterschätzender Teil hängt an der Verteidigung gegen Angriffe.
Ausblick
Wenn man den ersten Dienst mal am Laufen hat, sind weitere Dienste oft ein Kinderspiel. Dienste, die ohne viel Konfiguration auskommen, sind in einer Minute am Laufen.
Eine Übersicht aller Container bekommt man z.B. mit Portainer.
Quelle: https://nginxproxymanager.com
Netter Artikel, Danke
Hi,
ich bin gerade dabei mein NginxProxyManager durch Nginx und acme.sh in einem Proxmox-Kontainer abzulösen. Es ist oft so, dass NPM nach einem Update nicht mehr wie erwartet funktioniert. Alleine deswegen würde ich nicht das latest-Image sondern immer eine bestimmte Version (z.B. 2.10.4) nehmen. Backup vor dem Update ist hier essentiell !
Traefik macht in einer Docker-Umgebung viel mehr Sinn. Die vielen Vorteile will ich hier gar nicht aufzählen. Ich würde nicht mehr zu Nginx zurück wechseln.
Sehe ich auch so, hab ja auch schon nen Traefik Artikel geschrieben. Du darfst aber gerne noch einen schreiben, wo du alles aufzählst, was du hier in den Kommentaren nicht tun willst :)
Was ist der große Vorteil von Docker und nginx? So im Vergleich zu einem LAMP-Stack? Ständig stolpert man über Sachen, die jemand im Docker laufen lässt.
Innerhalb von Docker ist es egal was "in" der Webanwendung steckt. Du kannst also Problemlos Webanwendungen nebeneinander fahren, die unterschiedliche Stacks haben, also die eine Anwendung in Java, die andere in Go und noch 3 andere mit unterschiedlichen PHP-Versionen und -Konfigurationen. Um das alles brauchst du dich nicht kümmern und nichts von den Abhängigkeiten auf deinem Host installieren oder konfigurieren.
Ja, nur müssen dann (natürlich) auch alle drei PHP-(Container-)Versionen aktualisiert werden (und user namespace sollte für Docker ohnehin Pflicht sein (https://docs.docker.com/engine/security/userns-remap/).
Danke!
> Ich verwende keine unbenutzte Adresse, da es ja möglich ist, dass diese irgendwann doch vergeben wird.
Genau dafür gibt's doch example.com (oder .test (oder inoffiziell auch .lan)).
Ich habe aktuell im letzten Monat mit genau der beschriebenen Konfiguration gekämpft.
Zunächst ist meist das Problem, dass man einen DynDNS braucht und in dem Fall nur eine Domain zu Verfügung hat. Was bei einem Dienst kein Problem ist, aber wenn man viele kleinere Sachen - nextcloud, piwigo, twiki usw. - nutzen möchte, wird es schnell komplex und wie schon einige Kommentatoren hier anmerken, traefik ist dann vermutlich besser.
Ich habe NPM zuerst installiert und muss sagen, es ist in so einem Umfeld kompliziert. Bzw. führt nicht immer zum Erfolg. Ich habe piwigo nicht vernünftig zum laufen bekommen bisher. Daher werde ich mir demnächst traefik anschauen, das scheint eher auf die Problematik der verschiedenen Dienste einzugehen.
Der eigene Server zu Hause kann also schon komplexer sein als manche Beschreibungen über Docker, NPM oder Traefik das meist darstellen.
und mit der Nextcloud docker installation hatte ich schwer zu kämpfen. Sie lief zwar und war auch erreichbar, aber ich konnte nicht mit mehreren Acounts Verzeichnisse teilen. Weil, aus welchen Gründen auch immer, NC die dazugehörigen Javascripte nicht auslieferte und daher die Funktion im Browser nicht aufgerufen werden konnte. Aber in der App auf den Mobilgeräten. Damit war dann das Problem gelöst. Hat mich aber auch ein paar Tage gekostet, wo ich am Desktop saß und versucht habe rauszufinden was das Problem ist.
und aktuell hatte ich im beruflichen Umfeld auch mit einem Dockerproblem zu kämpfen. Das hat mich dann drei Tage gekostet, da Fehlersuche im Container auch erst gelernt sein muss. So einfach wie man das gerne darstellt sind Container dann nicht mehr. Zumal immer mehr Schichten oben drauf, auf den einschlägigen Kanälen beworben werden. Sei es so was Proxmox, Portainer, Kubernetes oder was weiß ich, was es noch alles gibt. Ich bin da schnell als Hobbyserveradmin überfordert und ständig am suchen.