NginxProxyManager / nginx-proxy-manager

Docker container for managing Nginx proxy hosts with a simple, powerful interface
https://nginxproxymanager.com
MIT License
23.27k stars 2.7k forks source link

Add support for Server Name Indication (SNI)-based routing for TCP/UDP streams in Nginx Proxy Manager. #4119

Open nonifo opened 3 weeks ago

nonifo commented 3 weeks ago

Description: Currently, Nginx Proxy Manager allows for simple TCP/UDP stream forwarding but lacks advanced routing capabilities based on the requested domain name (SNI). Enabling SNI-based routing for streams would allow users to direct traffic to specific backend servers based on the hostname provided by the client during the TLS handshake. This feature would mirror the SNI-based routing commonly used in HTTP proxies, but extend its benefits to TCP and UDP streams, such as SSH and other non-HTTP protocols that require secure, domain-specific routing.

Use Cases:

Advantages:

Suggested Implementation: Implement SNI-based routing within the "Streams" section of Nginx Proxy Manager to allow administrators to set up stream proxies that route based on the SNI hostname. This would likely require expanding the current stream configuration options and allowing users to specify hostnames directly for each stream backend.

bkeenke commented 3 weeks ago

I support the idea

nonifo commented 3 weeks ago

Here’s an example configuration demonstrating the functionality I'm looking for. This setup uses Nginx’s stream module to forward SPICE traffic based on SNI (Server Name Indication), allowing Nginx to direct requests for specific domains to a designated backend server. Additionally, it includes an HTTP proxy configuration for handling other HTTP-based requests.

# Forward SPICE traffic based on SNI
map $ssl_preread_server_name $upstream {
    example.domain.com 192.168.1.100:61000;  # Destination for SPICE traffic
    default 127.0.0.1:444;                   # Fallback server if no match
}

server {
    listen 61000 ssl;                        # Listen on port 61000 with SSL for SPICE
    proxy_pass $upstream;
    ssl_preread on;                          # Enable SNI to identify the correct backend

    # Use the same SSL certificate and key as the main server
    ssl_certificate /etc/letsencrypt/live/npm-1/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/npm-1/privkey.pem;
}

# HTTP/S proxy for other HTTP-based requests
server {
    listen 3128;
    proxy_pass 192.168.1.100:3128;           # HTTP proxy for port 3128
}

In this example:

The map block forwards requests based on the domain specified in the SNI header. If the domain matches example.domain.com, the request is forwarded to 192.168.1.100 on port 61000. A fallback server at 127.0.0.1:444 handles any unmatched requests. The second server block forwards HTTP-based requests received on port 3128 to 192.168.1.100:3128.

nonifo commented 3 weeks ago

Here’s another example configuration for SNI-based SSH forwarding using Nginx’s stream module. In this setup, Nginx listens on the default SSH port 22 and forwards incoming SSH traffic to different internal SSH servers based on the requested domain name (SNI).

# SSH Forwarding configuration based on SNI
map $ssl_preread_server_name $upstream_ssh {
    ssh1.example.com 192.168.1.101:22;   # Forward SSH traffic for ssh1.example.com
    ssh2.example.com 192.168.1.102:22;   # Forward SSH traffic for ssh2.example.com
    default 192.168.1.100:22;            # Default SSH server if no SNI match
}

server {
    listen 22 ssl;                       # Listen on port 22 with SSL for SNI-based routing
    proxy_pass $upstream_ssh;            # Forward to SSH server based on SNI map
    ssl_preread on;                      # Enable SNI inspection

    # SSL certificate and key (generic for SNI inspection)
    ssl_certificate /etc/letsencrypt/live/placeholder/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/placeholder/privkey.pem;
}
nonifo commented 3 weeks ago

General Nginx stream Configuration Template This template includes SSL-based SNI routing for traffic that requires domain-based routing (e.g., HTTPS, SPICE), as well as simple port-based forwarding for other protocols (e.g., SSH without SSL).

# General-purpose Nginx stream configuration for multi-protocol traffic

# Define upstream servers based on SNI for SSL-based services
map $ssl_preread_server_name $upstream {
    example1.com 192.168.1.101:443;      # HTTPS or other SSL traffic for example1.com
    example2.com 192.168.1.102:61000;    # SPICE or custom SSL service for example2.com
    default 192.168.1.100:444;           # Default server if no SNI match
}

# SSL-based traffic (e.g., HTTPS, SPICE) with SNI-based routing
server {
    listen 443 ssl;                      # Standard HTTPS port
    listen 61000 ssl;                    # Custom SSL port for specific services
    proxy_pass $upstream;                # Route based on SNI
    ssl_preread on;                      # Enable SNI inspection

    # Generic SSL certificate (only used for SNI inspection)
    ssl_certificate /etc/letsencrypt/live/placeholder/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/placeholder/privkey.pem;
}

# Port-based routing for non-SSL protocols (SSH, non-SNI HTTP, etc.)

# SSH Server 1
server {
    listen 2222;                         # External port for SSH server 1
    proxy_pass 192.168.1.101:22;         # Internal SSH server 1
}

# SSH Server 2
server {
    listen 2223;                         # External port for SSH server 2
    proxy_pass 192.168.1.102:22;         # Internal SSH server 2
}

# HTTP server (non-SSL)
server {
    listen 8080;                         # External port for HTTP traffic
    proxy_pass 192.168.1.103:80;         # Internal HTTP server
}

# Custom TCP service
server {
    listen 3000;                         # External port for custom service
    proxy_pass 192.168.1.104:3000;       # Internal custom service
}
synchrone commented 3 weeks ago

as a workaround, you can put anything you want included inside stream {} block into /data/nginx/custom/stream.conf