nginx
Bei nginx (ausgesprochen engine-ex) handelt es sich um einen Reverse-Proxy. Ein Reverse-Proxy ist im Vergleich zu einem normalen Proxy serverseitig und kümmert sich darum, eingehende Requests an die passenden Endpunkte weiterzuleiten bzw. bestimmte Seiten nach Validierung des eingehenden Requests zurück zu geben. nginx ist einer der bekanntesten und beliebtesten Reverse Proxies, und das aus gutem Grund, da er viele Funktionen liefert, sehr flott ist und relativ einfach zu konfigurieren ist.
Weiterleitung zu anderem Webserver
Im nachfolgenden Beispiel wird auf dem Port 80 auf die Domains codigo.at, www.codigo.at und wp.codigo.at geprüft. Passt der eingehende Request auf diese Bedingungen, dann wird dieser mit dem Status-Code 301 (= permanente Weiterleitung) auf https:// und die angefragte URL weitergeleitet. „server_tokens off“ definiert, dass im Fehlerfall die Version von nginx sowie das verwendete Betriebssystem nicht in die Fehlerseite inkludiert wird.
server {
listen 80;
server_name codigo.at www.codigo.at wp.codigo.at;
server_tokens off;
location / {
return 301 https://$host$request_uri;
}
}
Das nächste Beispiel ist schon etwas aufwändiger. Hier wird auf Port 443 sowie die Domäne codigo.at geprüft. Zusätzlich sind hier Zertifikate angegeben. Die Requests werden mittels „proxy_pass“ an http://192.168.1.10 weitergeleitet. Zusätzlich wird mittels „proxy_set_header“ der Request an den Proxy um zusätzliche Header erweitert.
server {
listen 443 ssl http2;
server_name codigo.at;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/codigo.at/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/codigo.at/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://192.168.1.10:9000;
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 $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Proto https;
}
}
Authentifizierung
Basic
Um eine Seite mit einer Basic-Authentifizierung zu schützen, müssen zusätzlich die Eigenschaften „auth_basic“ und „auth_basic_user_file“ angegeben werden. „auth_basic“ ist der Titel, der bei der Benutzer/Passwort-Aufforderung im Browser angezeigt wird. Hinter „auth_basic_user_file“ steht die Benutzerverwaltung, die Zugriff auf die Seite hat.
server {
listen 443 ssl;
server_name demo.codigo.at;
...
auth_basic "Gesicherter Bereich von demo.codigo.at";
auth_basic_user_file /etc/nginx/conf.d/nginx.htpasswd;
location {
...
}
}
Um eine solche Datei zu erstellen oder einen neuen Benutzer hinzuzufügen, muss folgender Befehl ausgeführt werden:
sudo htpasswd -c /etc/nginx/conf.d/nginx.htpasswd user1
Anschließend wird man aufgefordert, das Passwort für „user1“ einzugeben.
Authentifizierungsrequest
Anstatt der Standard-Abfrage kann auch ein Authentifizierungsrequest durchgeführt werden. Dieser wird mit „auth_request“ angegeben und muss den Http-Status-Code 200 (= OK) oder 401 (= nicht autorisiert) zurückliefern. Im Beispiel wird im Falle von 401 ein Redirect zu einer anderen Seite gemacht.
Damit ist es möglich, beispielsweise einen Authentifizierungsproxy zwischenzuschalten.
server {
listen 443 ssl http2;
server_name demo.codigo.at;
...
location /auth {
proxy_pass http://192.168.1.10:4180;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Auth-Request-Host https://seq.codigo.at;
proxy_set_header X-Auth-Request-Redirect $request_uri;
}
location / {
auth_request /auth/auth;
error_page 401 = /auth/signin;
proxy_pass http://192.168.1.10:9000;
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 $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Proto https;
}
}
Rückgabe von fixen Inhalten
Um einen Request nicht auf einen anderen Webserver weiterzuleiten, sondern fixe Dateien zurück zu liefern, wird „root“ verwendet.
server {
listen 443 ssl http2;
server_name demo.codigo.at;
location / {
root /var/www/demo;
}
}
Alternativ dazu existiert „alias“. Dies ist sehr ähnlich zu „root“, führt aber zu einem etwas anderen Fall, was tieferliegende Locations betrifft. Dies lässt sich am besten an einem Beispiel erklären.
location /demo/ {
root /var/www/demo/;
}
location /demo/ {
alias /var/www/demo/;
}
Im Fall von „root“, werden die Dateien im Verzeichnis „/var/www/demo/demo/“ erwartet. Im Falle von „alias“ im Verzeichnis „/var/www/demo/“. Sprich bei „root“ wird das Root-Verzeichnis um die Location erweitert, bei „alias“ nicht.
Load-Balancer
nginx kann auch als Load-Balancer verwendet werden, der einen Request an einen Pool von Webservern weiterleitet. Dafür gibt es drei unterstützte Mechanismen:
- Round-Robin ⇒ bei jedem Request wird der nächste Server aus dem Pool verwendet
- Least-Connected ⇒ der Request wird an den Server, der die wenigsten aktiven Verbindungen hat, weitergeleitet
- IP-Hash ⇒ mittels der Client-IP wird ein Hash gebildet und somit Request vom gleichen Client immer an den gleichen Server weitergeleitet, wodurch eine Session-Persistence gegeben ist
http {
upstream demo {
server demo1.codigo.at;
server demo2.codigo.at;
server demo3.codigo.at;
}
server {
listen 80;
location / {
least_conn; # Alternativ ip_hash oder auslassen für Round-Robin
proxy_pass http://demo;
}
}
}
Handelt es sich um eine gRPC-Verbindung, dann muss anstatt „proxy_pass“ „grpc_pass“ verwendet werden. Weitere Informationen und Einstellungen sind in dem Dokument zu finden.
Rewrite-Regeln
Mit „return“ kann ein Http-Status-Code zurückgegeben werden. Dies wird beispielsweise oft bei einem Redirect verwendet. Hierbei wird zusätzlich die Url angegeben, auf die weitergeleitet werden soll.
location / {
return 301 https://www.google.at;
}
Mit Hilfe von „rewrite“ kann die Request-URL umgeschrieben werden. Das nachfolgende Beispiel habe ich von der nginx-Seite kopiert, wo dieses auch noch näher beschrieben ist.
Im Beispiel wird geprüft, ob der Request mit „/download“ beginnt und später den Wert „/media“ oder „/audio“ enthält. Ist dies nicht der Fall, dann wird der Http-Status-Code 403 (= Forbidden) zurückgegeben. Ansonsten wird die URL umgeschrieben. Aus „/download/cdn-west/media/file1“ wird dann beispielsweise „/download/cdn-west/mp3/file1.mp3“. Das „last“ am Ende gibt an, dass, wenn die Regex passt, dass die nachfolgenden „rewrite“ oder „return“ ignoriert werden sollen.
location / {
rewrite ^(/download/.*)/media/(\w+)\.?.*$ $1/mp3/$2.mp3 last;
rewrite ^(/download/.*)/audio/(\w+)\.?.*$ $1/mp3/$2.ra last;
return 403;
}
If
Mit „if“-Direktiven können Bedingungen geprüft werden, die einen bestimmten Http-Status-Code zurückgeben oder einen „rewrite“ ausführen. „if“ sollte nur für diese zwei Zwecke verwendet werden, da ansonsten unerwartete Dinge passieren ;). Beispiele für die Schreibweise von if sind hier dokumentiert.
if ($request_method = POST ) {
return 405;
}
if ($args ~ post=140){
rewrite ^ http://example.com/ permanent;
}
Map
Maps sind ähnlich einem switch/case, wo eine Variable reingeht, und eine andere rauskommt.
map $existing_variable $new_variable {
"true" 1;
"false" 0;
default -1;
}
Variablen
Variablen spielen in nginx eine wichtige Rolle. Auch in den obigen Beispielen wurden einige verwendet (z. B. „$host“ oder „$remote_addr“). Eine komplette Auflistung dieser ist hier zu finden.
Bilder verkleinern
nginx hat auch noch ein paar andere nette Funktionen. Eine von diesen ist das automatisch Verkleinern von Bildern.
location ~ "^/images/(?<width>\d+)/(?<image>.+)" {
alias /var/www/html/images_original/$image;
image_filter resize $width $width;
image_filter_jpeg_quality 95;
image_filter_buffer 20M;
}
Bilder werden immer proportional verkleinert. Ist entweder Breite oder Höhe irrelevant, dann kann stattdessen ein Minus angegeben werden.
image_filter resize $width -;
„image_filter_buffer“ gibt den max. Buffer für das Lesen der Bilder an. Wird dieser überschritten, wird der Http-Status-Code 415 (= Unsupported Media Type) zurückgegeben.
Läuft nginx im Standard-Docker-Image, dann ist das benötigte ngx_http_image_filter_module nicht geladen. Dies muss in /etc/nginx/nginx.conf ergänzt werden (auf oberster Ebene).
load_module /etc/nginx/modules/ngx_http_image_filter_module.so;
Diverses
Nachfolgend noch ein paar ein paar weitere Informationen, ohne zu tief ins Detail zu gehen.
Mit „client_max_body_size“ wird die max. Größe des Body definiert. Ist dieser größer als der hier angegebene, dann wird der Http-Status-Code 413 retourniert. Ein Wert von 0 gibt an, dass es keine Größenbeschränkung gibt. Ohne Suffix bei der Zahl handelt es sich um Bytes, für Kilobyte muss das Suffix „K“ und für Megabyte das Suffix „M“ hinter der Zahl angegeben werden (z. B. 5K oder 7M).
client_max_body_size 0;
Mit „add_header“ kann der Response (nicht der Request!) um Header ergänzt werden. Diese können auch mehrfach definiert werden (unterschiedlich natürlich). Steht „always“ am Ende, dann wird dieser Header immer angefügt, egal welcher Http-Status-Code zurückgegeben wird. Ansonsten wird er nur bei positiven Codes hinzugefügt.
add_header 'X-My-Header' 'Wert des Headers' always;
Natürlich kann auch nginx auch Content cachen. Dies ist relativ einfach umsetzbar.
proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
server {
location / {
proxy_cache my_cache;
proxy_pass http://my_upstream;
}
}
Die Dokumentation für die Konfiguration ist hier zu finden.
Um die Responses zu komprimieren, muss „gzip on“ angegeben sein.
server {
listen 80;
gzip on;
...
}
Als letzter Punkt noch eine Möglichkeit, den Response vor der Rückgabe anzupassen.
location / {
root /var/www/html;
sub_filter_once on;
sub_filter_types text/html;
sub_filter '</head>' '<script src="/myscript.js"></script></head>';
}
Hiermit wird bei der Rückgabe einer HTML-Seite nach „</head>“ gesucht und dies durch „<script src=“/myscript.js“></script></head>“ ersetzt. Somit wird eine JavaScript-Datei in das Ergebnis eingeschleust ;).
Das Ganze funktioniert nicht, wenn ein „Accept-Encoding“-Header „gzip“ vorhanden ist und „proxy_pass“ verwendet wird, da das Ergebnis dann komprimiert ist. Dies kann deaktiviert werden, indem dieser Header auf leer gesetzt wird (einen kleinen Nachteil muss das Ganze ja haben 😆).
proxy_set_header Accept-Encoding "";
Die komplette Dokumentation zu nginx ist hier.