NGINX 生产运维手册
面向生产环境的 Nginx 安装、配置、排障、升级与日常运维指南
适用场景:接入层反向代理、负载均衡、SSL 终结、静态资源、四层代理、平滑升级
目录
- 架构与核心原理
- 信号与进程管理
- 命令行参数
- 配置文件结构
- [location 匹配与路径规则](#location 匹配与路径规则)
- [反向代理与 proxy_pass 路径规则](#反向代理与 proxy_pass 路径规则)
- 正向代理与反向代理
- 负载均衡
- [SSL/TLS 与证书运维](#SSL/TLS 与证书运维)
- [HTTP 跳转 HTTPS 两种场景](#HTTP 跳转 HTTPS 两种场景)
- 常用模块
- 生产环境性能优化
- 日志、切割与排障
- 平滑升级与动态加模块
- [生产值班 SOP](#生产值班 SOP)
- 安全加固清单
- [真实 IP 与多层代理](#真实 IP 与多层代理)
- [systemd 服务管理](#systemd 服务管理)
- [磁盘、inode 与日志容量巡检](#磁盘、inode 与日志容量巡检)
- 配置回滚与版本管理
- 监控告警与指标解读
- [upstream 节点摘除与灰度](#upstream 节点摘除与灰度)
- [WebSocket / SSE 长连接](#WebSocket / SSE 长连接)
- [静态资源、缓存与 map 模块](#静态资源、缓存与 map 模块)
- 日常巡检清单与常见故障
- [高可用:Keepalived + VIP](#高可用:Keepalived + VIP)
- [Kubernetes 与 Ingress 运维](#Kubernetes 与 Ingress 运维)
- [与 WAF / CDN 协作](#与 WAF / CDN 协作)
1. 架构与核心原理
1.1 优点
| 特性 | 说明 |
|---|---|
| 高并发 | 单机可支撑数万并发连接(取决于 worker 数、连接数、业务类型) |
| IO 多路复用 | Linux 下默认 epoll,高效处理大量 idle 连接 |
| 异步非阻塞 | 工作进程不阻塞等待单个连接,事件驱动处理请求 |
| 多进程模型 | 主进程负责监听外部控制信号,通过频道机制将相关信号操作传递给工作进程,多个工作进程间通过共享内存来共享数据和信息,默认配置下,工作进程的数量与主机CPU核数相同,充分利用cpu和进程的亲缘性将工作进程与CPU绑定,从而最大限度发挥CPU的处理能力 |
Master 进程职责:读取配置、绑定端口、创建/管理 Worker、处理信号(reload/upgrade/reopen 等)。
Worker 进程职责:接受连接、处理 HTTP/TCP 请求。Worker 之间不共享连接状态,因此 session 粘性需靠 ip_hash、sticky cookie 或外部存储(Redis 等)。
1.2 Master-Worker 请求处理时序
后端/upstream Worker 进程 Master 进程 客户端 后端/upstream Worker 进程 Master 进程 客户端 #mermaid-svg-9W4lV6Hfj9IqJTIj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9W4lV6Hfj9IqJTIj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9W4lV6Hfj9IqJTIj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9W4lV6Hfj9IqJTIj .error-icon{fill:#552222;}#mermaid-svg-9W4lV6Hfj9IqJTIj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9W4lV6Hfj9IqJTIj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9W4lV6Hfj9IqJTIj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9W4lV6Hfj9IqJTIj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9W4lV6Hfj9IqJTIj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9W4lV6Hfj9IqJTIj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9W4lV6Hfj9IqJTIj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9W4lV6Hfj9IqJTIj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9W4lV6Hfj9IqJTIj .marker.cross{stroke:#333333;}#mermaid-svg-9W4lV6Hfj9IqJTIj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9W4lV6Hfj9IqJTIj p{margin:0;}#mermaid-svg-9W4lV6Hfj9IqJTIj .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9W4lV6Hfj9IqJTIj text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-9W4lV6Hfj9IqJTIj .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-9W4lV6Hfj9IqJTIj .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-9W4lV6Hfj9IqJTIj .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-9W4lV6Hfj9IqJTIj .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-9W4lV6Hfj9IqJTIj #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-9W4lV6Hfj9IqJTIj .sequenceNumber{fill:white;}#mermaid-svg-9W4lV6Hfj9IqJTIj #sequencenumber{fill:#333;}#mermaid-svg-9W4lV6Hfj9IqJTIj #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-9W4lV6Hfj9IqJTIj .messageText{fill:#333;stroke:none;}#mermaid-svg-9W4lV6Hfj9IqJTIj .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9W4lV6Hfj9IqJTIj .labelText,#mermaid-svg-9W4lV6Hfj9IqJTIj .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-9W4lV6Hfj9IqJTIj .loopText,#mermaid-svg-9W4lV6Hfj9IqJTIj .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-9W4lV6Hfj9IqJTIj .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-9W4lV6Hfj9IqJTIj .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-9W4lV6Hfj9IqJTIj .noteText,#mermaid-svg-9W4lV6Hfj9IqJTIj .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-9W4lV6Hfj9IqJTIj .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9W4lV6Hfj9IqJTIj .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9W4lV6Hfj9IqJTIj .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9W4lV6Hfj9IqJTIj .actorPopupMenu{position:absolute;}#mermaid-svg-9W4lV6Hfj9IqJTIj .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-9W4lV6Hfj9IqJTIj .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9W4lV6Hfj9IqJTIj .actor-man circle,#mermaid-svg-9W4lV6Hfj9IqJTIj line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-9W4lV6Hfj9IqJTIj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 启动时读取配置、绑定 80/443 alt 反向代理 静态文件 keepalive 连接可复用 fork 多个 Worker TCP 连接 + HTTP 请求 解析 location / rewrite 转发请求 响应 sendfile 读取磁盘 HTTP 响应
1.3 reload 平滑重载时序
新 Worker 旧 Worker Master 进程 运维人员 新 Worker 旧 Worker Master 进程 运维人员 #mermaid-svg-hT6uZ2w1W0kSwIOG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hT6uZ2w1W0kSwIOG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hT6uZ2w1W0kSwIOG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hT6uZ2w1W0kSwIOG .error-icon{fill:#552222;}#mermaid-svg-hT6uZ2w1W0kSwIOG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hT6uZ2w1W0kSwIOG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hT6uZ2w1W0kSwIOG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hT6uZ2w1W0kSwIOG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hT6uZ2w1W0kSwIOG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hT6uZ2w1W0kSwIOG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hT6uZ2w1W0kSwIOG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hT6uZ2w1W0kSwIOG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hT6uZ2w1W0kSwIOG .marker.cross{stroke:#333333;}#mermaid-svg-hT6uZ2w1W0kSwIOG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hT6uZ2w1W0kSwIOG p{margin:0;}#mermaid-svg-hT6uZ2w1W0kSwIOG .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-hT6uZ2w1W0kSwIOG text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-hT6uZ2w1W0kSwIOG .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-hT6uZ2w1W0kSwIOG .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-hT6uZ2w1W0kSwIOG .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-hT6uZ2w1W0kSwIOG .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-hT6uZ2w1W0kSwIOG #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-hT6uZ2w1W0kSwIOG .sequenceNumber{fill:white;}#mermaid-svg-hT6uZ2w1W0kSwIOG #sequencenumber{fill:#333;}#mermaid-svg-hT6uZ2w1W0kSwIOG #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-hT6uZ2w1W0kSwIOG .messageText{fill:#333;stroke:none;}#mermaid-svg-hT6uZ2w1W0kSwIOG .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-hT6uZ2w1W0kSwIOG .labelText,#mermaid-svg-hT6uZ2w1W0kSwIOG .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-hT6uZ2w1W0kSwIOG .loopText,#mermaid-svg-hT6uZ2w1W0kSwIOG .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-hT6uZ2w1W0kSwIOG .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-hT6uZ2w1W0kSwIOG .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-hT6uZ2w1W0kSwIOG .noteText,#mermaid-svg-hT6uZ2w1W0kSwIOG .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-hT6uZ2w1W0kSwIOG .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-hT6uZ2w1W0kSwIOG .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-hT6uZ2w1W0kSwIOG .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-hT6uZ2w1W0kSwIOG .actorPopupMenu{position:absolute;}#mermaid-svg-hT6uZ2w1W0kSwIOG .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-hT6uZ2w1W0kSwIOG .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-hT6uZ2w1W0kSwIOG .actor-man circle,#mermaid-svg-hT6uZ2w1W0kSwIOG line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-hT6uZ2w1W0kSwIOG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 全程无端口中断,用户无感知 alt 配置有误 配置正确 kill -HUP / nginx -s reload 校验新配置语法 拒绝 reload,旧 Worker 继续服务 启动新 Worker(新配置) 发送 QUIT,停止接受新连接 处理完存量请求后退出
1.4 主配置文件模块分类
| 模块 | 作用 |
|---|---|
| CoreModule | 进程管理、内存管理、配置文件解析、日志 |
| EventsModule | 连接事件处理;按平台选择 epoll/kqueue/eventport 等 |
| HttpCoreModule | HTTP 核心:server/location、proxy、upstream、gzip 等 |
| StreamModule | 四层 TCP/UDP 代理(如 MySQL、Redis、MQTT over TLS) |
2. 信号与进程管理
信号又称软中断信号,可以通过调用系统命令 kill 来发送信号实现进程通信,或使用 nginx -s。
| 信号 | 命令行参数 | 功能 | 生产建议 |
|---|---|---|---|
| TERM 或 INT | stop | 快速关闭 Nginx 服务 | 仅紧急停机;正常下线用 quit |
| QUIT | quit | 安全关闭(处理完当前请求后退出) | 计划停机、滚动下线前使用 |
| HUP | reload | 平滑重启,热加载配置文件 | 改配置标准操作 ;先 nginx -t |
| WINCH | --- | 从容关闭工作进程 | 平滑升级中关闭旧 Worker |
| USR1 | reopen | 重新创建日志文件,主要用于日志切割 | logrotate 切割后必须执行 |
| USR2 | --- | 平滑更新 Nginx 执行文件 | 升级 nginx 版本时使用 |
bash
# 推荐:先检查再 reload
nginx -t && nginx -s reload
# 等价 kill 方式
kill -HUP "$(cat /export/server/nginx/logs/nginx.pid)"
# 日志切割后
kill -USR1 "$(cat /export/server/nginx/logs/nginx.pid)"
# 或
nginx -s reopen
生产注意 :
reload语法失败时旧配置继续生效;但若新配置语法正确、逻辑有误(如 upstream 指向错误 IP),仍会切到新配置------变更前务必在预发/灰度节点验证。
3. 命令行参数
| 参数 | 说明 |
|---|---|
-v |
nginx 版本信息 |
-V |
版本 + 编译参数 + 已安装模块(加模块/升级前必查) |
-t |
进行配置文件语法检查,测试配置文件的有效性 |
-T |
语法检查并输出所有有效配置内容(配置文件误删且 nginx 处于运行状态、没有 reload 加载过时,可使用该命令恢复配置) |
-q |
在测试配置文件的有效性时,不输出非错误信息 |
| `-s stop | quit |
-p |
指定 nginx 的执行目录 |
-c |
指定 nginx.conf 文件的位置 |
-g |
外部指定配置文件中的全局指令 |
bash
nginx -t # 1. 语法检查
nginx -s reload # 2. 平滑加载
nginx -V 2>&1 | tr ' ' '\n' | grep -i module # 查看已编译模块
4. 配置文件结构
典型层级:
nginx.conf
├── events { ... }
├── http {
│ ├── upstream backend { ... }
│ ├── log_format / access_log
│ ├── gzip / limit_req / proxy_* 全局默认值
│ └── include conf.d/*.conf; # 按站点/业务拆分
│ └── server { location { ... } }
└── stream { ... } # 四层代理(可选)
生产建议:
- 主配置放全局默认值;业务
server块放conf.d/,便于 Git 管理与 Code Review - 每个
server命名规范:域名.conf或业务线-环境.conf - 变更走 MR +
nginx -t+ 灰度 reload,避免直接在单机手改
5. location 匹配与路径规则
5.1 前缀与优先级
| 修饰符 | 含义 |
|---|---|
= |
精确匹配,优先级最高 |
^~ |
URL 以某个常规字符串开头;匹配成功后不再走正则 |
~ |
区分大小写的正则匹配 |
~* |
不区分大小写的正则匹配 |
!~ / !~* |
正则否定(很少用) |
/ |
通用匹配,任何请求都会匹配到 |
多个 location 配置时的匹配顺序:
- 精确匹配
= - 最长前缀匹配;若该前缀带
^~则直接选用,不再正则 - 按配置文件出现顺序 依次尝试正则
~/~* - 最后是通用匹配
/
5.2 root 与 alias
nginx
# root:URI = root路径 + 完整请求路径
location /abc/ {
root /home/www/nginx;
index 2.html index.html;
# 请求 /abc/2.html → /home/www/nginx/abc/2.html
}
# alias:URI = alias路径 + (请求路径 - location前缀)
location /abc/ {
alias /home/www/nginx/; # alias 目录结尾建议以 / 结尾
index 2.html index.html;
# 请求 /abc/2.html → /home/www/nginx/2.html
}
常见坑 :
location /abc配alias /path/时,访问/abc可能 301 到/abc/,路径拼接易出错。生产环境推荐统一写成location /abc/+alias /path/abc/或使用root。
5.3 rewrite 地址重写
URL 重写,把传入 Web 的请求重定向到其他 URL。可用在 server 和 location 中。
| 标志 | 行为 | 适用场景 |
|---|---|---|
redirect |
302 临时重定向,浏览器地址栏变为新 URL | 临时跳转 |
permanent |
301 永久重定向,浏览器会缓存 | 域名迁移、HTTP→HTTPS |
last |
重写后重新走一轮 location 匹配(server 级 rewrite 循环) | 适合 server 块;location 内慎用 |
break |
重写后停止当前 location 内后续 rewrite,不再重新匹配 | 适合 location 内单次重写 |
permanent 添加与不添加的区别:
- 加 permanent:浏览器收到 301,地址栏显示新 URL,F12 可见两次请求
- 不加 permanent:服务器内部转换,浏览器地址栏不变(内部 rewrite)
生产建议 :避免在
if中嵌套复杂 rewrite;rewrite 链超过 10 次会报 500。优先用return 301替代简单跳转。
6. 反向代理与 proxy_pass 路径规则
6.1 核心规则
当 proxy_pass 的 URL 带 URI 路径 (通常以 / 结尾)时,location 匹配到的部分会被替换 掉;不带 URI 时,完整请求 URI 原样转发。
| location | proxy_pass | 请求 | 转发到后端 |
|---|---|---|---|
/api/ |
http://127.0.0.1:8080/ |
/api/upload |
http://127.0.0.1:8080/upload |
/api |
http://127.0.0.1:8080/ |
/api/upload |
http://127.0.0.1:8080//upload(双斜杠,需避免) |
/api/ |
http://127.0.0.1:8080 |
/api/upload |
http://127.0.0.1:8080/api/upload |
/api |
http://127.0.0.1:8080 |
/api/upload |
http://127.0.0.1:8080/api/upload |
nginx
# 带 /:去掉 location 匹配前缀
location /pss/ {
proxy_pass http://127.0.0.1:18081/;
# /pss/bill.html → http://127.0.0.1:18081/bill.html
}
# 不带 /:保留完整路径
location /pss/ {
proxy_pass http://127.0.0.1:18081;
# /pss/bill.html → http://127.0.0.1:18081/pss/bill.html
}
6.2 生产推荐 proxy 头与超时
nginx
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
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 "";
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 8 32k;
}
修正说明 :原「正代示例」中
proxy_cache_*等配置实为反向代理场景,已移至第 7 节区分说明。
7. 正向代理与反向代理
7.1 概念对比
| 正向代理 | 反向代理 | |
|---|---|---|
| 隐藏对象 | 隐藏客户端 | 隐藏后端服务器 |
| 部署位置 | 客户端侧 / 内网出口 | 服务端接入层 |
| 典型用途 | 内网出网、翻墙、统一出口审计 | 负载均衡、SSL 终结、WAF 前置 |
| Nginx 默认支持 | 仅 HTTP 正向(HTTPS 需第三方模块) | 原生支持 |
7.2 反向代理示例
nginx
http {
upstream backend {
server 127.0.0.1:8000;
keepalive 32;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://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;
}
}
}
7.3 正向代理配置
背景 :192.168.1.191 可以上外网(公网 IP 223.234.56.45),192.168.1.192 不能上外网,使用 Nginx 正向代理让内网主机访问外网。
前提 :Nginx 需编译 ngx_http_proxy_connect_module(默认 Nginx 不支持 HTTPS 正向代理 CONNECT 隧道)。
nginx
# HTTP 正向代理
server {
resolver 8.8.8.8 valid=300s; # 必须指定 DNS,用于解析 $host
listen 80;
server_name localhost;
location / {
proxy_pass http://$host$request_uri;
proxy_set_header Host $host;
proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
# HTTPS 正向代理(需 proxy_connect 模块)
server {
resolver 8.8.8.8 valid=300s;
listen 443;
proxy_connect;
proxy_connect_allow 443 563;
proxy_connect_connect_timeout 10s;
proxy_connect_read_timeout 10s;
proxy_connect_send_timeout 10s;
location / {
proxy_pass http://$host;
proxy_set_header Host $host;
}
}
验证:
bash
curl -I http://www.baidu.com/ -v -x 127.0.0.1:80
curl -I https://www.baidu.com/ -v -x 127.0.0.1:443
客户端配置:
bash
# 仅 yum 走代理
# /etc/yum.conf
proxy=http://192.168.0.20:80
# 全局代理
export http_proxy=http://192.168.0.20:80
export https_proxy=http://192.168.0.20:443
export no_proxy=localhost,127.0.0.1,10.0.0.0/8
8. 负载均衡
通过 upstream 模块实现。
| 策略 | 说明 | 生产注意 |
|---|---|---|
| 轮询(默认) | 依次分配 | 后端性能不均时考虑 weight |
| weight | 加权轮询 | 权重高的机器承担更多流量 |
| ip_hash | 按客户端 IP 哈希 | 同一出口 IP 固定到同一后端;NAT 后大量用户同 IP 会失衡 |
| least_conn | 最少连接(1.9.1+) | 长连接场景比轮询更均衡 |
| hash | 通用哈希(如 $request_uri) |
缓存类业务可让同一 URL 固定后端 |
| fair | 按响应时间分配 | 第三方模块,开源版 Nginx 不包含 |
| url_hash | 按 URL 哈希 | 第三方模块(Tengine 等) |
nginx
upstream backend {
least_conn;
server 10.0.0.1:8080 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 weight=1 max_fails=3 fail_timeout=30s;
keepalive 64;
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
session 问题:ip_hash 只能缓解,生产推荐 Session 外置(Redis)+ 无状态应用,而非依赖 Nginx 粘性。
健康检查:
- 被动健康检查 :
max_fails+fail_timeout(默认机制) - 主动健康检查 :需
nginx_upstream_check_module(Tengine)或 Nginx Plus / OpenResty
9. SSL/TLS 与证书运维
9.1 证书替换
下载已申请完成的 SSL 证书文件

注意事项:
- CSR 文件是申请证书时上传或系统在线生成的,安装时可忽略
- 替换前备份旧证书,确认新证书链完整(fullchain / bundle)
替换步骤:
- 打开 SSL 证书配置路径
- 备份旧证书,上传新证书(如
xxx_bundle.crt和xxx.key) - 修改 conf 中
ssl_certificate和ssl_certificate_key路径 - 检查并重载:
nginx -t && nginx -s reload
9.2 生产推荐 SSL 配置
nginx
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
add_header Strict-Transport-Security "max-age=63072000" always;
}
9.3 四层 SSL(stream 模块)
背景:用于 TCP 层反向代理并终结 TLS(如 MQTT over TLS)。
yaml
stream {
upstream stream_backend {
zone tcp_servers 512k;
hash $remote_addr consistent;
server emqx:1883 max_fails=2 fail_timeout=30s;
}
server {
listen 1883 ssl;
proxy_pass stream_backend;
proxy_buffer_size 4k;
ssl_handshake_timeout 15s;
ssl_certificate /export/certs/ca.crt;
ssl_certificate_key /export/certs/ca.key;
}
}
9.4 证书到期监控
bash
# 检查证书过期时间(提前 30 天告警)
echo | openssl s_client -connect www.example.com:443 -servername www.example.com 2>/dev/null \
| openssl x509 -noout -dates
10. HTTP 跳转 HTTPS 两种场景
10.1 用户直接访问 Nginx(无上层代理)

nginx
# http server
server {
listen 80;
server_name www.example.com;
return 301 https://$host$request_uri;
}
# https server
server {
listen 443 ssl http2;
server_name www.example.com;
# ... SSL 与业务配置
}
10.2 上层有代理(SLB/CDN/WAF)

场景说明:
- 用户不直接访问 Nginx,而是通过上层 Proxy
- 实际 HTTPS 终结在上层 Proxy,Nginx 侧通常只收到 HTTP
- 此时
$scheme始终为http,不能直接用来判断
nginx
# 错误:条件恒真,导致无限重定向
server {
listen 80;
if ($scheme = "http") {
return 301 https://$host$request_uri;
}
}
# 正确:读取上层 Proxy 传递的 Header(名称因厂商而异)
server {
listen 80;
# 阿里云 SLB: X-Forwarded-Proto
# 某些 CDN: X-Forwarded-Scheme / X-Client-Scheme
if ($http_x_forwarded_proto = "http") {
return 301 https://$host$request_uri;
}
}
生产建议 :优先在上层 Proxy 做 HTTP→HTTPS 跳转;Nginx 层用
$http_x_forwarded_proto做二次兜底,并确认 Header 未被客户端伪造(上层 Proxy 应剥离外部传入的同名 Header)。
11. 常用模块
11.1 访问控制
基于用户认证(auth_basic):
nginx
auth_basic "Please input password";
auth_basic_user_file /etc/nginx/conf.d/auth_pwd;
bash
yum -y install httpd-tools
htpasswd -c /etc/nginx/conf.d/auth_pwd username
基于 IP 访问控制:
nginx
location /admin/ {
allow 10.18.45.65;
allow 10.18.45.181;
deny all;
}
11.2 rewrite 模块
nginx
server {
listen 80;
server_name www.aa.com;
location /test {
rewrite ^/test(.*)$ http://www.bb.com/test1$1 permanent;
}
}
11.3 压缩模块
nginx
gzip on;
gzip_http_version 1.1;
gzip_comp_level 2;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript;
gzip_static on; # 优先使用预压缩的 .gz 文件(需编译 gzip_static 模块)
11.4 防盗链
nginx
valid_referers none blocked *.a.com;
if ($invalid_referer) {
return 403;
}
注意:
if在 Nginx 中有坑,防盗链场景可用,复杂逻辑建议改用map。
11.5 状态监控(stub_status)
nginx
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
allow 10.0.0.0/8; # 监控网段
deny all;
}
指标含义:
| 指标 | 含义 |
|---|---|
| Active connections | 当前活跃连接数(含 waiting) |
| accepts / handled | 已接受 / 已处理连接数;两者差值大说明有丢弃 |
| requests | 总请求数 |
| Reading / Writing / Waiting | 读请求头 / 响应客户端 / 空闲 keepalive |
11.6 限流与连接限制
nginx
# http 块
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn:10m;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_conn conn 10;
}
}
12. 生产环境性能优化
12.1 核心参数
nginx
worker_processes auto; # 自动匹配 CPU 核数(推荐)
worker_cpu_affinity auto; # 1.9.1+ 自动绑核
worker_rlimit_nofile 102400;
events {
use epoll; # Linux 默认即是,可省略
worker_connections 10240; # 单 worker 最大连接数
multi_accept on; # 一次 accept 多个连接(原注释写反了)
# accept_mutex off; # 高并发现代内核通常关闭(默认 off)
}
http {
sendfile on;
tcp_nopush on; # 配合 sendfile,累计到一定大小再发
tcp_nodelay on; # keepalive 连接降低延迟
keepalive_timeout 65;
keepalive_requests 1000;
server_tokens off; # 隐藏版本号
autoindex off;
client_max_body_size 100m;
client_header_buffer_size 4k;
large_client_header_buffers 4 16k; # 过大易内存浪费;414 时适当调大
gzip on;
gzip_min_length 1k;
gzip_comp_level 4; # 官方推荐 1-6,4 为性能与压缩率折中
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_vary on; # 配合 CDN/缓存,原配置 off 不推荐
gzip_disable "MSIE [1-6]\.";
include conf.d/*.conf;
}
12.2 连接数估算
最大并发连接数 ≈ worker_processes × worker_connections
实际上游连接、静态资源、长连接都会占用 worker_connections;需预留 20%~30% 余量。同时检查系统 ulimit -n 和 /proc/sys/fs/file-max。
12.3 反向代理超时(按业务调整)
nginx
proxy_connect_timeout 5s; # 建连超时,不宜过长
proxy_send_timeout 60s;
proxy_read_timeout 60s; # 慢接口可单独在 location 调大
12.4 隐藏版本号
nginx
# http 块
server_tokens off;
如需彻底隐藏(Error 页也不显示),需 recompile 或 patch,一般 server_tokens off 即可。
13. 日志、切割与排障
13.1 推荐 access_log 格式
nginx
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct=$upstream_connect_time '
'uht=$upstream_header_time urt=$upstream_response_time '
'ups=$upstream_addr';
access_log /export/server/nginx/logs/access.log main;
error_log /export/server/nginx/logs/error.log warn;
13.2 日志轮转
按大小切割:
nginx
/export/server/nginx/logs/*.log {
create 0644 root root
compress
delaycompress
missingok
dateext
dateformat -%Y%m%d
rotate 1000
notifempty
size 100M
postrotate
if [ -f /export/server/nginx/logs/nginx.pid ]; then
/export/server/nginx/sbin/nginx -s reopen
fi
endscript
}
按时间切割(daily):
nginx
/export/server/nginx/logs/*.log {
create 0644 root root
daily
compress
delaycompress
missingok
dateext
dateformat -%Y%m%d
rotate 30
notifempty
postrotate
if [ -f /export/server/nginx/logs/nginx.pid ]; then
/export/server/nginx/sbin/nginx -s reopen
fi
endscript
}
bash
/usr/sbin/logrotate -f /etc/logrotate.d/nginx
修正 :原
dateformat %Y%m%d%s中%s并非 logrotate 标准占位符,应使用dateext+dateformat -%Y%m%d。
13.3 常见 HTTP 状态码排障
| 状态码 | 含义 | 常见原因 | 排查方向 |
|---|---|---|---|
| 499 | 客户端主动断开 | 用户取消、前端超时、LB 超时短于后端 | 查 $request_time、上游耗时、客户端/LB timeout |
| 502 | Bad Gateway | 后端未启动、端口不通、upstream 返回非法响应 | curl 后端IP:端口、查 error.log connect() failed |
| 503 | Service Unavailable | 限流触发、upstream 全部 marked down | 查 limit_req、后端健康、max_fails |
| 504 | Gateway Timeout | proxy_read_timeout 到期后端仍未响应 |
查 $upstream_response_time、后端慢 SQL/GC |
| 413 | Request Entity Too Large | 上传超过 client_max_body_size |
调大 limit 或走对象存储直传 |
| 414 | URI Too Long | 超过 large_client_header_buffers |
检查是否异常长 URL / Cookie |
13.4 502/504 排查时序
后端应用 Nginx 用户/监控 后端应用 Nginx 用户/监控 #mermaid-svg-MWWcl378SsNcVGOD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MWWcl378SsNcVGOD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MWWcl378SsNcVGOD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MWWcl378SsNcVGOD .error-icon{fill:#552222;}#mermaid-svg-MWWcl378SsNcVGOD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MWWcl378SsNcVGOD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MWWcl378SsNcVGOD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MWWcl378SsNcVGOD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MWWcl378SsNcVGOD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MWWcl378SsNcVGOD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MWWcl378SsNcVGOD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MWWcl378SsNcVGOD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MWWcl378SsNcVGOD .marker.cross{stroke:#333333;}#mermaid-svg-MWWcl378SsNcVGOD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MWWcl378SsNcVGOD p{margin:0;}#mermaid-svg-MWWcl378SsNcVGOD .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-MWWcl378SsNcVGOD text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-MWWcl378SsNcVGOD .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-MWWcl378SsNcVGOD .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-MWWcl378SsNcVGOD .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-MWWcl378SsNcVGOD .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-MWWcl378SsNcVGOD #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-MWWcl378SsNcVGOD .sequenceNumber{fill:white;}#mermaid-svg-MWWcl378SsNcVGOD #sequencenumber{fill:#333;}#mermaid-svg-MWWcl378SsNcVGOD #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-MWWcl378SsNcVGOD .messageText{fill:#333;stroke:none;}#mermaid-svg-MWWcl378SsNcVGOD .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-MWWcl378SsNcVGOD .labelText,#mermaid-svg-MWWcl378SsNcVGOD .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-MWWcl378SsNcVGOD .loopText,#mermaid-svg-MWWcl378SsNcVGOD .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-MWWcl378SsNcVGOD .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-MWWcl378SsNcVGOD .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-MWWcl378SsNcVGOD .noteText,#mermaid-svg-MWWcl378SsNcVGOD .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-MWWcl378SsNcVGOD .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-MWWcl378SsNcVGOD .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-MWWcl378SsNcVGOD .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-MWWcl378SsNcVGOD .actorPopupMenu{position:absolute;}#mermaid-svg-MWWcl378SsNcVGOD .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-MWWcl378SsNcVGOD .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-MWWcl378SsNcVGOD .actor-man circle,#mermaid-svg-MWWcl378SsNcVGOD line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-MWWcl378SsNcVGOD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} error.log: connect() failed proxy_read_timeout 到期 alt 502 - 连接失败 504 - 读取超时 请求 connect() refused / timeout 502 转发请求 处理过慢 504
13.5 常用排障命令
bash
# 配置检查
nginx -t
# 查看连接与 upstream
ss -s
ss -tnp | grep nginx
# 实时 access 日志(按状态码过滤)
tail -f access.log | awk '$9 ~ /^5/'
# 统计 5xx TOP URI
awk '$9 ~ /^5/ {print $7}' access.log | sort | uniq -c | sort -rn | head
# 压测 upstream 连通性
curl -v -H "Host: www.example.com" http://127.0.0.1/backend-health
14. 平滑升级与动态加模块
14.1 二进制平滑升级(USR2)
新 Worker 旧 Worker 新 Master 旧 Master 运维 新 Worker 旧 Worker 新 Master 旧 Master 运维 #mermaid-svg-wKZPkdAmlVHc0Evh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wKZPkdAmlVHc0Evh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wKZPkdAmlVHc0Evh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wKZPkdAmlVHc0Evh .error-icon{fill:#552222;}#mermaid-svg-wKZPkdAmlVHc0Evh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wKZPkdAmlVHc0Evh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wKZPkdAmlVHc0Evh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wKZPkdAmlVHc0Evh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wKZPkdAmlVHc0Evh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wKZPkdAmlVHc0Evh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wKZPkdAmlVHc0Evh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wKZPkdAmlVHc0Evh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wKZPkdAmlVHc0Evh .marker.cross{stroke:#333333;}#mermaid-svg-wKZPkdAmlVHc0Evh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wKZPkdAmlVHc0Evh p{margin:0;}#mermaid-svg-wKZPkdAmlVHc0Evh .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wKZPkdAmlVHc0Evh text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-wKZPkdAmlVHc0Evh .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-wKZPkdAmlVHc0Evh .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-wKZPkdAmlVHc0Evh .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-wKZPkdAmlVHc0Evh .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-wKZPkdAmlVHc0Evh #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-wKZPkdAmlVHc0Evh .sequenceNumber{fill:white;}#mermaid-svg-wKZPkdAmlVHc0Evh #sequencenumber{fill:#333;}#mermaid-svg-wKZPkdAmlVHc0Evh #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-wKZPkdAmlVHc0Evh .messageText{fill:#333;stroke:none;}#mermaid-svg-wKZPkdAmlVHc0Evh .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wKZPkdAmlVHc0Evh .labelText,#mermaid-svg-wKZPkdAmlVHc0Evh .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-wKZPkdAmlVHc0Evh .loopText,#mermaid-svg-wKZPkdAmlVHc0Evh .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-wKZPkdAmlVHc0Evh .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-wKZPkdAmlVHc0Evh .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-wKZPkdAmlVHc0Evh .noteText,#mermaid-svg-wKZPkdAmlVHc0Evh .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-wKZPkdAmlVHc0Evh .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wKZPkdAmlVHc0Evh .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wKZPkdAmlVHc0Evh .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wKZPkdAmlVHc0Evh .actorPopupMenu{position:absolute;}#mermaid-svg-wKZPkdAmlVHc0Evh .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-wKZPkdAmlVHc0Evh .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wKZPkdAmlVHc0Evh .actor-man circle,#mermaid-svg-wKZPkdAmlVHc0Evh line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-wKZPkdAmlVHc0Evh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 升级完成,零停机 替换 sbin/nginx 二进制 kill -USR2 fork 新 Master(新二进制) 启动新 Worker kill -WINCH(via pid.oldbin) 优雅关闭旧 Worker 全部退出 自动退出
步骤:
- 下载新版本,解压,执行旧版
nginx -V记录全部 configure 参数 - 在新源码目录
./configure <原参数>&&make(不要 make install) - 备份旧二进制:
cp sbin/nginx sbin/nginx.bak.$(date +%Y%m%d) - 复制新二进制:
cp objs/nginx /export/server/nginx/sbin/nginx - 发送 USR2:
kill -USR2 $(cat /export/server/nginx/logs/nginx.pid) - 确认新 Master 已启动:
ps aux | grep nginx - 关闭旧 Worker:
kill -WINCH $(cat /export/server/nginx/logs/nginx.pid.oldbin) - 验证:
/export/server/nginx/sbin/nginx -v
修正 :原步骤 8~9(对 oldbin 再发 HUP/QUIT)通常不需要;WINCH 后旧 Master 会在旧 Worker 全部退出后自动结束。
14.2 动态添加模块
背景 :已安装且正在使用的 Nginx,经 nginx -V 检查缺失某个模块,需要重新编译增加。

步骤:
nginx -V记录现有全部 configure 参数- 下载与当前运行版本一致的源码包
- 重新 configure:必须带上所有原有参数 + 新增模块参数
make(不要 make install)- 备份旧二进制:
cp sbin/nginx sbin/nginx.bak_20240918 - 覆盖二进制:
cp objs/nginx /export/server/nginx/sbin/nginx - 平滑升级(USR2 流程)或
nginx -t && nginx -s reload(仅加模块不涉及 ABI 变化时 reload 即可,大版本变更建议 USR2) nginx -V确认新模块已存在
bash
# 示例:增加 http_v2 模块(注意拼写 --with-http_v2_module,原 --wiht 为笔误)
./configure --prefix=/export/package/nginx-1.20.1/ \
--with-http_stub_status_module \
--with-http_ssl_module \
--with-http_v2_module \
# ... 其他原有参数
make
生产注意:原步骤「stop nginx 再覆盖」会造成业务中断;应改用 USR2 平滑升级。
15. 生产值班 SOP
15.1 配置变更 Checklist
| 步骤 | 操作 |
|---|---|
| 1 | 确认变更窗口与回滚方案 |
| 2 | 备份:cp -a conf.d/ conf.d.bak.$(date +%Y%m%d%H%M) |
| 3 | 修改配置 |
| 4 | nginx -t 语法检查 |
| 5 | 灰度节点先 reload,观察 5~10 分钟 |
| 6 | 全量 reload:nginx -s reload |
| 7 | 验证:核心业务 curl、监控 5xx 率、error.log |
15.2 接到 502/504 告警
- 先止损 :若单后端故障,upstream 中
down该节点或 SLB 摘除 - 看监控:5xx 比例、是否单节点 / 全集群
- 看 Nginx :
tail -100 error.log、stub_status - 探活后端 :
curl/ 端口探测 / 应用日志 - 关联变更:最近是否 reload、发版、证书更换
- 留证后调整:timeout、keepalive、upstream 参数
15.3 变更「只读 / 慎用」分级
| 级别 | 操作 | 说明 |
|---|---|---|
| 只读安全 | nginx -t、-V、tail 日志、stub_status |
随时可执行 |
| 低风险 | nginx -s reload、reopen |
标准运维操作 |
| 慎用 | stop、quit、USR2 升级 |
需变更窗口,双人复核 |
| 禁止慌乱操作 | 无备份直接改主配置、生产 kill -9 nginx |
可能导致全站不可恢复 |
16. 安全加固清单
| 项 | 配置 |
|---|---|
| 隐藏版本 | server_tokens off; |
| 禁止目录遍历 | autoindex off; |
| 限制请求体 | client_max_body_size 按业务最小化 |
| 限流防刷 | limit_req / limit_conn |
| 管理端隔离 | /nginx_status、/admin 仅内网 IP |
| TLS | 禁用 TLSv1.0/1.1,优先 TLS 1.3 |
| Header 安全 | X-Frame-Options、X-Content-Type-Options |
| 敏感路径 | 禁止访问 .git、.env、*.sql |
nginx
location ~ /\.(git|env|ht) {
deny all;
return 404;
}
17. 真实 IP 与多层代理
多层代理(CDN → SLB → Nginx → 应用)下,Nginx 默认看到的 $remote_addr 是上一跳代理 IP,会导致限流、黑白名单、access 日志、审计全部失真。
17.1 转发头设置(Nginx 作为中间层)
nginx
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;
17.2 还原客户端真实 IP(Nginx 作为最内层接入)
nginx
http {
# 只信任已知上游网段,切勿写 0.0.0.0/0
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 223.234.56.45/32; # SLB 公网 IP 示例
real_ip_header X-Forwarded-For; # 或 X-Real-IP
real_ip_recursive on; # 多级代理时从右向左解析
# 之后 $remote_addr 即为真实客户端 IP
}
17.3 各层 Header 对照
| 层级 | 常见传递方式 | Nginx 侧处理 |
|---|---|---|
| CDN | X-Forwarded-For / True-Client-IP |
set_real_ip_from CDN 回源 IP 段 |
| 云 SLB | X-Forwarded-For + X-Forwarded-Proto |
信任 SLB 内网/专线 IP |
| PROXY Protocol | 四层透传(Stream/部分 LB) | listen 443 proxy_protocol; + real_ip_header proxy_protocol; |
安全注意 :
set_real_ip_from必须精确到可信上游;否则攻击者伪造X-Forwarded-For可绕过 IP 限制。
18. systemd 服务管理
生产环境推荐用 systemd 托管 Nginx,实现开机自启、异常重启、统一 reload。
18.1 unit 文件示例
ini
# /usr/lib/systemd/system/nginx.service
[Unit]
Description=Nginx HTTP Server
After=network-online.target
Wants=network-online.target
[Service]
Type=forking
PIDFile=/export/server/nginx/logs/nginx.pid
ExecStartPre=/export/server/nginx/sbin/nginx -t -q
ExecStart=/export/server/nginx/sbin/nginx
ExecReload=/export/server/nginx/sbin/nginx -s reload
ExecStop=/export/server/nginx/sbin/nginx -s quit
PrivateTmp=true
Restart=on-failure
RestartSec=5s
LimitNOFILE=102400
[Install]
WantedBy=multi-user.target
18.2 常用命令
bash
systemctl daemon-reload # 修改 unit 后重载
systemctl enable --now nginx # 开机自启并启动
systemctl status nginx
systemctl reload nginx # 等价 nginx -s reload(会先走 ExecStartPre -t)
systemctl stop nginx # 等价 nginx -s quit(优雅停止)
journalctl -u nginx -f # 查看 systemd 侧日志
18.3 启动失败常见原因
| 现象 | 排查 |
|---|---|
nginx: [emerg] bind() to 0.0.0.0:80 failed |
端口被占用:`ss -tlnp |
open() ... failed (13: Permission denied) |
非 root 绑 80/443;或目录权限不对 |
nginx: configuration file ... test failed |
nginx -t 看具体行号 |
| SELinux 拦截 | ausearch -m avc -ts recent;或 setsebool -P httpd_can_network_connect 1 |
| PID 文件 stale | 进程已死但 pid 文件在:rm -f nginx.pid 后重启 |
维护建议 :日常变更用
systemctl reload nginx,不要restart(restart 会短暂中断连接)。
19. 磁盘、inode 与日志容量巡检
Nginx 本身很少占磁盘,日志和临时文件才是维护中的隐形炸弹。
19.1 风险点
| 路径/对象 | 风险 |
|---|---|
access.log / error.log |
高 QPS 下单日可达数十 GB |
proxy_temp_path |
大文件上传/代理缓冲写满磁盘 |
client_body_temp_path |
大 POST 上传占满 |
logrotate 失败 + nginx -s reopen 未执行 |
旧 fd 仍写已 rename 的文件,inode 泄漏 |
19.2 巡检命令
bash
# 磁盘空间(告警阈值建议 80% warn / 90% critical)
df -hT /export/server/nginx/logs
# inode(小文件海量时可能比磁盘先满)
df -i /export/server/nginx/logs
# 日志目录占用 TOP
du -sh /export/server/nginx/logs/* | sort -rh | head
# 当前 nginx 打开哪些日志文件
ls -l /proc/$(cat /export/server/nginx/logs/nginx.pid)/fd | grep log
# 验证 logrotate 最近一次是否成功
grep nginx /var/lib/logrotate/status
19.3 应急处理 SOP
bash
# 1. 确认是磁盘满还是 inode 满
df -h; df -i
# 2. 禁止直接 rm 正在写入的 access.log
# 正确做法:truncate 或 logrotate + reopen
truncate -s 0 /export/server/nginx/logs/access.log # 仅紧急止血
# 或
/usr/sbin/logrotate -f /etc/logrotate.d/nginx
nginx -s reopen
# 3. 清理已压缩的历史日志(确认 postrotate 已执行后再删)
find /export/server/nginx/logs -name "*.gz" -mtime +30 -delete
19.4 临时目录配置(建议显式指定到大分区)
nginx
http {
client_body_temp_path /data/nginx/client_body 1 2;
proxy_temp_path /data/nginx/proxy 1 2;
fastcgi_temp_path /data/nginx/fastcgi 1 2;
}
20. 配置回滚与版本管理
20.1 变更前备份(标准动作)
bash
BACKUP_DIR="/export/backup/nginx/conf.d.bak.$(date +%Y%m%d%H%M)"
mkdir -p "$BACKUP_DIR"
cp -a /export/server/nginx/conf.d/ "$BACKUP_DIR/"
cp -a /export/server/nginx/nginx.conf "$BACKUP_DIR/"
nginx -t # 改之前也先确认当前配置 OK
20.2 一键回滚
bash
# 回滚到指定备份
BACKUP_DIR="/export/backup/nginx/conf.d.bak.202406241030"
cp -a "$BACKUP_DIR/conf.d/"* /export/server/nginx/conf.d/
cp -a "$BACKUP_DIR/nginx.conf" /export/server/nginx/nginx.conf
nginx -t && nginx -s reload
20.3 Git 管理建议
nginx-config/ # 独立 Git 仓库
├── nginx.conf
├── conf.d/
│ ├── api.example.com.conf
│ └── static.example.com.conf
└── scripts/
├── deploy.sh # rsync + nginx -t + reload
└── rollback.sh
| 实践 | 说明 |
|---|---|
| 一域名一文件 | MR Review 时可读、可回滚 |
CI 跑 nginx -t |
合并前语法检查 |
| 变更单关联 commit | 故障时可快速定位「谁、何时、改了什么」 |
| 灰度节点先 deploy | 1 台验证 5~10 分钟再全量 |
21. 监控告警与指标解读
stub_status 适合人工查看;生产告警建议接入 nginx-prometheus-exporter 或 nginx-module-vts。
21.1 stub_status + exporter
nginx
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
bash
# nginx-prometheus-exporter 示例
nginx-prometheus-exporter -nginx.scrape-uri http://127.0.0.1/nginx_status
21.2 建议告警项
| 指标 | 阈值参考 | 含义 |
|---|---|---|
5xx_rate |
> 1% 持续 5min | 后端或配置异常 |
nginx_connections_active |
> worker_connections × workers × 0.8 | 连接接近上限 |
accepts - handled |
差值持续增大 | 连接被丢弃(worker 满、内核队列满) |
upstream_response_time p99 |
突增 3 倍 | 后端变慢 |
证书 expiry_days |
< 30 天 | 避免 HTTPS 中断 |
21.3 证书到期监控
bash
# 脚本/check_exporter 探活
echo | openssl s_client -connect www.example.com:443 -servername www.example.com 2>/dev/null \
| openssl x509 -noout -enddate
可与项目内
Prometheus笔记.md的 blackbox_exporter、Alertmanager 路由配合使用。
22. upstream 节点摘除与灰度
22.1 临时摘除故障节点
nginx
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080 down; # 立即摘除,reload 生效
server 10.0.0.3:8080 backup; # 仅主节点全 down 时启用
}
bash
nginx -t && nginx -s reload # down 标记需 reload,非热更新字段
22.2 灰度放量
nginx
upstream backend {
server 10.0.0.1:8080 weight=9; # 旧版本 90%
server 10.0.0.2:8080 weight=1; # 新版本 10%
}
22.3 维护窗口 SOP
新节点 旧节点 Nginx 运维 新节点 旧节点 Nginx 运维 #mermaid-svg-A2nagPvk3mnlLYmX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-A2nagPvk3mnlLYmX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-A2nagPvk3mnlLYmX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-A2nagPvk3mnlLYmX .error-icon{fill:#552222;}#mermaid-svg-A2nagPvk3mnlLYmX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-A2nagPvk3mnlLYmX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-A2nagPvk3mnlLYmX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-A2nagPvk3mnlLYmX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-A2nagPvk3mnlLYmX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-A2nagPvk3mnlLYmX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-A2nagPvk3mnlLYmX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-A2nagPvk3mnlLYmX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-A2nagPvk3mnlLYmX .marker.cross{stroke:#333333;}#mermaid-svg-A2nagPvk3mnlLYmX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-A2nagPvk3mnlLYmX p{margin:0;}#mermaid-svg-A2nagPvk3mnlLYmX .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-A2nagPvk3mnlLYmX text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-A2nagPvk3mnlLYmX .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-A2nagPvk3mnlLYmX .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-A2nagPvk3mnlLYmX .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-A2nagPvk3mnlLYmX .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-A2nagPvk3mnlLYmX #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-A2nagPvk3mnlLYmX .sequenceNumber{fill:white;}#mermaid-svg-A2nagPvk3mnlLYmX #sequencenumber{fill:#333;}#mermaid-svg-A2nagPvk3mnlLYmX #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-A2nagPvk3mnlLYmX .messageText{fill:#333;stroke:none;}#mermaid-svg-A2nagPvk3mnlLYmX .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-A2nagPvk3mnlLYmX .labelText,#mermaid-svg-A2nagPvk3mnlLYmX .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-A2nagPvk3mnlLYmX .loopText,#mermaid-svg-A2nagPvk3mnlLYmX .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-A2nagPvk3mnlLYmX .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-A2nagPvk3mnlLYmX .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-A2nagPvk3mnlLYmX .noteText,#mermaid-svg-A2nagPvk3mnlLYmX .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-A2nagPvk3mnlLYmX .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-A2nagPvk3mnlLYmX .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-A2nagPvk3mnlLYmX .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-A2nagPvk3mnlLYmX .actorPopupMenu{position:absolute;}#mermaid-svg-A2nagPvk3mnlLYmX .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-A2nagPvk3mnlLYmX .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-A2nagPvk3mnlLYmX .actor-man circle,#mermaid-svg-A2nagPvk3mnlLYmX line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-A2nagPvk3mnlLYmX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 存量连接处理完,无新流量 逐步放大 weight upstream 中标记 Old 为 down nginx -t && reload 部署/重启/维护 取消 down,调整 weight 灰度 reload
注意 :
down只阻止新连接分配;长连接/keepalive 需等超时或配合后端 graceful shutdown。
23. WebSocket / SSE 长连接
默认 proxy_read_timeout 60s 会导致 WebSocket、SSE 被 Nginx 主动断开。
nginx
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
location /sse/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_buffering off; # SSE 必须关缓冲
proxy_cache off;
proxy_read_timeout 86400s;
chunked_transfer_encoding off;
}
排障要点:
- 504 +
$upstream_response_time接近 timeout → 调大proxy_read_timeout - 连接数暴涨 → 检查
worker_connections是否够用(每个 WS 占 1 连接) - 502 +
upstream prematurely closed→ 后端重启或超时关连接
24. 静态资源、缓存与 map 模块
24.1 静态资源与 SPA
nginx
location / {
root /var/www/html;
try_files $uri $uri/ /index.html; # Vue/React SPA 路由
expires 7d;
add_header Cache-Control "public, immutable";
}
location ~* \.(js|css|png|jpg|gif|ico|woff2)$ {
expires 30d;
access_log off;
}
24.2 open_file_cache(高 QPS 静态站)
nginx
open_file_cache max=10000 inactive=60s;
open_file_cache_valid 80s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
24.3 map 替代复杂 if
nginx
# 按 User-Agent 分流
map $http_user_agent $backend {
default desktop;
~*Mobile mobile;
~*Android mobile;
}
upstream desktop { server 10.0.0.1:8080; }
upstream mobile { server 10.0.0.2:8080; }
server {
location / {
proxy_pass http://$backend;
}
}
# 维护页开关(运维常用)
map $remote_addr $maintenance {
default 1;
10.0.0.0/8 0; # 内网跳过维护页
127.0.0.1 0;
}
24.4 resolver(域名 upstream / 动态后端)
nginx
resolver 10.0.0.2 valid=30s ipv6=off;
location / {
set $upstream_api api.internal.example.com;
proxy_pass http://$upstream_api:8080;
}
valid过大会导致 DNS 变更后切换慢;K8s Service 域名建议valid=10s~30s。
25. 日常巡检清单与常见故障
25.1 日检 / 周检清单
| 频率 | 检查项 | 命令/方法 |
|---|---|---|
| 每日 | 进程存活 | systemctl is-active nginx |
| 每日 | 5xx 率 | 监控面板 / `awk '$9~/^5/' access.log |
| 每日 | 磁盘/inode | df -h; df -i |
| 每日 | 证书有效期 | openssl / 监控告警 |
| 每周 | 配置漂移 | nginx -T 与 Git 对比 |
| 每周 | error.log 新 emerg/alert | grep emerg error.log |
| 每周 | worker 数是否正常 | `ps aux |
| 每月 | 内核/连接数余量 | ss -s、stub_status |
| 每月 | logrotate 是否生效 | /var/lib/logrotate/status |
25.2 常见故障速查
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 全站 502 | 后端全挂 / upstream 配错 | curl upstream、查 error.log |
| 间歇 502 | 单后端故障 / keepalive 到 dead socket | 摘节点、proxy_next_upstream |
| 全站 504 | 后端集体慢 / timeout 过短 | 查 $upstream_response_time、调 timeout |
| reload 后部分 404 | location/proxy_pass 路径变更 | 对比 Git diff、nginx -T |
| 磁盘满 | 日志未切割 | logrotate + reopen + 清理 .gz |
| 连接数打满 | worker_connections 不足 / CC | 调参、限流、上游 WAF |
| 改配置不生效 | 改错文件 / 未 reload | `nginx -T |
| 502 仅 POST 大文件 | client_max_body_size 太小 |
调大 limit 或走 OSS 直传 |
25.3 维护权限建议
bash
# 推荐:专用低权用户运行 worker
user nginx nginx;
# 配置目录:root 拥有,nginx 只读
chown -R root:root /export/server/nginx/conf.d
chmod 644 /export/server/nginx/conf.d/*.conf
# 日志目录:nginx 可写
chown -R nginx:nginx /export/server/nginx/logs
26. 高可用:Keepalived + VIP
裸机 / 虚拟机部署 Nginx 时,常用 Keepalived + VIP(虚拟 IP) 实现主备切换,避免单点故障。
26.1 架构示意
后端应用 Backup Nginx Master Nginx VIP 10.0.0.100 用户 后端应用 Backup Nginx Master Nginx VIP 10.0.0.100 用户 #mermaid-svg-gx9I3GWdK7UwPNI9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gx9I3GWdK7UwPNI9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gx9I3GWdK7UwPNI9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gx9I3GWdK7UwPNI9 .error-icon{fill:#552222;}#mermaid-svg-gx9I3GWdK7UwPNI9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gx9I3GWdK7UwPNI9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gx9I3GWdK7UwPNI9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gx9I3GWdK7UwPNI9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gx9I3GWdK7UwPNI9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gx9I3GWdK7UwPNI9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gx9I3GWdK7UwPNI9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gx9I3GWdK7UwPNI9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gx9I3GWdK7UwPNI9 .marker.cross{stroke:#333333;}#mermaid-svg-gx9I3GWdK7UwPNI9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gx9I3GWdK7UwPNI9 p{margin:0;}#mermaid-svg-gx9I3GWdK7UwPNI9 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-gx9I3GWdK7UwPNI9 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-gx9I3GWdK7UwPNI9 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-gx9I3GWdK7UwPNI9 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-gx9I3GWdK7UwPNI9 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-gx9I3GWdK7UwPNI9 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-gx9I3GWdK7UwPNI9 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-gx9I3GWdK7UwPNI9 .sequenceNumber{fill:white;}#mermaid-svg-gx9I3GWdK7UwPNI9 #sequencenumber{fill:#333;}#mermaid-svg-gx9I3GWdK7UwPNI9 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-gx9I3GWdK7UwPNI9 .messageText{fill:#333;stroke:none;}#mermaid-svg-gx9I3GWdK7UwPNI9 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-gx9I3GWdK7UwPNI9 .labelText,#mermaid-svg-gx9I3GWdK7UwPNI9 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-gx9I3GWdK7UwPNI9 .loopText,#mermaid-svg-gx9I3GWdK7UwPNI9 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-gx9I3GWdK7UwPNI9 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-gx9I3GWdK7UwPNI9 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-gx9I3GWdK7UwPNI9 .noteText,#mermaid-svg-gx9I3GWdK7UwPNI9 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-gx9I3GWdK7UwPNI9 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-gx9I3GWdK7UwPNI9 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-gx9I3GWdK7UwPNI9 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-gx9I3GWdK7UwPNI9 .actorPopupMenu{position:absolute;}#mermaid-svg-gx9I3GWdK7UwPNI9 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-gx9I3GWdK7UwPNI9 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-gx9I3GWdK7UwPNI9 .actor-man circle,#mermaid-svg-gx9I3GWdK7UwPNI9 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-gx9I3GWdK7UwPNI9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Keepalived MASTER 持有 VIP Master 故障 / nginx 进程挂 请求 流量转发 反向代理 VIP 漂移 Backup 升 MASTER 继续服务 用户无感知(TCP 层可能断连重试)
典型拓扑:
VIP 10.0.0.100
│
┌────────────┴────────────┐
│ │
Nginx-A (MASTER) Nginx-B (BACKUP)
10.0.0.11 10.0.0.12
Keepalived priority 100 Keepalived priority 90
│ │
└────────────┬────────────┘
│
后端 upstream
26.2 Keepalived 配置示例
Master(10.0.0.11):
conf
# /etc/keepalived/keepalived.conf
global_defs {
router_id NGINX_HA_01
script_user root
enable_script_security
}
# Nginx 健康检查:进程不存在则降优先级,触发 VIP 漂移
vrrp_script check_nginx {
script "/etc/keepalived/check_nginx.sh"
interval 2
weight -30
fall 3
rise 2
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass NginxHa2024!
}
virtual_ipaddress {
10.0.0.100/24 dev eth0
}
track_script {
check_nginx
}
notify_master "/etc/keepalived/notify.sh MASTER"
notify_backup "/etc/keepalived/notify.sh BACKUP"
notify_fault "/etc/keepalived/notify.sh FAULT"
}
Backup(10.0.0.12) :同上,仅改 state BACKUP、priority 90、router_id NGINX_HA_02。
健康检查脚本:
bash
#!/bin/bash
# /etc/keepalived/check_nginx.sh
# 同时检查进程 + 本地 HTTP 探活(比只检查 pid 更可靠)
if ! pgrep -x nginx > /dev/null; then
exit 1
fi
curl -sf -o /dev/null -H "Host: health.internal" http://127.0.0.1/nginx_status || exit 1
exit 0
bash
chmod +x /etc/keepalived/check_nginx.sh
systemctl enable --now keepalived
26.3 双机 Nginx 配置要点
| 项 | 要求 |
|---|---|
| 配置文件 | 两台完全一致(Git 同步 / Ansible 下发) |
| 证书 | 同步到相同路径,同时更新 |
| 日志 | 各自写本地;或挂载共享存储 / 日志采集 Sidecar |
| upstream | 指向相同后端池 |
| pid / logs | 本地路径,不共享 |
bash
# 变更时:先改 Git → 两台同步 → 两台分别 nginx -t → 两台 reload
ansible nginx_ha -m synchronize -a "src=conf.d/ dest=/export/server/nginx/conf.d/"
ansible nginx_ha -a "nginx -t && nginx -s reload"
26.4 切换与脑裂
正常切换流程:
- Master 上 Nginx 挂掉或 check 脚本失败
- Keepalived 降低 Master 优先级
- Backup 抢占 VIP(发送 gratuitous ARP)
- 流量切到 Backup
脑裂(Split-Brain):两台同时持有 VIP → 部分用户访问 A、部分访问 B,配置不一致时出故障。
| 原因 | 预防 |
|---|---|
| 网络分区 | 多播/单播 VRRP 心跳网络独立;unicast_peer 替代 multicast |
| 防火墙拦截 VRRP | 放行协议 112 或单播 198.18/198.19 |
| check 脚本误判 | 加 fall/rise 防抖;探活 URL 独立于业务 |
| 两台都 MASTER | 确保 virtual_router_id、auth_pass 一致且唯一 |
conf
# 单播模式(云环境推荐,部分云不支持组播)
unicast_src_ip 10.0.0.11
unicast_peer {
10.0.0.12
}
26.5 维护 SOP
| 场景 | 操作 |
|---|---|
| 升级 Master Nginx | 先 systemctl stop keepalived 让 VIP 漂到 Backup → 升级 Master → 启动 keepalived |
| 升级 Keepalived 配置 | 先改 Backup 并 reload → 再改 Master |
| 演练切换 | Backup 上 systemctl restart keepalived 观察 VIP;确认告警触发 |
| 紧急回切 | Master 恢复后自动抢回(priority 高);或手动 systemctl restart keepalived |
bash
# 查看当前 VIP 在哪台
ip addr show eth0 | grep 10.0.0.100
# 查看 Keepalived 状态
systemctl status keepalived
journalctl -u keepalived -f
与云 SLB 对比:云环境更推荐直接用 SLB/ALB 做四层/七层负载 + 健康检查,Keepalived 适合私有云、IDC、无云 LB 的场景。
27. Kubernetes 与 Ingress 运维
K8s 中 Nginx 常以 Ingress Controller 形式运行(如 ingress-nginx),与裸机 Nginx 运维思路相通,但配置载体、发布方式、排障路径不同。
27.1 架构对比
业务 Pod K8s Service Ingress Controller Pod 云 LB / NodePort 用户 业务 Pod K8s Service Ingress Controller Pod 云 LB / NodePort 用户 #mermaid-svg-xUaEwZ6CruRJJHmy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xUaEwZ6CruRJJHmy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xUaEwZ6CruRJJHmy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xUaEwZ6CruRJJHmy .error-icon{fill:#552222;}#mermaid-svg-xUaEwZ6CruRJJHmy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xUaEwZ6CruRJJHmy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xUaEwZ6CruRJJHmy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xUaEwZ6CruRJJHmy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xUaEwZ6CruRJJHmy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xUaEwZ6CruRJJHmy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xUaEwZ6CruRJJHmy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xUaEwZ6CruRJJHmy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xUaEwZ6CruRJJHmy .marker.cross{stroke:#333333;}#mermaid-svg-xUaEwZ6CruRJJHmy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xUaEwZ6CruRJJHmy p{margin:0;}#mermaid-svg-xUaEwZ6CruRJJHmy .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-xUaEwZ6CruRJJHmy text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-xUaEwZ6CruRJJHmy .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-xUaEwZ6CruRJJHmy .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-xUaEwZ6CruRJJHmy .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-xUaEwZ6CruRJJHmy .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-xUaEwZ6CruRJJHmy #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-xUaEwZ6CruRJJHmy .sequenceNumber{fill:white;}#mermaid-svg-xUaEwZ6CruRJJHmy #sequencenumber{fill:#333;}#mermaid-svg-xUaEwZ6CruRJJHmy #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-xUaEwZ6CruRJJHmy .messageText{fill:#333;stroke:none;}#mermaid-svg-xUaEwZ6CruRJJHmy .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-xUaEwZ6CruRJJHmy .labelText,#mermaid-svg-xUaEwZ6CruRJJHmy .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-xUaEwZ6CruRJJHmy .loopText,#mermaid-svg-xUaEwZ6CruRJJHmy .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-xUaEwZ6CruRJJHmy .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-xUaEwZ6CruRJJHmy .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-xUaEwZ6CruRJJHmy .noteText,#mermaid-svg-xUaEwZ6CruRJJHmy .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-xUaEwZ6CruRJJHmy .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-xUaEwZ6CruRJJHmy .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-xUaEwZ6CruRJJHmy .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-xUaEwZ6CruRJJHmy .actorPopupMenu{position:absolute;}#mermaid-svg-xUaEwZ6CruRJJHmy .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-xUaEwZ6CruRJJHmy .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-xUaEwZ6CruRJJHmy .actor-man circle,#mermaid-svg-xUaEwZ6CruRJJHmy line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-xUaEwZ6CruRJJHmy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} HTTPS 请求 转发到 Ingress Controller 匹配 Ingress 规则 转发到 Service ClusterIP kube-proxy / IPVS 负载 响应
| 对比项 | 裸机 Nginx | K8s Ingress-Nginx |
|---|---|---|
| 配置载体 | nginx.conf / conf.d/*.conf |
Ingress / ConfigMap 资源 |
| 生效方式 | nginx -t && reload |
Controller watch API 自动 reload |
| 后端发现 | 静态 IP / 域名 | Service Endpoints 动态更新 |
| 证书 | 文件挂载 | Secret (tls) 或 cert-manager |
| 日志位置 | /var/log/nginx/ |
Pod stdout / 挂载 volume |
| 扩缩容 | 手动 / Keepalived | Deployment replicas + HPA |
27.2 Ingress 资源示例
yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"
nginx.ingress.kubernetes.io/proxy-send-timeout: "120"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: api-example-com-tls
rules:
- host: api.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
27.3 常用 Ingress-Nginx Annotations
| Annotation | 作用 | 运维场景 |
|---|---|---|
proxy-body-size |
上传大小限制 | 413 报错 |
proxy-read-timeout |
读超时 | 504 / 慢接口 |
proxy-send-timeout |
写超时 | 大文件上传 |
ssl-redirect |
HTTP→HTTPS | 与 CDN 协作时注意 |
use-forwarded-headers |
信任 X-Forwarded-* | 前面有 CDN/LB 时必开 |
compute-full-forwarded-for |
追加 X-Forwarded-For | 真实 IP 链路 |
limit-rps / limit-connections |
限流 | 防刷 |
configuration-snippet |
注入 location 块 | 慎用,易冲突 |
canary / canary-weight |
金丝雀发布 | 灰度流量 |
全局 ConfigMap(ingress-nginx-controller):
yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-nginx-controller
namespace: ingress-nginx
data:
use-forwarded-headers: "true"
compute-full-forwarded-for: "true"
use-proxy-protocol: "false"
log-format-upstream: '$remote_addr - $host [$time_local] "$request" $status $body_bytes_sent "$http_referer" rt=$request_time urt=$upstream_response_time ups=$upstream_addr'
proxy-body-size: "50m"
27.4 运维操作
bash
# 查看 Ingress 及后端
kubectl get ingress -A
kubectl describe ingress api-ingress -n production
# 查看 Controller Pod
kubectl get pod -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
# 进入 Controller 看实际 nginx 配置(排障神器)
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- cat /etc/nginx/nginx.conf
kubectl exec -n ingress-nginx deploy/ingress-nginx-controller -- nginx -T 2>/dev/null | less
# 查看 Controller 日志
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx -f --tail=200
# 验证后端 Endpoints 是否正常
kubectl get endpoints api-service -n production
27.5 常见故障
| 现象 | 排查 |
|---|---|
| 404 / default backend | Ingress host/path 不匹配;pathType 错误 |
| 502 | Service 无 Endpoints(Pod 未 Ready);Pod 端口与 targetPort 不一致 |
| 504 | proxy-read-timeout 过短;Pod 资源不足 |
| 证书错误 | Secret 过期;cert-manager 续签失败 kubectl describe certificate |
| 改 Ingress 不生效 | Controller 未 watch 到;看 Controller 日志有无 reload 错误 |
| 仅部分节点 502 | Node 网络 / Calico 策略;Endpoints 分布不均 |
27.6 发布与回滚
bash
# 变更前导出当前配置
kubectl get ingress api-ingress -n production -o yaml > ingress.bak.yaml
# 应用变更
kubectl apply -f ingress.yaml
# 回滚
kubectl apply -f ingress.bak.yaml
# 或
kubectl rollout undo deployment/ingress-nginx-controller -n ingress-nginx
27.7 监控(与 Prometheus 联动)
ingress-nginx 默认暴露 :10254/metrics,Prometheus 抓取 job 示例:
yaml
# ServiceMonitor 或 static_configs
- job_name: ingress-nginx
static_configs:
- targets: ['ingress-nginx-controller-metrics:10254']
关键指标:nginx_ingress_controller_requests(按 status/class/host)、nginx_ingress_controller_nginx_process_connections。
28. 与 WAF / CDN 协作
生产链路常见:用户 → CDN → WAF → SLB → Nginx → 应用。Nginx 作为源站接入层,需与上层正确协作。
28.1 全链路架构
应用 源站 Nginx 云 SLB WAF CDN 边缘节点 用户 应用 源站 Nginx 云 SLB WAF CDN 边缘节点 用户 #mermaid-svg-06rhIOIcLcLbZZN0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-06rhIOIcLcLbZZN0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-06rhIOIcLcLbZZN0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-06rhIOIcLcLbZZN0 .error-icon{fill:#552222;}#mermaid-svg-06rhIOIcLcLbZZN0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-06rhIOIcLcLbZZN0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-06rhIOIcLcLbZZN0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-06rhIOIcLcLbZZN0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-06rhIOIcLcLbZZN0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-06rhIOIcLcLbZZN0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-06rhIOIcLcLbZZN0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-06rhIOIcLcLbZZN0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-06rhIOIcLcLbZZN0 .marker.cross{stroke:#333333;}#mermaid-svg-06rhIOIcLcLbZZN0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-06rhIOIcLcLbZZN0 p{margin:0;}#mermaid-svg-06rhIOIcLcLbZZN0 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-06rhIOIcLcLbZZN0 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-06rhIOIcLcLbZZN0 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-06rhIOIcLcLbZZN0 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-06rhIOIcLcLbZZN0 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-06rhIOIcLcLbZZN0 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-06rhIOIcLcLbZZN0 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-06rhIOIcLcLbZZN0 .sequenceNumber{fill:white;}#mermaid-svg-06rhIOIcLcLbZZN0 #sequencenumber{fill:#333;}#mermaid-svg-06rhIOIcLcLbZZN0 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-06rhIOIcLcLbZZN0 .messageText{fill:#333;stroke:none;}#mermaid-svg-06rhIOIcLcLbZZN0 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-06rhIOIcLcLbZZN0 .labelText,#mermaid-svg-06rhIOIcLcLbZZN0 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-06rhIOIcLcLbZZN0 .loopText,#mermaid-svg-06rhIOIcLcLbZZN0 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-06rhIOIcLcLbZZN0 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-06rhIOIcLcLbZZN0 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-06rhIOIcLcLbZZN0 .noteText,#mermaid-svg-06rhIOIcLcLbZZN0 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-06rhIOIcLcLbZZN0 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-06rhIOIcLcLbZZN0 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-06rhIOIcLcLbZZN0 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-06rhIOIcLcLbZZN0 .actorPopupMenu{position:absolute;}#mermaid-svg-06rhIOIcLcLbZZN0 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-06rhIOIcLcLbZZN0 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-06rhIOIcLcLbZZN0 .actor-man circle,#mermaid-svg-06rhIOIcLcLbZZN0 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-06rhIOIcLcLbZZN0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt 拦截 放行 alt 缓存命中 缓存未命中 / 动态 静态资源 / 动态回源 直接返回 回源请求 SQL注入/XSS/CC 检测 403/挑战页 转发 HTTP + X-Forwarded-* 反向代理 响应(经原路或 CDN 缓存)
28.2 职责划分
| 层级 | 典型职责 | Nginx 是否应重复做 |
|---|---|---|
| CDN | 静态加速、边缘缓存、DDoS 基础防护 | 静态 expires 可配合,不必重复缓存 |
| WAF | Web 攻击防护、Bot 管理、CC 高级防护 | 基础 limit_req 作兜底,不替代 WAF |
| SLB | 四层/七层负载、健康检查、SSL 终结(可选) | upstream 健康检查与之互补 |
| Nginx | 路由、限流兜底、Header 治理、应用反代 | 源站最后一道接入 |
28.3 源站只接受 CDN/WAF 回源 IP(安全基线)
禁止公网直连源站,仅允许 CDN/WAF 回源 IP 段访问 80/443:
nginx
# 在 http 或 server 块最前面
# 阿里云 CDN 回源 IP 段从控制台导出;以下为示例
geo $is_trusted_source {
default 0;
10.0.0.0/8 1; # 内网 SLB
223.5.5.0/24 1; # CDN 回源 IP 段(示例,以厂商文档为准)
47.93.0.0/16 1; # WAF 回源 IP 段(示例)
}
server {
listen 443 ssl;
if ($is_trusted_source = 0) {
return 403;
}
# ... 业务配置
}
或使用防火墙(更推荐,在 Nginx 之前拦截):
bash
# iptables:仅允许 CDN 回源 IP + 内网 SLB
iptables -A INPUT -p tcp --dport 443 -s 223.5.5.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j DROP
CDN/WAF 回源 IP 会变更,需订阅厂商公告或 API 同步,定期更新白名单。
28.4 真实 IP 传递(与第 17 章联动)
| 厂商 | 回源 Header | Nginx 配置 |
|---|---|---|
| 阿里云 CDN | Ali-CDN-Real-IP / X-Forwarded-For |
real_ip_header X-Forwarded-For |
| 腾讯云 CDN | X-Forwarded-For |
同上 |
| Cloudflare | CF-Connecting-IP |
real_ip_header CF-Connecting-IP |
| 通用 WAF | X-Forwarded-For |
set_real_ip_from WAF 回源 IP 段 |
nginx
# Cloudflare 示例
set_real_ip_from 173.245.48.0/20;
# ... 其他 CF IP 段见官方文档
real_ip_header CF-Connecting-IP;
28.5 HTTPS 终结层级
| 模式 | 用户侧 | CDN→源站 | Nginx 侧 |
|---|---|---|---|
| 全链路 HTTPS | HTTPS | HTTPS | listen 443 ssl |
| CDN 终结(常见) | HTTPS | HTTP | listen 80 + 信任 X-Forwarded-Proto |
| 双重 HTTPS | HTTPS | HTTPS | 源站也配 SSL(更安全,略增源站负载) |
nginx
# CDN 终结 SSL 时,源站 Nginx 判断跳转
map $http_x_forwarded_proto $redirect_to_https {
default "";
"http" "https://$host$request_uri";
}
# 通常 CDN 已做跳转,源站无需再 301
28.6 缓存协作
CDN 缓存静态,源站 Nginx 职责:
nginx
# 静态:长缓存 + 版本号/hash 文件名
location /static/ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
add_header X-Cache-Status "ORIGIN"; # 便于排查
}
# 动态 API:禁止 CDN 缓存
location /api/ {
add_header Cache-Control "no-store, no-cache, must-revalidate";
add_header Pragma "no-cache";
proxy_pass http://backend;
}
CDN 刷新 / 预热:
| 操作 | 场景 | 说明 |
|---|---|---|
| URL 刷新 | 单个页面更新 | CDN 控制台/API |
| 目录刷新 | 整站静态发版 | 刷新 /static/ |
| 预热 | 大促前 | 提前灌入热点 URL |
Cache-Control 变更 |
发版后仍命中旧缓存 | 检查 CDN 是否「忽略源站 Cache-Control」 |
28.7 WAF 协作与误拦排查
WAF 拦截后用户看到 403/405/验证码,源站 Nginx 无 access 日志 → 问题在 WAF/CDN 层,不在 Nginx。
排查步骤:
- CDN/WAF 控制台查拦截日志(规则 ID、特征)
- 对比正常请求与拦截请求的 Header/Body/URI
- 临时加白(指定 URL/IP/规则 ID)验证
- 调整规则灵敏度或排除业务接口(如大 POST、特殊 Content-Type)
Nginx 侧避免误触发 WAF:
nginx
# 避免异常大的 Header(WAF 可能判为攻击)
large_client_header_buffers 4 16k;
# 避免 URI 中异常字符(按业务需要)
# 若 WAF 已拦,源站不必重复
28.8 协作运维 Checklist
| 变更类型 | CDN | WAF | Nginx 源站 |
|---|---|---|---|
| 新域名上线 | 添加加速域名、回源 Host | 接入防护、绑定域名 | server_name + 证书 |
| SSL 证书更新 | CDN 侧证书(用户可见) | --- | 源站证书(回源 HTTPS 时) |
| 源站 IP 变更 | 改回源地址 | 改回源地址 | 更新防火墙白名单 |
| 发版静态资源 | 刷新/预热 CDN | --- | 确认 Cache-Control |
| 502 排障 | 查 CDN 回源失败日志 | 查是否误拦 | 查 upstream/error.log |
| 大促保障 | 预热 + 放宽带宽 | 调高 CC 阈值 | 扩容 + limit_req 兜底 |
28.9 典型故障
| 现象 | 可能层级 | 处理 |
|---|---|---|
| 全国用户 403,源站无日志 | WAF/CDN 拦截 | WAF 白名单/规则调整 |
| 部分地区慢 | CDN 节点 | CDN 预热、切换线路 |
| 源站 IP 暴露被 CC | 安全 | 防火墙仅允许回源 IP;隐藏 DNS 直连 |
| HTTPS 混合内容 | CDN/源站配置 | 检查 X-Forwarded-Proto、资源 URL 协议 |
| 刷新 CDN 仍旧内容 | 缓存 Key | 检查是否按 Query/Header 缓存;Purge 是否成功 |
| 仅 CDN 用户 502 | 回源链路 | CDN 回源 Host/端口/协议是否与 Nginx 一致 |
附录:原文优化对照摘要
| 原内容 | 修正/补充 |
|---|---|
-s reopen 写为「重新打开配置文件」 |
应为「重新打开日志文件」 |
| 正向代理示例用了 upstream + proxy_cache | 实为反向代理,已分节重写 |
multi_accept off 注释为「批量建立连接」 |
应为 multi_accept on 才批量 accept |
fair / url_hash 作为内置策略 |
标注为第三方模块,开源版不含 |
| 平滑升级步骤 8~9 对 oldbin 发 HUP/QUIT | 通常只需 USR2 + WINCH |
加模块前 nginx stop |
改为 USR2 平滑升级,避免中断 |
logrotate dateformat %Y%m%d%s |
修正为 -%Y%m%d |
HTTP→HTTPS 仅用 $scheme |
上层代理场景需读 X-Forwarded-Proto 等 Header |
| 缺少 502/504/499 排障、限流、日志格式、SOP | 已补充完整生产章节 |
| 缺少 real_ip、systemd、磁盘巡检、回滚、监控 | 已补充第 17~25 章 |
| 缺少高可用、K8s Ingress、WAF/CDN 协作 | 已补充第 26~28 章 |
| 预热 | 大促前 | 提前灌入热点 URL |
| Cache-Control 变更 | 发版后仍命中旧缓存 | 检查 CDN 是否「忽略源站 Cache-Control」 |
28.7 WAF 协作与误拦排查
WAF 拦截后用户看到 403/405/验证码,源站 Nginx 无 access 日志 → 问题在 WAF/CDN 层,不在 Nginx。
排查步骤:
- CDN/WAF 控制台查拦截日志(规则 ID、特征)
- 对比正常请求与拦截请求的 Header/Body/URI
- 临时加白(指定 URL/IP/规则 ID)验证
- 调整规则灵敏度或排除业务接口(如大 POST、特殊 Content-Type)
Nginx 侧避免误触发 WAF:
nginx
# 避免异常大的 Header(WAF 可能判为攻击)
large_client_header_buffers 4 16k;
# 避免 URI 中异常字符(按业务需要)
# 若 WAF 已拦,源站不必重复
28.8 协作运维 Checklist
| 变更类型 | CDN | WAF | Nginx 源站 |
|---|---|---|---|
| 新域名上线 | 添加加速域名、回源 Host | 接入防护、绑定域名 | server_name + 证书 |
| SSL 证书更新 | CDN 侧证书(用户可见) | --- | 源站证书(回源 HTTPS 时) |
| 源站 IP 变更 | 改回源地址 | 改回源地址 | 更新防火墙白名单 |
| 发版静态资源 | 刷新/预热 CDN | --- | 确认 Cache-Control |
| 502 排障 | 查 CDN 回源失败日志 | 查是否误拦 | 查 upstream/error.log |
| 大促保障 | 预热 + 放宽带宽 | 调高 CC 阈值 | 扩容 + limit_req 兜底 |
28.9 典型故障
| 现象 | 可能层级 | 处理 |
|---|---|---|
| 全国用户 403,源站无日志 | WAF/CDN 拦截 | WAF 白名单/规则调整 |
| 部分地区慢 | CDN 节点 | CDN 预热、切换线路 |
| 源站 IP 暴露被 CC | 安全 | 防火墙仅允许回源 IP;隐藏 DNS 直连 |
| HTTPS 混合内容 | CDN/源站配置 | 检查 X-Forwarded-Proto、资源 URL 协议 |
| 刷新 CDN 仍旧内容 | 缓存 Key | 检查是否按 Query/Header 缓存;Purge 是否成功 |
| 仅 CDN 用户 502 | 回源链路 | CDN 回源 Host/端口/协议是否与 Nginx 一致 |