如何将Stunnel置于Nginx反向代理后隐藏OpenVPN?配置疑问
Absolutely, you can pull this off without messing up your existing blog proxies or breaking Stunnel's encapsulation. The key is to use Nginx's stream module to route traffic based on SNI (Server Name Indication) at the TCP layer—this lets Nginx forward raw SSL traffic to Stunnel without decrypting it, preserving Stunnel's VPN encapsulation. Here's a step-by-step breakdown:
Core Concept
Instead of letting Nginx terminate SSL for all 443 traffic, we’ll use ssl_preread to read the SNI header during the SSL handshake (without decrypting the connection) and route:
- Normal blog subdomains (e.g.,
blog1.example.com) to your existing HTTP/HTTPS proxy setup - Your VPN subdomain (e.g.,
vpn.example.com) directly to Stunnel's listening port
Step 1: Verify Nginx Stream Module Support
First, confirm your Nginx installation has the stream module enabled. Most modern package managers (like Ubuntu’s nginx-full, RHEL’s nginx) include it by default. Check with:
nginx -V 2>&1 | grep -q stream && echo "Stream module enabled" || echo "Stream module missing"
If it’s missing, recompile Nginx with --with-stream or install the appropriate package.
Step 2: Update Nginx Configuration
Stream Block (TCP Layer Routing)
Add this to your main nginx.conf file (or include it from a separate config):
stream { # Map SNI values to backend destinations map $ssl_preread_server_name $backend { vpn.example.com stunnel_backend; # Your VPN subdomain default http_backend; # All other subdomains go to blogs } # Define backend pools upstream stunnel_backend { server 127.0.0.1:9000; # Stunnel's local listening port } upstream http_backend { server 127.0.0.1:8443; # Local port for your blog proxy setup } # Listen on port 443 (TCP) server { listen 443; ssl_preread on; # Critical: Read SNI without decrypting SSL proxy_pass $backend; proxy_timeout 1h; # Longer timeout for VPN long-lived connections } }
HTTP Block (Blog Proxy Setup)
Modify your existing HTTP configuration to listen on local port 8443 instead of 443 (since the stream module now handles 443):
http { # Keep your existing global config (mime types, logs, etc.) server { listen 127.0.0.1:8443 ssl; server_name blog1.example.com blog2.example.com; # Your blog subdomains # Your existing SSL certificate config (use a wildcard/multi-domain cert) ssl_certificate /path/to/fullchain.pem; ssl_certificate_key /path/to/privkey.pem; # Your existing blog proxy rules location / { proxy_pass http://localhost:8000; # Example blog service port proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # Add other proxy headers as needed } } # Repeat server blocks for other blogs, all listening on 127.0.0.1:8443 }
Step 3: Configure Stunnel
Update your stunnel.conf to listen on the local port we specified (9000) and forward traffic to your VPN service (e.g., OpenVPN, Shadowsocks):
client = no # Run in server mode [vpn] accept = 127.0.0.1:9000 # Match Nginx's stunnel_backend port connect = 127.0.0.1:1080 # Your VPN service's local listening port # Optional: If Stunnel handles its own SSL encryption (recommended) cert = /path/to/stunnel-cert.pem key = /path/to/stunnel-key.pem
Step 4: Test & Verify
- Test Blog Services: Run
curl -v https://blog1.example.com—you should get your normal blog response with no changes. - Test VPN Subdomain: Use
openssl s_client -connect vpn.example.com:443—you should see Stunnel's SSL handshake details (if enabled), confirming traffic reaches Stunnel. - Reload Nginx: Apply changes and check for errors:
nginx -s reload tail -f /var/log/nginx/error.log
Alternative If Stream Module Isn't Available
If you can’t use the stream module (e.g., old Nginx version), two workarounds:
- Separate Port: Run Stunnel on a different port (e.g., 4443) and update your VPN client to connect there. Less stealthy but simpler.
- IPTables Forwarding: Use iptables to route traffic for your VPN subdomain to Stunnel, though this is less flexible than Nginx's SNI routing.
Key Notes
- SSL Certificates: Use a wildcard (
*.example.com) or multi-domain certificate to cover all subdomains—this ensures clients trust both blog and VPN connections. - Stealth: SNI routing makes VPN traffic look like normal HTTPS to external observers, boosting stealth.
- Long Connections: The
proxy_timeout 1hin the stream block prevents premature VPN connection drops.
内容的提问来源于stack exchange,提问作者Maxithi

