Skip to content

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=65535
bash
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_protocolTLS 版本(TLSv1.2 / TLSv1.3)
$ssl_cipher加密套件
$request_time整个请求耗时(含 TLS 握手)
$upstream_response_time后端响应耗时

日志字段越多,单行越长。高流量入口要考虑日志体积和采集成本——可以只给关键站点启用扩展格式。

九、常见 HTTPS 故障

现象常见原因排查入口
证书过期证书到期未续签或未 reloadopenssl x509 -dates
证书不匹配SNI 问题、默认证书、域名和证书 SAN 不对openssl s_client -servername
HTTPS 正常但跳转变成 HTTP缺少 X-Forwarded-Proto 或后端没信任代理头看后端生成的 Location 头
413 Request Entity Too Large上传超过 client_max_body_sizeNginx access log 的 413、配置
429/503(触发限流)客户端请求速率超过限制access log 状态码、limit 配置
499(客户端断开)客户端超时主动断开,通常后端太慢对比 $request_time 和客户端超时
502/504后端异常或超时upstream 日志、后端日志

HTTPS 问题的排查分两段:TLS 握手阶段的问题看证书、SNI、TLS 协议版本;握手之后的 4xx/5xx 就按普通 HTTP 请求链路查。