Nginx HTTPS 与 WebSocket 代理配置实战记录

去年做项目的时候,需要同时处理 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.pemxxx.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 证书路径
ssl_certificate /www/ssl/example.pem;
ssl_certificate_key /www/ssl/example.key;

# SSL 会话缓存
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;
}

# API 代理
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 192.168.0.101:38080 weight=1;
# server 192.168.0.102:38080 weight=1 backup;
}

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;

# WebSocket 代理
location /wss {
proxy_pass http://websocket_backend/;
proxy_read_timeout 60s;

# 关键:WebSocket 支持
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;
}

# HTTP 请求代理
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;
}

# HTTP 重定向到 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}

# HTTPS + WSS 服务器
server {
listen 443 ssl http2;
server_name example.com;

# SSL 配置
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;

# 现代 TLS 配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers off;

# HSTS(强制 HTTPS)
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 压缩
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";
}

# API 代理
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 "";
}

# WebSocket 代理
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 哈希(保持会话)
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 配置语法
nginx -t

# 检查错误日志
tail -f /var/log/nginx/error.log

# 常见错误:
# - "no live upstreams":后端服务未启动
# - "Connection refused":后端端口未监听

解决方案

1
2
3
4
5
6
7
# 确保 proxy_pass 末尾有斜杠(如果使用 location 匹配)
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
// 前端连接 WSS
const ws = new WebSocket('wss://example.com/wss');

// 常见错误:
// - "Error in connection establishment":Nginx 未正确配置 Upgrade
// - "SSL handshake failed":证书配置错误

排查步骤

  1. 确认 Nginx 配置了 proxy_http_version 1.1 和 Upgrade 头
  2. 确认防火墙放行了 443 端口
  3. 使用 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; # 保持 32 个空闲连接
}

location /api/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}

写在最后

Nginx HTTPS + WSS 配置的几个要点:

  1. SSL 证书:正确配置 ssl_certificatessl_certificate_key,确保证书和私钥匹配
  2. WebSocket 代理:必须设置 proxy_http_version 1.1UpgradeConnection
  3. 超时配置:WebSocket 长连接需要设置较长的 proxy_read_timeout
  4. 负载均衡:通过 upstream 配置多个后端,使用 ip_hash 保持会话一致性
  5. 安全头部:启用 HSTS、X-Frame-Options 等安全头部提升安全性
  6. HTTP 跳转:将 80 端口的 HTTP 请求 301 重定向到 HTTPS

配置过程中有任何问题,欢迎留言交流。