去年做项目的时候,需要同时处理 HTTPS 请求和 WSS 连接,Nginx 作为入口网关是个不错的选择。这篇记录一下配置过程和遇到的问题。
架构设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| Nginx HTTPS + WSS 架构
客户端 │ │ HTTPS (443) / WSS (443) ▼ ┌─────────────┐ │ Nginx │ SSL 终端、反向代理、负载均衡 │ │ • TLS 握手、证书验证 │ :443 │ • 静态资源直接响应 │ │ • WebSocket Upgrade 处理 └──────┬──────┘ │ ┌────┴────┐ │ │ ▼ ▼ ┌─────┐ ┌─────────┐ │HTTP │ │WebSocket│ │后端 │ │服务器 │ │:80 │ │:38080 │ └─────┘ └─────────┘
|
Nginx 处理 SSL,后端服务专心跑业务逻辑。这样做的好处是:
- 支持多后端负载均衡
- 统一入口,便于安全管控
- 证书管理集中化
SSL 证书配置
准备工作
获取 SSL 证书文件(通常由证书颁发机构提供):
| 文件 |
说明 |
xxx.pem 或 xxx.crt |
证书文件(公钥) |
xxx.key |
私钥文件 |
基础 HTTPS 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| server { listen 443 ssl; server_name api.example.com;
ssl_certificate /www/ssl/example.pem; ssl_certificate_key /www/ssl/example.key;
ssl_session_cache shared:SSL:10m; ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on;
server_tokens off;
access_log /www/log/https_access.log;
location /static/ { alias /www/static/; expires 7d; }
location /api/ { proxy_pass http://backend_api; 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; } }
|
| 参数 |
说明 |
推荐值 |
ssl_certificate |
证书文件路径 |
/www/ssl/xxx.pem |
ssl_certificate_key |
私钥文件路径 |
/www/ssl/xxx.key |
ssl_session_cache |
SSL 会话缓存 |
shared:SSL:10m |
ssl_session_timeout |
会话超时时间 |
5m |
ssl_ciphers |
加密套件 |
禁用弱算法 |
ssl_protocols |
TLS 版本 |
TLSv1.2 TLSv1.3 |
WebSocket 代理配置(WSS)
基础 WSS 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| upstream websocket_backend { server 192.168.0.100:38080 weight=1; }
server { listen 443 ssl; server_name ws.example.com;
ssl_certificate /www/ssl/example.pem; ssl_certificate_key /www/ssl/example.key; ssl_session_cache shared:SSL:10m; ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on;
server_tokens off;
location /wss { proxy_pass http://websocket_backend/; proxy_read_timeout 60s;
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; }
location / { proxy_pass http://websocket_backend; 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_redirect http:// $scheme://; } }
|
WebSocket 代理关键参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ┌─────────────────────────────────────────────────────────────┐ │ WebSocket 代理参数说明 │ ├─────────────────────────────────────────────────────────────┤ │ │ proxy_http_version 1.1; │ ─────────────────────── │ HTTP/1.1 支持持久连接,是 WebSocket 的基础 │ │ proxy_set_header Upgrade $http_upgrade; │ ─────────────────────────────────────── │ 将客户端的 Upgrade 请求头传递给后端 │ │ proxy_set_header Connection "upgrade"; │ ────────────────────────────────────── │ 告诉后端建立 WebSocket 连接 │ │ proxy_read_timeout 60s; │ ─────────────────────── │ WebSocket 空闲超时时间,太短时间会断开长连接 │ └─────────────────────────────────────────────────────────────┘
|
| 参数 |
作用 |
注意事项 |
proxy_http_version 1.1 |
使用 HTTP/1.1 |
WebSocket 必需 |
proxy_set_header Upgrade |
协议升级请求 |
WebSocket 必需 |
proxy_set_header Connection |
连接类型 |
WebSocket 必需 |
proxy_read_timeout |
空闲超时 |
根据业务调整,游戏建议 60s+ |
proxy_send_timeout |
发送超时 |
默认 60s |
完整生产配置
综合 HTTPS + WSS + 负载均衡
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| upstream api_backend { server 127.0.0.1:3000 weight=5; server 127.0.0.1:3001 weight=5; server 127.0.0.1:3002 backup; keepalive 32; }
upstream ws_backend { server 192.168.0.100:38080 weight=1; server 192.168.0.101:38080 weight=1; server 192.168.0.102:38080 weight=1; }
server { listen 80; server_name example.com; return 301 https://$server_name$request_uri; }
server { listen 443 ssl http2; server_name example.com;
ssl_certificate /www/ssl/example.pem; ssl_certificate_key /www/ssl/example.key; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=63072000" 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;
server_tokens off;
gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
location /static/ { alias /www/static/; expires 30d; add_header Cache-Control "public, immutable"; }
location /api/ { proxy_pass http://api_backend; 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 Connection ""; }
location /ws { proxy_pass http://ws_backend; proxy_read_timeout 86400s; proxy_send_timeout 86400s;
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-for $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; }
location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } }
|
| 安全头部 |
作用 |
Strict-Transport-Security |
HSTS,强制浏览器使用 HTTPS |
X-Frame-Options |
防止点击劫持 |
X-Content-Type-Options |
防止 MIME 类型嗅探 |
X-XSS-Protection |
启用 XSS 过滤 |
Referrer-Policy |
控制 Referrer 信息 |
负载均衡策略
常用策略对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| upstream backend { server 192.168.0.1:8080; server 192.168.0.2:8080;
server 192.168.0.1:8080 weight=5; server 192.168.0.2:8080 weight=10;
ip_hash; server 192.168.0.1:8080; server 192.168.0.2:8080;
least_conn; server 192.168.0.1:8080; server 192.168.0.2:8080; }
|
| 策略 |
指令 |
适用场景 |
| 轮询 |
默认 |
无状态服务 |
| 加权轮询 |
weight |
后端性能不均 |
| IP 哈希 |
ip_hash |
需要会话保持 |
| 最少连接 |
least_conn |
长连接服务 |
常见问题排查
问题一:WebSocket 连接失败
1 2 3 4 5 6 7 8 9
| nginx -t
tail -f /var/log/nginx/error.log
|
解决方案:
1 2 3 4 5 6 7
| location /ws { proxy_pass http://backend/; }
curl -I http://localhost:38080/health
|
问题二:SSL 证书错误
1 2 3 4 5 6 7 8 9
| ls -la /www/ssl/
openssl x509 -in /www/ssl/example.pem -noout -dates
openssl x509 -noout -modulus -in /www/ssl/example.pem | openssl md5 openssl rsa -noout -modulus -in /www/ssl/example.key | openssl md5
|
问题三:WSS 前端连接报错
1 2 3 4 5 6
| const ws = new WebSocket('wss://example.com/wss');
|
排查步骤:
- 确认 Nginx 配置了
proxy_http_version 1.1 和 Upgrade 头
- 确认防火墙放行了 443 端口
- 使用
openssl s_client 测试连接:
1
| openssl s_client -connect example.com:443 -servername example.com
|
性能优化
SSL 会话复用
1 2 3 4 5 6 7 8
| ssl_session_tickets on;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
|
连接保持
1 2 3 4 5 6 7 8 9 10
| upstream backend { server 127.0.0.1:3000; keepalive 32; }
location /api/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; }
|
写在最后
Nginx HTTPS + WSS 配置的几个要点:
- SSL 证书:正确配置
ssl_certificate 和 ssl_certificate_key,确保证书和私钥匹配
- WebSocket 代理:必须设置
proxy_http_version 1.1、Upgrade 和 Connection 头
- 超时配置:WebSocket 长连接需要设置较长的
proxy_read_timeout
- 负载均衡:通过 upstream 配置多个后端,使用
ip_hash 保持会话一致性
- 安全头部:启用 HSTS、X-Frame-Options 等安全头部提升安全性
- HTTP 跳转:将 80 端口的 HTTP 请求 301 重定向到 HTTPS
配置过程中有任何问题,欢迎留言交流。