Dieser Blog beschäftigt sich ja mit etlichen Themen rund um selbst gehostete (Web-)Anwendungen. Die eigene Cloud mit Nextcloud ist hierbei wohl das größte Themengebiet, aber es wurden auch andere Lösungen gezeigt, wie z.B. der eigene Firefox Sync Server.
Spätestens, wenn mehrere Webanwendungen auf einem (Home-)Server gehostet werden sollen, braucht man ein Konzept, um diese Anwendungen voneinander zu trennen. Über lange Zeit habe ich in diesem Blog immer die Variante bevorzugt, mehrere Webanwendungen einfach in verschiedenen Unterverzeichnissen des Webservers laufen zu lassen, wie bei diesem älteren Artikel über Nextcloud. In Zukunft werde ich diese Trennung eher über unterschiedliche (Sub)-Domains realisieren, siehe dazu der aktuelle Artikel zu Nextcloud. Darüber hinaus besteht noch eine dritte Möglichkeit, die Trennung durch die Verwendung unterschiedlicher Ports umzusetzen.
In diesem Artikel möchte ich daher nun genauer auf diese Varianten eingehen, Vor- als auch Nachteile beschreiben und die möglichen Webserver-Konfigurationen skizzieren. Die Ausführen sollen dabei eher allgemein gehalten und nicht an einer speziellen Webanwendung festgemacht werden.
Auch wenn in diesem Artikel nginx als Webserver verwendet wird, so sind die Konzepte auch auch andere Webserver (z.B. Apache) übertragbar.
Inhalt
Basics: Domains, Webserver und virtuelle Hosts
Zunächst soll kurz skizziert werden, was zum Hosten einer Webanwendung benötigt wird.
Domain
Erst einmal wird eine Domain benötigt, über die die Webanwendung später erreichbar sein soll. Im Heim-Bereich kommt hier oftmals eine DynDNS-Domain zum Einsatz, da ein privater Internet-Anschluss keine feste IP-Adresse hat. Einfach ausgedrückt mappt ein DynDNS-Dienst immer die Domain auf die aktuelle IP-Adresse.
Es gibt eine große Auswahl an DynDNS-Diensten. Aus Gründen des Datenschutzes sollte man dabei einen Anbieter wählen, dessen Server-Standort in Deutschland liegt. Einige empfohlene Anbieter sind z.B.:
Im Business-Bereich werden meist feste IP-Adressen verwendet, daher ist hier kein DynDNS-Dienst notwendig. Hier reicht es dann aus, einen A-Record (IPv4) bzw. einen AAAA-Record (IPv6) für die Domain zu setzen. Dies wird meist in der DNS-Verwaltung des Domain-Providers konfiguriert. Da sind das Vorgehen von Anbieter zu Anbieter unterscheidet, kann an dieser Stelle leider keine allgemeingültige Anleitung erfolgen.
Router-/Netzwerk-Konfiguration
Damit die später zu erstellende Webanwendung dann später auch im Internet erreichbar ist, müssen im Router noch Port-Forwardings definiert werden. Wichtig sind dabei die Ports 80 (HTTP) und 443 (HTTPS). Anfragen, wie über diese Ports eingehen, müssen auf den Server weitergeleitet werden, auf dem der Webserver (nginx) läuft.
Auch hier unterscheidet sich das Vorgehen je nach Router-Hersteller. Wie dies z.B. bei einer FritzBox eingerichtet werden kann, wird auf den AVM-Hilfeseiten erklärt.
Auf dem folgenden Screenshot sieht man die Einrichtung eines Port-Forwardings für Port 80. Analog ist dann mit dem Port 443 zu verfahren.
Bei Bedarf können hier auch andere Ports freigeschaltet werden. Dies muss dann später bei der Konfiguration der virtuellen Hosts beachtet werden.
Webserver
Als Webserver kommt im Rahmen des Artikels nginx zum Einsatz. Damit dieser eine Website ausliefern kann, wird mindestens ein virtueller Host (vHost) benötigt. Bei nginx wird ein vHost durch einen Server-Block definiert.
Im folgenden Beispiel werden zwei sehr einfache virtuelle Hosts definiert – einer für HTTP (Port 80) und einer für Port 443 (HTTPS). Beide bedienen die gleiche Domain und es wird einfach nur statischer Content (z.B. HTML) ausgeliefert, welcher im Verzeichnis /var/www/meinedomain.de liegt:
server { # Virtual host 1: # - Listens on port 80 (HTTP) - IPv4 and IPv6. # - Is capable of serving requests for domain "meinedomain.de" and IP address 192.168.178.60 (local IP of server). # - Forwards all requests to the second vHost (HTTPS).listen 80 default_server; listen [::]:80 default_server;server_name meinedomain.de 192.168.178.60; root /var/www/meinedomain.de;location / {# Enforce HTTPSreturn 301 https://$server_name$request_uri; }}server { # Virtual host 2: # - Listens on port 443 (HTTPS) - IPv4 and IPv6. # - Is capable of serving requests for domain "meinedomain.de" and IP address 192.168.178.60 (local IP of server).listen 443 ssl http2;listen [::]:443 ssl http2;server_name meinedomain.de 192.168.178.60; # Certificates used ssl_certificate /etc/letsencrypt/meinedomain1.de/fullchain.pem; ssl_certificate_key /etc/letsencrypt/meinedomain1.de/key.pem; ssl_trusted_certificate /etc/letsencrypt/meinedomain1.de/ca.pem; # Include headers and SSL specific stuff. include /etc/nginx/snippets/ssl.conf; include /etc/nginx/snippets/headers.conf; root /var/www/meinedomain.de;}
Hinweis: Die für einen HTTPS-vHost notwendige SSL-Konfiguration wird hier nicht genauer behandelt (bis auf die Einbindung der TLS-Zertifikate), da dies den Rahmen des Artikels sprengen würde. Daher wird diese Konfiguration den vHosts hier nur beispielhaft per „include“ hinzugefügt. Mehr Informationen zu Zertifikaten und der SSL-Paramter kann folgenden Artikeln entnommen werden:
- Let’s Encrypt Zertifikate mit acme.sh und nginx
- RSA und ECDSA-Zertifikate mit nginx (Hybrid-Lösung)
- TLSv1.3 unter Ubuntu Server 18.04 LTS mit nginx
Die virtuellen Hosts werden normalerweise im Verzeichnis /etc/nginx/conf.d gespeichert (mit der Dateiendung .conf), so dass nginx diese beim Starten automatisch anzieht. Zur besseren Übersichtlichkeit wird meist eine Datei pro vHosts angelegt. Wenn – wie in diesem Beispiel – der virtuelle Host für HTTP lediglich für eine generelle Weiterleitung auf HTTPS gedacht ist, dann können beide vHosts auch in einer einzelnen Datei aufgeführt werden (z.B. unter /etc/nginx/conf.d/meinedomain.de.conf). Der Name dieser Datei ist prinzipiell egal, wichtig ist nur, dass die Dateiendung .conf lautet, sonst wird diese von nginx nicht geladen.
Die oben aufgeführte Konfiguration der virtuellen Hosts wäre eine Standard-Definition, wenn eben nur eine Webanwendung über eine Domain mit den Standard-Ports (80/443) betrieben werden soll.
Webanwendung
Die Webanwendung wird durch den Webserver gehostet (z.B. Nextcloud, Firefox Sync, etc.). Da sich dieser Artikel mit den verschiedenen Möglichkeiten des Hostings beschäftigt und keine zusätzliche Komplexität durch die Webanwendung selbst hinzugefügt werden soll, wird hier nur von einer einfachen statischen HTML-Website ausgegangen.
Mehrere Webanwendungen parallel hosten
Was passiert nun aber, wenn mehrere Webanwendungen parallel auf den gleichen Server gehostet werden sollen? Hier muss der Webserver auf Grund eines Requests eine Unterscheidung vornehmen können, welche Webanwendung ausgeliefert werden soll. Dazu müssen sich weitere virtuelle Hosts in mindestens einem Aspekt unterscheiden:
- Unterschiedliche Ports.
- Unterschiedliche Domains.
- Unterschiedlicher Pfade auf dem Webserver.
Daraus resultieren für das Vorhaben nun drei Möglichkeiten.
Ein virtueller Host pro Webanwendung (unterschiedliche Ports)
Die einfachste Möglichkeit besteht nun darin, dass man für jede Webanwendung einfach andere Ports verwendet.
Ein Beispiel mit zwei virtuellen Hosts für zwei Webanwendungen könnte dann wie folgt aussehen (der Einfachheit halber werden hier die vHosts für HTTP weggelassen und nur die vHosts für HTTPS gezeigt):
server { # Virtual host 1/web application 1: # - Listens on port 443 (HTTPS) - IPv4 and IPv6. # - Is capable of serving requests for domain "meinedomain.de" and IP address 192.168.178.60 (local IP of server).listen 443 ssl http2;listen [::]:443 ssl http2;server_name meinedomain.de 192.168.178.60; ssl_certificate /etc/letsencrypt/meinedomain1.de/fullchain.pem; ssl_certificate_key /etc/letsencrypt/meinedomain1.de/key.pem; ssl_trusted_certificate /etc/letsencrypt/meinedomain1.de/ca.pem; # Include headers and SSL specific stuff. include /etc/nginx/snippets/ssl.conf; include /etc/nginx/snippets/headers.conf; root /var/www/webapplication1;}server { # Virtual host 2/web application 2: # - Listens on port 444 (HTTPS) - IPv4 and IPv6. # - Is capable of serving requests for domain "meinedomain.de:444" and IP address 192.168.178.60:444 (local IP of server).listen 444 ssl http2;listen [::]:444 ssl http2;server_name meinedomain.de 192.168.178.60; ssl_certificate /etc/letsencrypt/meinedomain1.de/fullchain.pem; ssl_certificate_key /etc/letsencrypt/meinedomain1.de/key.pem; ssl_trusted_certificate /etc/letsencrypt/meinedomain1.de/ca.pem; # Include headers and SSL specific stuff. include /etc/nginx/snippets/ssl.conf; include /etc/nginx/snippets/headers.conf; root /var/www/webapplication2;}
Beide vHosts „hören“ auf den Namen meinedomain.de, unterscheiden sich allerdings im verwendeten Port. Die relevanten Zeilen sind im obigen Beispiel markiert. Für Anwendung 1 ist dies der Standard-Port 443, für Anwendung 2 der Port 444 (dieser Port muss dann auch „offen“ sein, d.h. man benötigt ein weiteres Port-Forwarding).
Da sich die Domains hier nicht unterscheiden, kann aber das gleiche TLS-Zertikat zum Einsatz kommen.
Folglich landet man bei Webanwendung 1, wenn man im Browser nun https://meindomain.de aufruft. Die Angabe des Ports 443 ist hier nicht notwendig, da dies der Standard-Port für HTTPS ist und der Browser diesen automatisch nutzt.
Um Webanwendung 2 aufzurufen, muss der alternative Port 444 im Browser spezifiziert werden: https://meinedomain.de:444
Vorteile
- Einfache Trennung der einzelnen Webanwendungen mittels unterschiedlicher Ports.
- Keine zweite Domain benötigt: Beide Webanwendungen laufen über die gleiche Domain.
- Es wird nur ein TLS-Zertifikat benötigt.
- Anleitungen/Tutorials zu bestimmten Webanwendungen unter nginx können meistens einfach übernommen werden – hier muss dann lediglich der Port angepasst werden.
Nachteile
- Wenig komfortabel: Beim Aufruf der zweiten Webanwendung muss in sämtlichen Clients (z.B. Browser, Apps, etc.) immer der abweichende Port (444) mit angegeben werden. Falls hier z.B. eine App keinen alternativen Port zulässt, kann damit auch nicht auf die zweite Webanwendung zugegriffen werden.
- Weiteres Port-Forwarding benötigt: Für die zusätzlichen Port müssen weitere Port-Forwardings eingerichtet werden. Das widerspricht dem Konzept, dass man möglichst wenige Port-Forwardings konfigurieren sollte.
Ein virtueller Host pro Webanwendung (unterschiedliche Domains)
Wenn man nun keine vom Standard abweichenden Ports verwenden möchte, muss man ein anderes Kriterium nutzen, um eine saubere Trennung zu realisieren. Der zweite Lösungsansatz besteht nun darin, unterschiedliche Domains zu verwenden.
In unserem Beispiel könnte dies dann wie folgt aussehen. Es werden wieder nur die vHosts für HTTPS gezeigt:
server { # Virtual host 1/web application 1: # - Listens on port 443 (HTTPS) - IPv4 and IPv6. # - Is capable of serving requests for domain "meinedomain1.de" and IP address 192.168.178.60 (local IP of server).listen 443 ssl http2;listen [::]:443 ssl http2;server_name meinedomain1.de 192.168.178.60; # IMPORTANT: You need a specific TLS certificate for meinedomain1.de ssl_certificate /etc/letsencrypt/meinedomain1.de/fullchain.pem; ssl_certificate_key /etc/letsencrypt/meinedomain1.de/key.pem; ssl_trusted_certificate /etc/letsencrypt/meinedomain1.de/ca.pem; # Include headers and SSL specific stuff. include /etc/nginx/snippets/ssl.conf; include /etc/nginx/snippets/headers.conf; root /var/www/webapplication1;}server { # Virtual host 2/web application 2: # - Listens on port 443 (HTTPS) - IPv4 and IPv6. # - Is capable of serving requests for domain "meinedomain2.de".listen 443 ssl http2;listen [::]:443 ssl http2;server_name meinedomain2.de; # IMPORTANT: You need a specific TLS certificate for meinedomain2.de ssl_certificate /etc/letsencrypt/meinedomain2.de/fullchain.pem; ssl_certificate_key /etc/letsencrypt/meinedomain2.de/key.pem; ssl_trusted_certificate /etc/letsencrypt/meinedomain2.de/ca.pem; # Include headers and SSL specific stuff. include /etc/nginx/snippets/ssl.conf; include /etc/nginx/snippets/headers.conf; root /var/www/webapplication2;}
Für beide Webanwendungen wird nun der gleiche Port (443) verwendet, jedoch unterschiedliche Domains: meinedomain1.de für Anwendung 1, meinedomain2.de für Anwendung 2. In den virtuellen Hosts wird dies über die Anweisung server_name angegeben (oben wieder markiert).
Eine Nebenwirkung hat dieses Konzept allerdings: Nur Webanwendung 1 ist nun noch über die lokale IP-Adresse des Servers erreichbar. Dies ist notwendig, da ja ansonsten wieder beide vHosts auf die gleiche IP-Adresse und den gleichen Port konfiguriert wären.
Im Browser kann man dann https://meinedomain1.de für Anwendung 1, https://meinedomain2.de für Anwendung 2 aufrufen.
Die Besonderheit dieser Lösung liegt nun in der Domain-Konfiguration: Beide Domains müssen auf die (Internet-)IP des Webservers auflösen. Bei statischen IP-Adressen kann dies einfach in der Verwaltung des Domain-Providers angegeben werden. Meistens gibt es hier einen Punkt zur DNS-Verwaltung von Domains. Hier müssen die sog. A-Records (für IPv4) und AAAA-Records (für IPv6) auf die entsprechenden IPs des Servers gesetzt werden.
Für diese Lösung werden übrigens nicht zwingend mehrere Second-Level-Domains (wie z.B. meinedomain1.de und meinedomain2.de) benötigt. Hier reicht auch eine Second-Level-Domain (meinedomain.de), zu der dann mehrere Sub-Domains erstellt werden (bielspielsweise app1.meinedomain.de und app2.meinedomain.de).
Im Heimbereich kommt darüber hinaus meist DnyDNS zum Einsatz. Hier wird die Konfiguration etwas umständlicher, da meistens nur eine einzige DynDNS-Domain im Router hinterlegt werden kann (z.B. bei einer FritzBox). Damit die zweite Domain nun ebenfalls „daheim am Router ankommt“, kann für die zweite Domain ein sog. CNAME-Record gesetzt werden (dies geschieht ebenfalls in der Domain-Verwaltung des Providers). Da sich das Vorgehen von Provider zu Provider unterscheidet, kann hierfür leider keine genaue Anleitung erfolgen. Der folgende Screenshot zeigt beispielhaft die Einrichtung eines CNAME-Records für eine Subdomain beim Provider All-Inkl (Affiliate-Link):
Vorteile
- Einfache Trennung der Webanwendungen auf Grund unterschiedlicher Domains.
- Keine zusätzlichen Port-Forwardings benötigt.
- Anleitungen/Tutorials zu bestimmten Webanwendungen unter nginx können meistens einfach 1:1 übernommen werden.
Nachteile
- Ggf. etwas kompliziertere Domain-Konfiguration erforderlich (v.a. bei der Verwendung von DynDNS).
- Es werden mehrere TLS-Zertifikate benötigt (eines pro Domain).
- Nur eine Webanwendung kann so konfiguriert werden, dass diese auf die lokale IP des Servers reagiert.
Webanwendungen in Unterverzeichnissen (mit Gateway-Host)
Wenn beide zuvor genannten Lösungen nicht umzusetzen sind, beispielsweise wenn nicht mehrere Domains zur Verfügung stehen und man keine zusätzlichen Ports freigeben möchte, kann man mehrere Webanwendungen auch in Unterverzeichnisses des Webservers hosten.
In diesem Fall wird die Webserver-Konfiguration etwas komplizierter, da man einen vorgeschalteten virtuellen Host („Gateway-Host“) benötigt, der alle Requests auf die Domain abfängt und diese anschließend an Hand des Verzeichnisses an weitere vHosts weiterleitet.
Eine solche Konfiguration könnte dabei wie folgt aussehen:
server { # Gateway host handling all the requests. # - Listens on port 443 (HTTPS) - IPv4 and IPv6. # - Is capable of serving requests for domain "meinedomain.de" and IP address 192.168.178.60 (local IP of server).# - Handles all SSL related stuff.listen 443 ssl http2;listen [::]:443 ssl http2;server_name meinedomain.de 192.168.178.60; ssl_certificate /etc/letsencrypt/meinedomain1.de/fullchain.pem; ssl_certificate_key /etc/letsencrypt/meinedomain1.de/key.pem; ssl_trusted_certificate /etc/letsencrypt/meinedomain1.de/ca.pem; # Include headers and SSL specific stuff. include /etc/nginx/snippets/ssl.conf; include /etc/nginx/snippets/headers.conf; root /var/www/# Disable access to the web root.location = / {deny all;}# Subdirectory for app 1 (https://meinedomain.de/app1)location ^~ /app1 {proxy_set_header Host $host;proxy_set_header X-Forwarded-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 https;proxy_pass http://127.0.0.1:81;proxy_redirect off;}# Subdirectory for app 2 (https://meinedomain.de/app2)location ^~ /app2 {proxy_set_header Host $host;proxy_set_header X-Forwarded-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 https;proxy_pass http://127.0.0.1:82;proxy_redirect off;}}server { # Virtual host for web application 1:# - Only local (127.0.0.1), cannot be addressed directly (without gateway host). # - Listens on port 81 (HTTP) - IPv4 and IPv6. # - Is capable of serving requests for local IP address 127.0.0.1.listen 81;server_name 127.0.0.1;location ^~ /app1 {root /var/www/webapplication1;}}server { # Virtual host for web application 2:# - Only local (127.0.0.1), cannot be addressed directly (without gateway host). # - Listens on port 82 (HTTP - different port as for vHost serving web application 1) - IPv4 and IPv6. # - Is capable of serving requests for local IP address 127.0.0.1.listen 82;server_name 127.0.0.1;location ^~ /app2 {root /var/www/webapplication2;}}
Der Gateway-Host nimmt hier alle Requests auf die Domain (oder lokale IP-Adresse des Servers) auf dem Standard-Port 443 (HTTPS) entgegen und ist für das SSL-Handling zuständig. Die Unterscheidung, welche Webanwendung nun gemeint ist, wird durch das Unterverzeichnis definiert (also https://meinedomain.de/app1 bzw. https://meinedomain.de/app2). Entsprechende Requests werden dann an die vHosts der jeweiligen Anwendung weitergeleitet (proxy_pass).
Die einzelnen Webanwendungen werden durch jeweils einen vitruellen Host abgebildet. Diese Hosts laufen rein lokal (127.0.0.1) auf unterschiedlichen Ports. Daher können diese vHosts nicht „von außen“ angesprochen werden, es muss immer erst der Gateway-Host passiert werden.
Vorteile
- Nur eine Domain notwendig.
- Es müssen keine zusätzlichen Port-Forwardings konfiguriert werden.
- Es wird lediglich ein TLS-Zertifikat für den Gatway-Host benötigt.
Nachteile
- Kompliziertere Konfiguration, da immer ein Routing vom Gateway-Host an die Anwendungs-vHosts implementiert werden muss.
- Anleitungen/Tutorials zu bestimmten Webanwendungen unter nginx können nicht einfach übernommen werden. Hier muss zunächst immer eine Lösung implementiert werden, die das Konzept des Gateway-Hosts berücksichtigt.
Empfehlung: Welche Lösung ist die beste?
Eine eindeutige Empfehlung kann an dieser Stelle nicht erfolgen, da dies auch immer von den individuellen Gegebenheiten/Bedürfnissen abhängt.
Dennoch ist meine bevorzugte Lösung die Trennung der einzelnen Webanwendungen durch unterschiedliche (Sub-)Domains. Auf diese Weise laufen alle Anwendungen auf den Standard-Ports 80 (HTTP) und 443 (HTTPS) und man spart sich einiges an Komplexität, die man mit einem Gateway-Host beachten müsste. Dadurch wird die gesamte Webserver-Konfiguration um einiges einfacher.
Überführung der Lösungen/Mischbetrieb
Abschließend noch der Hinweis, dass eine einmal umgesetzte Lösung nicht in Stein gemeißelt ist. Es ist jederzeit möglich, eineLösung in eine andere zu überführen. Normalerweise geschieht das dann einzig und allein über die Webserver-Konfiguration (vHosts) – die Konfiguration der Webanwendung muss meistens gar nicht geändert werden.
Die im Artikel gezeigten Beispiele für die einzelnen virtuellen Hosts von nginx können bei der Überführung der Lösungen hilfreich sein. Falls nach einer Änderung der Konfiguration die Webanwendung nicht mehr funktioniert, sollte auf jeden Fall ein Blick in das nginx Error-Log (/var/log/nginx/error.log) helfen. Meistens ist das Problem dann auf eine falsche Verzeichnisstruktur zurück zu führen – entweder die lokale Verzeichnisstruktur, oder aber die Struktur, die in den virtuellen Hosts konfiguriert wurde.
Man muss sich darüber hinaus auch nicht auf eine Lösung festlegen: Es ist genau so möglich, mehrere Lösungen parallel auf einem Server zu betreiben. So wäre es beispielsweise denkbar, dass zwei Webanwendungen unter den Domains meinedomain1.de/app1 und meinedomain1.de/app2 verfügbar sind und eine dritte Anwendung über meinedomain3.de erreichbar ist.
Fazit
Der Artikel hat drei Lösungsansätze aufgezeigt, wie unter nginx mehrere Webanwendungen parallel gehostet werden können und welche Vor- bzw. Nachteile die einzelnen Lösungen bieten. Wie immer gibt es hier nicht die eine richtige Lösung, da dies immer von den jeweiligen Anforderungen abhängt.
Welchen Ansatz bevorzugt ihr? Hinterlasst mir dazu doch einfach einen Kommentar.
Weiterführende Artikel
- Nextcloud auf Ubuntu Server 18.04 LTS mit nginx, MariaDB, PHP, Let’s Encrypt, Redis und Fail2ban
- Let’s Encrypt Zertifikate mit acme.sh und nginx
- RSA und ECDSA-Zertifikate mit nginx (Hybrid-Lösung)
- TLSv1.3 unter Ubuntu Server 18.04 LTS mit nginx
- Eigener Firefox Sync Server mit nginx
- Nextcloud: Online-Office mit ONLYOFFICE (mit eigener Subdomain)
Links
- Dynamisches DNS (Wikipedia)
- A Resource Record (Wikipedia)
- AAAA Resource Record (Wikipedia)
- Virtual Hosting (Wikipedia)
- CNAME Resource Record (Wikipedia)
Ähnliche Artikel:
HTTPS für alle: SSL-Zertifikate mit Let’s Encrypt und nginxLet’s Encrypt Zertifikate mit acme.sh und nginxLet’s Encrypt: Umstieg von Certbot auf acme.sh (nginx)RSA und ECDSA-Zertifikate mit nginx (Hybrid-Lösung)