Appearance
Nginx HTTPS 与调优
HTTPS 请求到达 Nginx 后,Nginx 负责 TLS 握手、证书返回和加解密,解密后的 HTTP 请求再交给静态文件模块或后端应用:
text
client --HTTPS--> nginx --HTTP/HTTPS--> backend内部后端是否继续走 HTTPS 取决于网络环境和合规要求。普通内网应用常见做法是在 Nginx 终止 TLS,Nginx 到后端走 HTTP(同机或内网);跨机房、跨云或合规有要求时,后端链路也继续 HTTPS。
一、证书文件
Nginx 需要两类文件:证书链(站点证书 + 中间证书)和私钥。
部署:
bash
mkdir -p /etc/nginx/ssl/example.local
chmod 700 /etc/nginx/ssl/example.local # 私钥目录严格控制访问
# 证书文件放进去后
chown root:root /etc/nginx/ssl/example.local/*
chmod 600 /etc/nginx/ssl/example.local/example.local.key # 私钥不能给其他用户读
chmod 644 /etc/nginx/ssl/example.local/fullchain.pem # 证书可以让 nginx 读检查证书信息:
bash
openssl x509 -in /etc/nginx/ssl/example.local/fullchain.pem -noout -subject -issuer -dates私钥泄露比证书泄露严重得多——有了私钥就能冒充这个站点。部署脚本里私钥权限要单独处理,不要和普通静态文件混用同样的权限模板。
二、HTTPS 虚拟主机配置
nginx
server {
listen 443 ssl;
http2 on; # Nginx 1.25.1+ 推荐独立指令
server_name example.local;
ssl_certificate /etc/nginx/ssl/example.local/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/example.local/example.local.key;
ssl_protocols TLSv1.2 TLSv1.3; # 禁用 TLSv1.0/1.1
ssl_session_cache shared:SSL:10m; # 共享的 SSL session 缓存,加速 TLS 握手
ssl_session_timeout 10m;
root /data/www/example;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}老版本常见的 listen 443 ssl http2 写在一起也是合法的;1.25.1 之后分离 http2 on 更清晰。reload 时如果出现 deprecated warning,就把 HTTP/2 拆出来。
HTTP 跳转 HTTPS:
nginx
server {
listen 80;
server_name example.local;
return 301 https://$host$request_uri; # 永久重定向,保留原始 URI
}验证 HTTPS 是否正常:
bash
nginx -t && systemctl reload nginx
curl -I https://example.local/排查证书链问题时,openssl s_client 比浏览器更直观:
bash
openssl s_client -connect example.local:443 -servername example.local </dev/null-servername 带上 SNI(Server Name Indication)——多域名共用同一个 IP 时,没有 SNI 可能拿到默认证书而非目标站点证书。浏览器一般会自动带 SNI,命令行工具不一定。
三、HTTPS 反向代理
Nginx 终止 TLS,解密后转发给后端,并标记原始协议:
nginx
upstream app_backend {
server 127.0.0.1:8080;
}
server {
listen 443 ssl;
http2 on;
server_name app.example.local;
ssl_certificate /etc/nginx/ssl/app.example.local/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/app.example.local/app.example.local.key;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://app_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 https; # 告诉后端原始请求是 HTTPS
}
}X-Forwarded-Proto https 是关键——后端框架如果不知道原始请求是 HTTPS,可能在生成回调地址时用 http://。登录跳转、OAuth 重定向、静态资源链接全都会因为协议不对而异常。
四、worker 参数的估算
nginx
user nginx;
worker_processes auto; # 通常等于 CPU 核数
events {
worker_connections 4096; # 单 worker 最大并发连接数
multi_accept on; # 一次 accept 尽可能多的新连接
}
http {
sendfile on; # 静态文件零拷贝
keepalive_timeout 65; # 长连接保持时间
keepalive_requests 1000; # 单长连接最多处理多少个请求
}最大并发连接数的估算:
text
max_clients ≈ worker_processes × worker_connections但这个值受系统文件句柄限制(ulimit -n)。systemd 管理的 Nginx 需要在 unit 里显式设置:
ini
# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=65535bash
systemctl daemon-reload
systemctl restart nginx # LimitNOFILE 改了必须 restart,reload 不生效连接数打满时,只调大 worker_connections 不一定解决问题——如果后端处理不过来,更多连接只是把排队位置从 Nginx 挪到了后端。
五、限流
按客户端 IP 限制请求速率:
nginx
http {
# 定义限流规则——在共享内存里按 IP 计数,每秒 10 个请求
limit_req_zone $binary_remote_addr zone=per_ip:10m rate=10r/s;
server {
listen 80;
server_name api.example.local;
location /api/ {
limit_req zone=per_ip burst=20 nodelay;
# burst=20: 允许瞬时 20 个突发请求排队
# nodelay: 超出 rate 但未超出 burst 的请求不排队等待,直接处理
proxy_pass http://127.0.0.1:8080;
}
}
}| 参数 | 含义 |
|---|---|
$binary_remote_addr | 按客户端 IP 统计(二进制格式省内存) |
rate=10r/s | 每秒允许 10 个请求 |
burst=20 | 允许短时间 20 个突发,超出 rate 的部分排队或拒绝 |
nodelay | 突发请求不排队,超出 rate 立即处理直到 burst 用尽 |
限流默认返回 503,可以改成更语义化的 429:
nginx
limit_req_status 429;如果 Nginx 前面还有一层负载均衡,$remote_addr 拿到的是负载均衡的 IP,所有客户端看起来是同一个来源。这种情况需要先正确传递和识别真实客户端 IP,再按真实 IP 限流。
六、限速和上传大小
下载限速:
nginx
location /download/ {
root /data/www;
limit_rate_after 10m; # 前 10MB 不限速
limit_rate 1m; # 之后限速 1MB/秒
}上传大小限制——默认 1MB,超过返回 413:
nginx
server {
listen 80;
server_name upload.example.local;
client_max_body_size 100m; # 允许 100MB 以内的上传
location / {
proxy_pass http://127.0.0.1:8080;
}
}Nginx 的 client_max_body_size 只是链路中的一层。后端框架有自己的上传限制、网关也有超时时间、客户端也可能超时。Nginx 放了 100MB,不代表整个链路都能承载 100MB 上传。
七、gzip 压缩
nginx
gzip on;
gzip_comp_level 5; # 压缩级别,1-9,5 是常用的 CPU/压缩比平衡点
gzip_min_length 1024; # 小于 1KB 的不压,压了反而更大
gzip_types text/plain text/css application/json application/javascript application/xml;压缩适合文本类响应(HTML、CSS、JS、JSON、XML),不适合已经压缩过的文件(图片、视频、zip)——对这些文件再压一遍白白消耗 CPU。CPU 紧张的入口机器上 gzip_comp_level 设太高,worker 会更忙。
八、日志——把 TLS 信息打出来
nginx
log_format main_ext '$remote_addr - $host "$request" '
'status=$status bytes=$body_bytes_sent '
'rt=$request_time '
'tls=$ssl_protocol/$ssl_cipher '
'upstream=$upstream_addr '
'ustatus=$upstream_status '
'urt=$upstream_response_time';
access_log /var/log/nginx/access.log main_ext;| 字段 | 含义 |
|---|---|
$ssl_protocol | TLS 版本(TLSv1.2 / TLSv1.3) |
$ssl_cipher | 加密套件 |
$request_time | 整个请求耗时(含 TLS 握手) |
$upstream_response_time | 后端响应耗时 |
日志字段越多,单行越长。高流量入口要考虑日志体积和采集成本——可以只给关键站点启用扩展格式。
九、常见 HTTPS 故障
| 现象 | 常见原因 | 排查入口 |
|---|---|---|
| 证书过期 | 证书到期未续签或未 reload | openssl x509 -dates |
| 证书不匹配 | SNI 问题、默认证书、域名和证书 SAN 不对 | openssl s_client -servername |
| HTTPS 正常但跳转变成 HTTP | 缺少 X-Forwarded-Proto 或后端没信任代理头 | 看后端生成的 Location 头 |
| 413 Request Entity Too Large | 上传超过 client_max_body_size | Nginx access log 的 413、配置 |
| 429/503(触发限流) | 客户端请求速率超过限制 | access log 状态码、limit 配置 |
| 499(客户端断开) | 客户端超时主动断开,通常后端太慢 | 对比 $request_time 和客户端超时 |
| 502/504 | 后端异常或超时 | upstream 日志、后端日志 |
HTTPS 问题的排查分两段:TLS 握手阶段的问题看证书、SNI、TLS 协议版本;握手之后的 4xx/5xx 就按普通 HTTP 请求链路查。