完整的 n8n Nginx 反向代理配置(SSL、WebSocket、安全头)
用于 n8n 的生产就绪 Nginx 配置,包括通过 Let's Encrypt 的 SSL、WebSocket 支持、安全头、速率限制和 Docker 网络。
Why Most n8n Nginx Configs Are Incomplete
Search for "n8n nginx config" and you'll find dozens of examples. Most of them are broken in subtle ways: WebSocket connections fail silently, webhooks return HTTP URLs instead of HTTPS, large payloads get rejected, or long-running workflows time out.
The problem is that n8n isn't a simple web app. It needs WebSocket connections for the editor's real-time features, long timeout windows for workflow executions, large body limits for webhook payloads with file attachments, and proper header forwarding so it knows its own public URL.
This guide gives you a complete, production-tested Nginx configuration that handles all of these -- plus SSL with automatic renewal, security headers, and rate limiting.
Prerequisites
Before you start, you need:
- A VPS with n8n running (Docker or bare metal)
- A domain name pointed to your VPS IP (e.g.,
n8n.yourdomain.com) - Nginx installed:
sudo apt install nginx - Certbot for Let's Encrypt SSL:
sudo apt install certbot python3-certbot-nginx
The Complete Production Config
Here's the full Nginx server block. We'll break down every section after:
# Rate limiting zone — 10 requests/second per IP
limit_req_zone $binary_remote_addr zone=n8n_ratelimit:10m rate=10r/s;
# WebSocket upgrade map
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name n8n.yourdomain.com;
return 301 https://$server_name$request_uri;
}
# Main HTTPS server
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name n8n.yourdomain.com;
# --- SSL Configuration ---
ssl_certificate /etc/letsencrypt/live/n8n.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n8n.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_stapling on;
ssl_stapling_verify on;
# --- Security Headers ---
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# --- Body Size (for webhook payloads with attachments) ---
client_max_body_size 50m;
# --- Webhook Endpoint (rate limited) ---
location /webhook/ {
limit_req zone=n8n_ratelimit burst=20 nodelay;
proxy_pass http://127.0.0.1:5678;
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_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
location /webhook-test/ {
proxy_pass http://127.0.0.1:5678;
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_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
# --- Main Application (WebSocket + HTTP) ---
location / {
proxy_pass http://127.0.0.1:5678;
# Forward client identity
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;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Timeouts for long-running operations
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 3600s;
# Disable buffering for real-time events
proxy_buffering off;
proxy_cache off;
}
}
Section-by-Section Breakdown
Rate Limiting
limit_req_zone $binary_remote_addr zone=n8n_ratelimit:10m rate=10r/s;
This creates a rate limiting zone that allows 10 requests per second per IP address. The 10m allocates 10 MB of shared memory for tracking IP addresses (enough for ~160,000 unique IPs).
The rate limit is applied only to the /webhook/ location, not the entire application. This protects your webhook endpoints from abuse without interfering with the n8n editor's normal operation.
The burst=20 nodelay in the webhook location allows short bursts of up to 20 requests before rate limiting kicks in, and nodelay processes burst requests immediately rather than queuing them.
WebSocket Upgrade Map
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
This is essential for n8n's real-time editor features. The map directive dynamically sets the Connection header based on whether the request is a WebSocket upgrade or a regular HTTP request.
Without this, you'd need to hardcode Connection "upgrade" on every request -- which breaks regular HTTP responses. The map handles both cases correctly.
If WebSocket connections fail, the n8n editor will load but "Listen for Test Event" in webhook nodes will never receive data. The editor appears to work while test webhooks silently break. This is the most common symptom of missing WebSocket configuration.
SSL Configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:...';
We restrict to TLS 1.2 and 1.3 only (TLS 1.0 and 1.1 are deprecated and insecure). The cipher suite prioritizes ECDHE for forward secrecy and AES-GCM for authenticated encryption.
ssl_stapling enables OCSP stapling, which speeds up SSL handshakes by including the certificate validation response directly from Nginx instead of requiring the client to contact the certificate authority.
Security Headers
Each header serves a specific purpose:
- Strict-Transport-Security (HSTS) -- tells browsers to always use HTTPS for this domain for one year.
includeSubDomainsextends this to all subdomains. - X-Frame-Options -- prevents your n8n instance from being embedded in iframes on other sites (clickjacking protection).
- X-Content-Type-Options -- stops browsers from MIME-type sniffing, forcing them to respect the declared content type.
- X-XSS-Protection -- enables the browser's built-in XSS filter.
- Referrer-Policy -- controls how much referrer information is sent with requests.
Proxy Headers
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
These two headers are why most n8n webhook issues exist. Without X-Forwarded-Proto, n8n doesn't know the connection is HTTPS -- so webhook URLs show http:// instead of https://. Without X-Forwarded-Host, n8n uses its internal hostname (often localhost in Docker) instead of your public domain.
Both of these must be paired with N8N_PROXY_HOPS=1 in your n8n environment, or n8n will ignore them entirely.
Timeouts
proxy_read_timeout 3600s; # 1 hour for the main app
proxy_read_timeout 300s; # 5 minutes for webhooks
The main application location has a 1-hour read timeout. This is necessary because n8n keeps WebSocket connections open for the editor, and long-running workflow executions can take minutes to complete. Without a generous timeout, Nginx closes the connection and n8n reports a failed execution even though the workflow completed.
Webhook endpoints use a shorter 5-minute timeout. Webhook requests should complete quickly; if they're taking more than 5 minutes, you have a workflow design issue.
Buffering
proxy_buffering off;
proxy_cache off;
Disabling buffering ensures that n8n's real-time events (execution progress, test webhook data) are sent to the browser immediately. With buffering enabled, Nginx accumulates response data before forwarding it, which adds latency to the editor's real-time features.
Setting Up SSL with Let's Encrypt
Get a free SSL certificate:
# Get the certificate (Nginx must be running with the HTTP config)
sudo certbot --nginx -d n8n.yourdomain.com
# Test automatic renewal
sudo certbot renew --dry-run
Certbot adds a cron job automatically. Certificates renew every 60-90 days. If renewal fails, your webhooks will break silently (external services will get SSL errors but you won't see anything in n8n's logs).
Set up a monitoring check:
# Add to crontab: alert if certificate expires within 14 days
0 9 * * * certbot certificates 2>&1 | grep -A2 "n8n.yourdomain.com" | grep "VALID" || echo "SSL cert expiring soon" | mail -s "SSL Alert" [email protected]
n8n Environment Variables
Your n8n Docker Compose must include these environment variables for the Nginx proxy to work correctly:
services:
n8n:
image: n8nio/n8n:latest
restart: always
ports:
- "127.0.0.1:5678:5678"
environment:
- N8N_HOST=n8n.yourdomain.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.yourdomain.com/
- N8N_PROXY_HOPS=1
- N8N_SECURE_COOKIE=true
volumes:
- n8n_data:/home/node/.n8n
Key details:
127.0.0.1:5678:5678-- binds n8n to localhost only. Without127.0.0.1:, Docker publishes the port on all interfaces, making n8n directly accessible on port 5678 and bypassing Nginx entirely.WEBHOOK_URL-- the trailing slash matters. Include it.N8N_PROXY_HOPS=1-- tells n8n to trust one layer of proxy headers.N8N_SECURE_COOKIE=true-- marks session cookies as secure (HTTPS only).
The most common mistake: binding to 0.0.0.0:5678 instead of 127.0.0.1:5678. This makes n8n accessible directly on port 5678, bypassing your Nginx security headers, rate limiting, and SSL. Always bind to localhost when using a reverse proxy.
Docker Network Configuration
If n8n and Nginx are both running in Docker, 127.0.0.1 in the proxy_pass won't work because each container has its own localhost. Use Docker's internal networking instead:
services:
n8n:
image: n8nio/n8n:latest
networks:
- n8n-network
# Do NOT publish ports when using Docker networking
environment:
- N8N_HOST=n8n.yourdomain.com
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.yourdomain.com/
- N8N_PROXY_HOPS=1
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/n8n.conf
- /etc/letsencrypt:/etc/letsencrypt:ro
networks:
- n8n-network
networks:
n8n-network:
driver: bridge
In this setup, change proxy_pass in your Nginx config from http://127.0.0.1:5678 to http://n8n:5678 (using the Docker service name).
Testing Your Configuration
After setting everything up, verify each component:
# 1. Test Nginx config syntax
sudo nginx -t
# 2. Reload Nginx
sudo systemctl reload nginx
# 3. Test HTTPS access
curl -I https://n8n.yourdomain.com
# 4. Test WebSocket upgrade
curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGVzdA==" \
https://n8n.yourdomain.com/rest/push
# 5. Test webhook endpoint
curl -X POST https://n8n.yourdomain.com/webhook/test-path \
-H "Content-Type: application/json" \
-d '{"test": true}'
# 6. Test security headers
curl -I https://n8n.yourdomain.com | grep -E "Strict|X-Frame|X-Content|X-XSS|Referrer"
# 7. Verify n8n sees correct protocol
docker exec n8n env | grep -E "WEBHOOK|PROXY|PROTOCOL"
Expected results:
- Step 3: HTTP 200 with HTTPS redirect working
- Step 4: HTTP 101 Switching Protocols (confirms WebSocket works)
- Step 5: HTTP 404 (expected -- no workflow registered at that path, but proves Nginx forwards to n8n)
- Step 6: All five security headers present
Hostinger
Hostinger VPS includes full root access for Nginx + SSL configuration. KVM 1 at $6.49/mo with NVMe storage for fast n8n performance.
* Affiliate link — we may earn a commission at no extra cost to you.
Common Errors and Fixes
| Error | Cause | Fix |
|---|---|---|
| 502 Bad Gateway | n8n not running or wrong proxy_pass address | Check docker ps, verify n8n is up and port matches |
| 504 Gateway Timeout | Workflow takes longer than proxy timeout | Increase proxy_read_timeout |
| WebSocket fails silently | Missing map directive or upgrade headers | Add the map $http_upgrade block and WebSocket headers |
Webhook URLs show http:// | Missing X-Forwarded-Proto or N8N_PROXY_HOPS | Add both the header and the env variable |
Webhook URLs show localhost | Missing X-Forwarded-Host or WEBHOOK_URL | Add the header and set WEBHOOK_URL in n8n env |
| 413 Request Entity Too Large | Payload exceeds client_max_body_size | Increase to 50m or higher |
| n8n accessible on port 5678 directly | Docker port binding on 0.0.0.0 | Change to 127.0.0.1:5678:5678 |
| SSL certificate errors | Expired cert or wrong path | Run certbot renew and check certificate paths |
VPS Recommendations for n8n + Nginx
Running Nginx alongside n8n adds minimal overhead (~20-50 MB RAM). Any VPS suitable for n8n handles the reverse proxy easily:
- Contabo VPS 1 ($4.50/mo): 8 GB RAM, 4 vCPU -- handles n8n + Nginx + PostgreSQL comfortably
- Hostinger KVM 1 ($6.49/mo): 4 GB RAM, NVMe storage, easy management panel
- DigitalOcean ($6-12/mo): One-click Nginx marketplace image, built-in monitoring
- Vultr ($5-12/mo): 32 locations for optimal latency to your users
DigitalOcean
DigitalOcean's one-click Nginx + Let's Encrypt Droplet gets you a reverse proxy ready in 60 seconds. Pair with n8n for a complete automation stack.
* Affiliate link — we may earn a commission at no extra cost to you.
Conclusion
A properly configured Nginx reverse proxy is essential for any production n8n deployment. It provides SSL termination, WebSocket support, security headers, rate limiting, and payload handling that n8n can't do on its own.
Copy the complete config from this guide, replace n8n.yourdomain.com with your domain, run Certbot for SSL, and set the correct n8n environment variables. Test all five components (HTTPS, WebSocket, webhooks, headers, and port binding) and you'll have a bulletproof setup that handles everything production throws at it.
准备好开始自动化了吗?立即获取VPS。
立即开始使用 Hostinger VPS 主机。特惠价格可用。
* 联盟链接 — 我们可能会获得佣金,不会增加您的费用