Zum Inhalt

Edge Ingress Gateway: Nginx Proxy Manager (NPM), Wildcard DNS-01 ACME & mTLS-Zugriffslisten

Erstellt: 06.11.2026 Aktualisiert: 06.11.2026

Management Summary: Selbst gehostete Container-Dienste ohne zentrale TLS-Terminierung, HTTP/3-Unterstützung und strikte Zugangskontrollen direkt ins Internet freizugeben, führt zwangsläufig zu Sicherheitsrisiken. Dieser Leitfaden beschreibt das Produktiv-Deployment von Nginx Proxy Manager (NPM). NPM kombiniert die bewährte C-basierte OpenResty/Nginx-Event-Schleife mit einer intuitiven Verwaltungsebene zur Orchestrierung von Let's Encrypt Wildcard-Zertifikaten (DNS-01), WebSocket-/gRPC-Proxying, Real-IP-Wiederherstellung (ngx_http_realip_module) und kryptografischer Mutual TLS (mTLS) Client-Authentifizierung.

  • Zero-Port-80 DNS-01 Wildcards


    Bezieht Wildcard-Zertifikate (*.lan.nejat-hakan.de) über ACME-DNS-01-APIs, ohne jemals Port 80 oder 443 ins öffentliche Internet öffnen zu müssen.

  • Dedizierte Docker Ingress Bridge


    Isoliert Backend-Dienste in einem internen Bridge-Netzwerk (proxy_net), sodass keinerlei Backend-Ports (8080, 3000, 9000) auf der Host-NIC gebunden werden.

  • Security-Header & mTLS


    Erzwingt HSTS Preload, CSP, X-Frame-Options und Client-Zertifikatsvalidierung (ssl_verify_client on) über wiederverwendbare Nginx-Snippets.

  • Strukturierte JSON-Access-Logs


    Erfasst Upstream-Latenzen ($upstream_response_time) und reale Client-IPs (CF-Connecting-IP / X-Forwarded-For) zur Analyse mit CrowdSec und Fail2ban.


1. Architektur: Isoliertes Container-Ingress & Split-Horizon TLS

In einer gehärteten Self-Hosting-Umgebung binden interne Anwendungen niemals Ports auf 0.0.0.0. Stattdessen dient Nginx Proxy Manager als zentrales Ingress-Gateway im Docker-Netzwerk proxy_net und leitet Anfragen über interne Docker-DNS-Namen weiter (http://immich-server:2283, http://vaultwarden:80).

%%{init: {'flowchart': {'wrappingWidth': 600, 'subGraphTitleMargin': {'top': 15, 'bottom': 15}}}}%%
flowchart TB
    subgraph Clients["Externe & LAN Clients"]
        Browser["HTTPS / HTTP3 Client<br/>*.nejat-hakan.de"]
        ACME["Let's Encrypt CA<br/>DNS-01 API Challenge"]
    end

    subgraph IngressGateway["Nginx Proxy Manager (proxy_net)"]
        NginxCore["OpenResty / Nginx Engine<br/>TLS 1.3 + HTTP/2 + HTTP/3"]
        Certbot["Embedded Certbot<br/>DNS-01 Wildcard-Erneuerung"]
        AccessList["Access Lists & mTLS<br/>IP Allowlist / Client-Zertifikat"]
    end

    subgraph InternalBackends["Isolierte Backend-Container (Keine Host-Ports)"]
        Vault["Vaultwarden<br/>vaultwarden:80"]
        Nextcloud["Nextcloud App<br/>nextcloud:80"]
        Jellyfin["Jellyfin Media<br/>jellyfin:8096"]
    end

    Browser -->|"TCP/UDP 443 (SNI)"| NginxCore
    Certbot <-->|"TXT _acme-challenge"| ACME
    NginxCore --> AccessList
    AccessList -->|"WebSocket + Proxy"| Vault
    AccessList -->|"FastCGI / HTTP"| Nextcloud
    AccessList -->|"Streaming Puffer aus"| Jellyfin

2. Produktiv-Stack (NPM + Externes Ingress-Netzwerk)

Erstellen Sie zunächst das isolierte Ingress-Netzwerk:

Externes Docker-Ingress-Netzwerk anlegen
docker network create --driver=bridge --subnet=172.28.0.0/16 proxy_net
/opt/npm/compose.yaml
services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    container_name: nginx-proxy-manager
    restart: unless-stopped
    ports:
      - "80:80/tcp"
      - "443:443/tcp"
      - "443:443/udp"
      - "127.0.0.1:81:81/tcp"
    environment:
      TZ: "Europe/Berlin"
      DISABLE_IPV6: "false"
      PUID: 1000
      PGID: 1000
    volumes:
      - ./data:/data:Z
      - ./letsencrypt:/etc/letsencrypt:Z
    networks:
      - proxy_net

networks:
  proxy_net:
    external: true

Admin-Port 81 niemals auf 0.0.0.0 binden

Binden Sie Port 81 stets ausschließlich an Loopback (127.0.0.1:81:81/tcp). Greifen Sie auf die NPM-Weboberfläche entweder via SSH-Portforwarding (ssh -L 8181:127.0.0.1:81 user@host) zu oder erstellen Sie einen internen Proxy-Host (npm.lan.nejat-hakan.de → http://127.0.0.1:81), der durch eine LAN-Zugriffsliste abgesichert ist.


3. Wiederverwendbare Nginx-Härtungs-Snippets (/data/nginx/custom/)

NPM bindet benutzerdefinierte Konfigurationsdateien aus /data/nginx/custom/ automatisch ein:

  • http_top.conf: Wird am Anfang des http {}-Blocks geladen.
  • server_proxy.conf: Wird in jeden generierten Proxy-Host-server {}-Block injiziert.
/opt/npm/data/nginx/custom/http_top.conf
# Versionsnummern im Server-Header und auf Fehlerseiten ausblenden
server_tokens off;

# TLS 1.3 / 1.2 elliptische Kurven härten
ssl_ecdh_curve X25519:secp384r1;
ssl_session_tickets off;

# Lokales WireGuard-VPN und Docker-Subnetz für X-Forwarded-For vertrauen
set_real_ip_from 172.28.0.0/16;
set_real_ip_from 10.88.0.0/24;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
/opt/npm/data/nginx/custom/server_proxy.conf
# HSTS & moderne Browser-Isolations-Header erzwingen
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), microphone=(), payment=(), usb=()" always;

# Standard-Proxy-Puffer für große OIDC/JWT-Header vergrößern
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
Custom Nginx Configuration Tab (Host-Spezifisch)
# Chunked-Uploads erlauben und Festplatten-Pufferung für Medienstreams deaktivieren
client_max_body_size 16G;
proxy_request_buffering off;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;

4. Mutual TLS (mTLS) Client-Zertifikatsauthentifizierung

Für sensible administrative Oberflächen (Proxmox, Portainer) bietet Mutual TLS (mTLS) maximale Sicherheit: Nginx bricht den TLS-Handshake bereits auf Layer 4/7 ab, bevor auch nur ein einziges HTTP-Byte an das Backend gelangt, falls der Client kein gültiges Zertifikat der internen CA vorlegt.

Interne mTLS Root-CA und Client-PKCS#12-Bundle erzeugen
mkdir -p /opt/npm/data/mtls && cd /opt/npm/data/mtls

# 1. Privaten Root-CA-Schlüssel und Zertifikat generieren (10 Jahre Gültigkeit)
openssl genpkey -algorithm ED25519 -out ca.key
openssl req -x509 -new -key ca.key -sha256 -days 3650 -out ca.crt \
  -subj "/CN=Sovereign Infrastructure mTLS Root CA/O=NejatHakan"

# 2. Client-Schlüssel und Zertifikatsanforderung erzeugen und signieren
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out client.key
openssl req -new -key client.key -out client.csr -subj "/CN=nejat-workstation/O=Engineering"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 825 -sha256

# 3. PKCS#12 (.p12) Bundle für Browser-/Mobilgeräte-Import exportieren
openssl pkcs12 -export -out nejat-client.p12 -inkey client.key -in client.crt -certfile ca.crt

Fügen Sie im Reiter Advanced des gewünschten Proxy-Hosts in NPM folgende Zeilen ein:

NPM Proxy Host Advanced Tab — mTLS Enforcement
ssl_client_certificate /data/mtls/ca.crt;
ssl_verify_client on;
ssl_verify_depth 2;

5. Verifikation & Log-Audit

Konfigurationsintegrität im laufenden Container prüfen und Latenzen auswerten:

Nginx-Konfiguration testen & Logs prüfen
# 1. Generierte Nginx-Dateien im Container syntaktisch prüfen
docker exec nginx-proxy-manager nginx -t

# 2. Worker-Prozesse ohne Verbindungsabbruch neu laden
docker exec nginx-proxy-manager nginx -s reload

# 3. Statuscodes und Antwortzeiten in Echtzeit analysieren
tail -f /opt/npm/data/logs/proxy-host-*_access.log