Fernwartung Download starten

Nginx als Reverse Proxy: Interne Dienste sicher veröffentlichen

LinuxSecurityServerNetzwerk
Nginx als Reverse Proxy: Interne Dienste sicher veröffentlichen

Webapplikationen, APIs und Dashboards laufen intern auf verschiedenen Ports und Servern. Sie direkt ins Internet zu stellen ist ein Sicherheitsrisiko — offene Ports, fehlende TLS-Verschlüsselung und keine zentrale Zugriffskontrolle. Ein Nginx Reverse Proxy löst diese Probleme: Er bündelt alle Dienste hinter einem einzigen Einstiegspunkt, terminiert TLS, setzt Security Headers und limitiert Zugriffe.

Was ist ein Reverse Proxy?

Ein Reverse Proxy nimmt eingehende Client-Anfragen entgegen und leitet sie an interne Backend-Server weiter. Der Client kommuniziert nur mit dem Proxy — die Backend-Server sind nicht direkt erreichbar.

Internet → Nginx (Port 443) → Backend 1 (10.0.20.10:3000)
                             → Backend 2 (10.0.20.11:8080)
                             → Backend 3 (10.0.20.12:9090)

Vorteile

  • TLS-Terminierung: Ein Zertifikat für alle Dienste, zentral verwaltet
  • Zentrale Zugriffskontrolle: Rate Limiting, IP-Filtering, Basic Auth
  • Security Headers: HSTS, CSP, X-Frame-Options an einer Stelle konfigurieren
  • Load Balancing: Traffic auf mehrere Backend-Server verteilen
  • Caching: Statische Inhalte cachen, Backend entlasten
  • Logging: Zentrale Access- und Error-Logs

Nginx installieren und konfigurieren

Installation

# Debian/Ubuntu
apt update && apt install nginx -y

# Nginx starten und aktivieren
systemctl enable --now nginx

# Statusprüfung
systemctl status nginx

Grundstruktur der Konfiguration

Nginx nutzt eine hierarchische Konfiguration:

/etc/nginx/
├── nginx.conf              # Hauptkonfiguration
├── sites-available/        # Verfügbare Server Blocks
│   ├── default
│   ├── grafana.example.com
│   └── nextcloud.example.com
├── sites-enabled/          # Aktive Server Blocks (Symlinks)
│   ├── grafana.example.com -> ../sites-available/grafana.example.com
│   └── nextcloud.example.com -> ../sites-available/nextcloud.example.com
├── snippets/               # Wiederverwendbare Konfigurationsblöcke
│   ├── ssl-params.conf
│   ├── security-headers.conf
│   └── proxy-params.conf
└── conf.d/                 # Zusätzliche Konfigurationen

Globale Nginx-Optimierung

In /etc/nginx/nginx.conf die Grundeinstellungen optimieren:

worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
    multi_accept on;
    use epoll;
}

http {
    # Grundeinstellungen
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    server_tokens off;  # Nginx-Version nicht preisgeben

    # Logging
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    '$upstream_response_time';

    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log warn;

    # Gzip
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript
               text/xml application/xml application/xml+rss text/javascript;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Server Blocks und proxy_pass

Einfacher Reverse Proxy

Beispiel: Grafana (intern auf Port 3000) über grafana.example.com erreichbar machen:

# /etc/nginx/sites-available/grafana.example.com
server {
    listen 80;
    server_name grafana.example.com;

    # HTTP → HTTPS Redirect
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name grafana.example.com;

    ssl_certificate /etc/letsencrypt/live/grafana.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/grafana.example.com/privkey.pem;
    include snippets/ssl-params.conf;
    include snippets/security-headers.conf;

    location / {
        proxy_pass http://10.0.20.10:3000;
        include snippets/proxy-params.conf;
    }
}

Aktivieren:

ln -s /etc/nginx/sites-available/grafana.example.com /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

Proxy-Parameter als Snippet

Erstellen Sie /etc/nginx/snippets/proxy-params.conf für wiederverwendbare Proxy-Einstellungen:

proxy_http_version 1.1;
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-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;

Die Header sind entscheidend:

  • X-Real-IP: Die tatsächliche Client-IP (nicht die Proxy-IP)
  • X-Forwarded-For: Kette aller Proxies im Pfad
  • X-Forwarded-Proto: Ob der Client HTTPS oder HTTP verwendet
  • Host: Der ursprüngliche Hostname der Anfrage

Ohne diese Header sieht das Backend nur die IP des Nginx-Servers und weiß nicht, ob der Client TLS verwendet.

SSL-Terminierung mit Let’s Encrypt

Certbot installieren

apt install certbot python3-certbot-nginx -y

Zertifikat beantragen

# Automatisch mit Nginx-Plugin
certbot --nginx -d grafana.example.com

# Oder manuell (Standalone)
certbot certonly --standalone -d grafana.example.com

# Wildcard-Zertifikat (DNS-Challenge)
certbot certonly --manual --preferred-challenges dns -d '*.example.com'

SSL-Parameter als Snippet

Erstellen Sie /etc/nginx/snippets/ssl-params.conf:

# Moderne TLS-Konfiguration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

# SSL-Session-Cache
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 9.9.9.9 valid=300s;

# DH-Parameter (einmalig generieren: openssl dhparam -out /etc/nginx/dhparam.pem 4096)
ssl_dhparam /etc/nginx/dhparam.pem;

Automatische Erneuerung

# Certbot-Timer prüfen
systemctl status certbot.timer

# Manueller Test der Erneuerung
certbot renew --dry-run

Certbot erneuert Zertifikate automatisch 30 Tage vor Ablauf und lädt Nginx per Post-Hook neu:

# /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh
#!/bin/bash
systemctl reload nginx

WebSocket-Support

Viele moderne Applikationen nutzen WebSockets (Grafana, Proxmox Console, Nextcloud Talk). WebSockets erfordern spezielle Proxy-Konfiguration:

# /etc/nginx/sites-available/proxmox.example.com
server {
    listen 443 ssl http2;
    server_name proxmox.example.com;

    ssl_certificate /etc/letsencrypt/live/proxmox.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/proxmox.example.com/privkey.pem;
    include snippets/ssl-params.conf;

    location / {
        proxy_pass https://10.0.20.5:8006;
        include snippets/proxy-params.conf;

        # WebSocket-Support
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Längere Timeouts für VNC/Console
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

Die zwei entscheidenden Zeilen für WebSockets sind:

  • proxy_set_header Upgrade $http_upgrade; — signalisiert dem Backend das Protokoll-Upgrade
  • proxy_set_header Connection "upgrade"; — hält die Verbindung für WebSocket offen

Rate Limiting

Rate Limiting schützt vor Brute-Force-Angriffen, DDoS und übermäßiger API-Nutzung.

Rate-Limit-Zone definieren

In /etc/nginx/nginx.conf (im http-Block):

# 10 Requests/Sekunde pro IP, 10 MB Speicher für Tracking
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;

# Strengeres Limit für Login-Seiten
limit_req_zone $binary_remote_addr zone=login:10m rate=3r/s;

# API-Limit
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;

Rate Limiting anwenden

server {
    listen 443 ssl http2;
    server_name app.example.com;

    # Allgemeines Rate Limiting
    location / {
        limit_req zone=general burst=20 nodelay;
        proxy_pass http://10.0.20.15:8080;
        include snippets/proxy-params.conf;
    }

    # Strenges Limit für Login
    location /login {
        limit_req zone=login burst=5 nodelay;
        proxy_pass http://10.0.20.15:8080;
        include snippets/proxy-params.conf;
    }

    # API mit eigenem Limit
    location /api/ {
        limit_req zone=api burst=50 nodelay;
        proxy_pass http://10.0.20.15:8080;
        include snippets/proxy-params.conf;
    }
}

Parameter:

  • rate=10r/s: Maximale Anfragen pro Sekunde
  • burst=20: Erlaubt kurzzeitig 20 zusätzliche Anfragen (werden verzögert abgearbeitet)
  • nodelay: Burst-Anfragen sofort verarbeiten statt verzögern

Security Headers

Erstellen Sie /etc/nginx/snippets/security-headers.conf:

# HSTS: Browser soll nur HTTPS verwenden (2 Jahre)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

# Clickjacking-Schutz
add_header X-Frame-Options "SAMEORIGIN" always;

# XSS-Schutz
add_header X-Content-Type-Options "nosniff" always;

# Referrer-Policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Permissions-Policy
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

# Content-Security-Policy (anpassen je nach Applikation)
# add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;

Die Content-Security-Policy ist auskommentiert, da sie für jede Applikation individuell angepasst werden muss. Eine zu restriktive CSP kann die Funktionalität der Webapp beeinträchtigen.

Headers testen

# Header-Check mit curl
curl -I https://grafana.example.com

# Oder online: securityheaders.com

Mehrere Dienste auf einem Server

Ein typisches Setup mit mehreren internen Diensten:

# Grafana
server {
    listen 443 ssl http2;
    server_name grafana.example.com;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include snippets/ssl-params.conf;
    include snippets/security-headers.conf;
    location / {
        proxy_pass http://10.0.20.10:3000;
        include snippets/proxy-params.conf;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

# Nextcloud
server {
    listen 443 ssl http2;
    server_name cloud.example.com;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include snippets/ssl-params.conf;
    include snippets/security-headers.conf;
    client_max_body_size 10G;  # Große Uploads erlauben
    location / {
        proxy_pass http://10.0.20.11:80;
        include snippets/proxy-params.conf;
    }
}

# Proxmox VE
server {
    listen 443 ssl http2;
    server_name pve.example.com;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include snippets/ssl-params.conf;
    location / {
        proxy_pass https://10.0.20.5:8006;
        include snippets/proxy-params.conf;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
    }
}

Mit einem Wildcard-Zertifikat (*.example.com) reicht ein einzelnes Zertifikat für alle Subdomains.

Troubleshooting

Häufige Fehler

SymptomUrsacheLösung
502 Bad GatewayBackend nicht erreichbarBackend-Service prüfen, Firewall-Regeln
504 Gateway TimeoutBackend antwortet zu langsamproxy_read_timeout erhöhen
413 Request Entity Too LargeUpload zu großclient_max_body_size erhöhen
WebSocket-Verbindung bricht abUpgrade-Header fehlenUpgrade und Connection Header setzen
Backend sieht nur Proxy-IPHeader-Forwarding fehltX-Real-IP und X-Forwarded-For setzen
Mixed Content-WarnungBackend generiert HTTP-LinksX-Forwarded-Proto https setzen

Konfiguration testen

# Syntax prüfen
nginx -t

# Detailliertes Debug-Logging aktivieren
error_log /var/log/nginx/error.log debug;

# Upstream-Antworten loggen
log_format upstream '$remote_addr - $upstream_addr - $upstream_status - $upstream_response_time';

Fazit

Nginx als Reverse Proxy ist der Standard für die sichere Veröffentlichung interner Dienste. TLS-Terminierung mit Let’s Encrypt, WebSocket-Support, Rate Limiting und Security Headers lassen sich mit wenigen Konfigurationszeilen umsetzen. Die Snippet-basierte Konfiguration mit wiederverwendbaren Blöcken hält die Konfiguration DRY und wartbar. Wer interne Dienste ins Internet bringt, sollte das nie ohne Reverse Proxy tun.

Mehr zu diesen Themen:

IT-Beratung gewünscht?

Kontaktieren Sie uns für eine unverbindliche Beratung zu Proxmox, OPNsense, TrueNAS und mehr.

Jetzt Kontakt aufnehmen