NGINX 不只是一个静态 Web 服务器。它还可以承担:
静态资源服务器;
HTTPS 终止;
HTTP/HTTPS 反向代理;
WebSocket 和 gRPC 代理;
HTTP、TCP、UDP 负载均衡;
内容缓存;
限流与连接控制;
多域名虚拟主机;
内部统一入口;
应用节点故障摘除;
高可用入口集群。
在单机环境中,启动一个 NGINX 并不困难:
1 2 dnf install -y nginx systemctl enable --now nginx
真正困难的是把它变成一个可持续运行的生产基础设施:
1 2 3 4 5 6 7 配置可验证 -> 发布可回滚 -> 上游故障可降级 -> NGINX 进程可自愈 -> 入口节点可漂移 -> 日志和指标可观测 -> 容量和内核参数经过验证
本文基于 Rocky Linux 10,使用系统仓库中的 NGINX 1.26.3 作为原生部署基线;Docker 部分使用 NGINX 官方稳定镜像,并固定到明确版本。内容覆盖动静分离、反向代理、负载均衡、健康监测、Keepalived 集群、调优和容器化部署。
截至 2026 年 7 月 31 日,Rocky Linux 10 AppStream 仓库中的 NGINX 基线为 1.26.3;NGINX 官方当前稳定分支为 1.30.x。系统 RPM 与 Docker 镜像不必强行使用相同版本,但都必须固定并经过测试,不要在生产环境长期追踪 latest。
一、整体架构 本文示例环境:
角色
主机名
IP
作用
NGINX 1
nginx01.example.com
192.168.9.10
主入口节点
NGINX 2
nginx02.example.com
192.168.9.11
备用入口节点
VIP
app.example.com
192.168.9.100
客户端访问地址
Backend 1
app01.example.com
192.168.9.31:8080
Java/业务服务
Backend 2
app02.example.com
192.168.9.32:8080
Java/业务服务
Backend 3
app03.example.com
192.168.9.33:8080
备用业务服务
flowchart LR
Client[浏览器/客户端] -->|HTTPS 443| VIP[VIP 192.168.9.100]
VIP --> N1[NGINX 01]
VIP -.故障切换.-> N2[NGINX 02]
N1 --> Static1[本地静态资源]
N2 --> Static2[本地静态资源]
N1 --> B1[Backend 1]
N1 --> B2[Backend 2]
N1 --> B3[Backend 3 Backup]
N2 --> B1
N2 --> B2
N2 --> B3
K1[Keepalived] --- N1
K2[Keepalived] --- N2
这里需要区分两层“负载均衡”:
1 2 3 4 5 6 Keepalived: 负责 NGINX 入口节点高可用 默认是 Active/Standby,不会同时分担两台 NGINX 的流量 NGINX upstream: 负责多个后端应用之间的请求分发
需要两台 NGINX 同时处理流量时,应在它们前面使用:
云负载均衡;
硬件负载均衡;
LVS/IPVS;
BGP/ECMP;
Anycast;
DNS 多地址与健康探测。
二、安装前的系统准备 检查系统:
1 2 3 4 cat /etc/rocky-releaseuname -runame -m hostnamectl
设置主机名:
1 hostnamectl set-hostname nginx01.example.com
同步时间:
1 2 3 timedatectl set-timezone Asia/Shanghai systemctl enable --now chronyd chronyc tracking
更新系统:
1 2 3 dnf clean all dnf makecache dnf upgrade -y
安装基础工具:
1 2 3 4 5 6 7 8 9 10 11 12 dnf install -y \ vim \ curl \ wget \ jq \ tar \ unzip \ bind-utils \ lsof \ tcpdump \ policycoreutils-python-utils \ firewalld
确认 SELinux:
生产环境不要为了省事直接关闭 SELinux。
三、安装 NGINX 3.1 使用 Rocky Linux 10 官方仓库 查看包:
1 2 dnf info nginx dnf list --showduplicates nginx
安装:
如果需要 TCP/UDP Stream 代理:
1 dnf install -y nginx-mod-stream
查看版本和编译参数:
1 2 3 nginx -v nginx -V rpm -q nginx nginx-core nginx-mod-stream
检查模块:
1 2 3 nginx -V 2>&1 | tr ' ' '\n' | sort ls -lah /usr/share/nginx/modulesls -lah /usr/share/nginx/modules-enabled 2>/dev/null || true
3.2 启动服务 1 systemctl enable --now nginx
检查:
1 2 3 4 5 6 systemctl status nginx --no-pager -l systemctl is-enabled nginx systemctl is-active nginx ss -lntp | grep nginx curl -I http://127.0.0.1/
查看 systemd Unit:
3.3 firewalld 1 2 3 4 5 6 7 systemctl enable --now firewalld firewall-cmd --permanent --add-service=http firewall-cmd --permanent --add-service=https firewall-cmd --reload firewall-cmd --list-all
3.4 主要目录
路径
用途
/etc/nginx/nginx.conf
主配置
/etc/nginx/conf.d/*.conf
虚拟主机和业务配置
/etc/nginx/default.d/*.conf
默认 Server 扩展配置
/usr/share/nginx/html
默认静态目录
/var/log/nginx/access.log
访问日志
/var/log/nginx/error.log
错误日志
/run/nginx.pid
Master PID
/usr/share/nginx/modules/*.conf
动态模块加载配置
推荐将业务配置拆分:
1 2 3 4 5 6 7 8 9 10 11 /etc/nginx/ ├── nginx.conf ├── conf.d/ │ ├── 00-upstreams.conf │ ├── 10-status.conf │ ├── app.example.com.conf │ └── stream.conf └── snippets/ ├── proxy-common.conf ├── ssl-common.conf └── security-headers.conf
不要把所有域名、upstream、TLS 和缓存规则堆进一个几千行的 nginx.conf。
四、配置测试与平滑发布 每次修改后先执行:
查看完整展开配置:
只有测试通过后才 Reload:
或者:
推荐使用 systemd:
1 nginx -t && systemctl reload nginx
Reload 的流程:
sequenceDiagram
participant Admin as 运维
participant Master as NGINX Master
participant Old as Old Workers
participant New as New Workers
Admin->>Master: reload
Master->>Master: 重新读取并校验配置
Master->>New: 启动新 Worker
Master->>Old: 请求优雅退出
Old->>Old: 完成现有连接
Old-->>Master: 退出
Reload 不等于重启:
新请求由新 Worker 处理;
旧 Worker 尽量完成已有请求;
长连接可能让旧 Worker 保留较长时间;
配置语法错误时 Reload 不会应用新配置。
生产发布流程至少应是:
1 2 3 4 nginx -tcp -a /etc/nginx "/backup/nginx-$(date +%F-%H%M%S) " systemctl reload nginx curl -fsS http://127.0.0.1:8088/healthz
五、推荐的主配置 备份:
1 2 3 cp -a \ /etc/nginx/nginx.conf \ "/etc/nginx/nginx.conf.bak.$(date +%F-%H%M%S) "
编辑 /etc/nginx/nginx.conf:
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 user nginx;worker_processes auto;worker_rlimit_nofile 131072 ;error_log /var/log/nginx/error .log warn ;pid /run/nginx.pid;include /usr/share/nginx/modules/*.conf ;events { worker_connections 8192 ; }http { log_format main '$remote_addr - $remote_user [$time_local ] "$request " ' '$status $body_bytes_sent "$http_referer " ' '"$http_user_agent " "$http_x_forwarded_for " ' 'request_time=$request_time ' 'upstream_addr=$upstream_addr ' 'upstream_status=$upstream_status ' 'upstream_connect_time=$upstream_connect_time ' 'upstream_header_time=$upstream_header_time ' 'upstream_response_time=$upstream_response_time ' 'request_id=$request_id ' ; access_log /var/log/nginx/access.log main; include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on ; tcp_nopush on ; keepalive_timeout 65 ; keepalive_requests 10000 ; server_tokens off ; client_max_body_size 50m ; client_body_timeout 30s ; client_header_timeout 15s ; send_timeout 60s ; types_hash_max_size 4096 ; open_file_cache max=10000 inactive=60s ; open_file_cache_valid 120s ; open_file_cache_min_uses 2 ; open_file_cache_errors on ; gzip on ; gzip_vary on ; gzip_comp_level 5 ; gzip_min_length 1024 ; gzip_types text/plain text/css text/xml application/json application/javascript application/xml application/rss+xml image/svg+xml; map $http_upgrade $connection_upgrade { default upgrade; '' close; } limit_req_zone $binary_remote_addr zone=api_rate:20m rate=20r/s; limit_conn_zone $binary_remote_addr zone=per_ip_conn:20m ; include /etc/nginx/conf.d/*.conf ; }
5.1 worker_processes
通常会按可用 CPU 数量创建 Worker。
不要直接写:
如果机器只有 8 核,额外 Worker 只会增加调度和内存开销。
5.2 worker_connections 理论客户端连接上限并不简单等于:
1 worker_processes × worker_connections
反向代理请求通常同时占用:
还受到:
文件句柄;
upstream keepalive;
HTTP/2 多路复用;
长连接;
WebSocket;
系统端口;
内存;
上游容量;
共同限制。
5.3 gzip 适合压缩:
1 2 3 4 5 6 HTML CSS JavaScript JSON XML SVG
不建议重复压缩:
1 2 3 4 5 6 7 8 JPEG PNG WebP MP4 ZIP Gzip Brotli RPM
已经压缩的文件再压一遍,通常只会多烧 CPU,文件大小却像拒绝加班的员工一样基本不动。
六、静态资源服务器 6.1 创建目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 mkdir -p /srv/www/app/releases/20260731mkdir -p /srv/www/app/shared/assetscat > /srv/www/app/releases/20260731/index.html <<'EOF' <!doctype html> <html lang="zh-CN" > <head > <meta charset="UTF-8" > <title>Example Application</title> </head> <body> <h1>Rocky Linux 10 + NGINX</h1> </body> </html> EOFln -sfn \ /srv/www/app/releases/20260731 \ /srv/www/app/current
权限:
1 2 3 chown -R root:nginx /srv/www/app find /srv/www/app -type d -exec chmod 0755 {} \; find /srv/www/app -type f -exec chmod 0644 {} \;
6.2 SELinux Context 自定义静态目录要设置持久化 Label:
1 2 3 4 5 6 semanage fcontext \ -a \ -t httpd_sys_content_t \ '/srv/www/app(/.*)?' restorecon -Rv /srv/www/app
检查:
1 2 ls -ldZ /srv/www/appls -lZ /srv/www/app/current
不要长期使用:
chcon 的修改可能在 restorecon 或重新标记后丢失。
6.3 静态站点配置 创建 /etc/nginx/conf.d/app.example.com.conf:
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 server { listen 80 ; server_name app.example.com; root /srv/www/app/current; index index.html; location = /nginx-health { access_log off ; default_type text/plain; return 200 "ok\n" ; } location /assets/ { alias /srv/www/app/shared/assets/; try_files $uri =404 ; expires 30d ; add_header Cache-Control "public, max-age=2592000, immutable" ; access_log off ; } location / { try_files $uri $uri / /index.html; } location ~* \.html$ { expires -1 ; add_header Cache-Control "no-cache, no-store, must-revalidate" ; } }
验证:
1 2 3 4 5 6 7 8 nginx -t systemctl reload nginx curl -H 'Host: app.example.com' \ http://127.0.0.1/ curl -H 'Host: app.example.com' \ http://127.0.0.1/nginx-health
6.4 root 与 alias 的区别 root 会把完整 URI 拼到目录后:
1 2 3 location /assets/ { root /srv/www/app; }
请求:
实际文件:
1 /srv/www/app/assets/logo.svg
alias 会用指定目录替换匹配到的 Location:
1 2 3 location /assets/ { alias /srv/www/app/shared/assets/; }
请求:
实际文件:
1 /srv/www/app/shared/assets/logo.svg
使用 alias 时,Location 和目录末尾的斜杠要保持一致,否则很容易制造出“文件明明存在但 NGINX 就是说 404”的玄学现场。
七、前后端动静分离 动静分离的典型职责:
1 2 3 4 5 6 7 8 /index.html、/assets/*: 由 NGINX 本地直接返回 /api/*: 代理到 Java、Go、Node.js 等后端 /ws/*: 代理 WebSocket
flowchart TD
Request[客户端请求] --> N[NGINX]
N -->|/ /assets| Static[静态文件]
N -->|/api| Backend[业务服务]
N -->|/ws| WS[WebSocket 服务]
7.1 配置 upstream 创建 /etc/nginx/conf.d/00-upstreams.conf:
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 upstream app_backend { zone app_backend 64k ; least_conn; server 192.168.9.31:8080 weight=5 max_conns=200 max_fails=3 fail_timeout=10s ; server 192.168.9.32:8080 weight=5 max_conns=200 max_fails=3 fail_timeout=10s ; server 192.168.9.33:8080 backup max_fails=2 fail_timeout=10s ; keepalive 64 ; keepalive_requests 1000 ; keepalive_timeout 60s ; }
7.2 代理公共参数 创建目录:
1 mkdir -p /etc/nginx/snippets
创建 /etc/nginx/snippets/proxy-common.conf:
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 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-Host $host ;proxy_set_header X-Forwarded-Proto $scheme ;proxy_set_header X-Forwarded-Port $server_port ;proxy_set_header X-Request-ID $request_id ;proxy_set_header Connection "" ;proxy_connect_timeout 3s ;proxy_send_timeout 60s ;proxy_read_timeout 60s ;proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;proxy_next_upstream_tries 3 ;proxy_next_upstream_timeout 10s ;proxy_buffering on ;
7.3 完整动静分离配置 更新 /etc/nginx/conf.d/app.example.com.conf:
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 server { listen 80 ; server_name app.example.com; root /srv/www/app/current; index index.html; location = /nginx-health { access_log off ; default_type text/plain; return 200 "ok\n" ; } location /assets/ { alias /srv/www/app/shared/assets/; try_files $uri =404 ; expires 30d ; add_header Cache-Control "public, max-age=2592000, immutable" ; access_log off ; } location /api/ { limit_req zone=api_rate burst=40 nodelay; limit_conn per_ip_conn 50 ; include /etc/nginx/snippets/proxy-common.conf; proxy_pass http://app_backend; } location /ws/ { proxy_http_version 1 .1 ; proxy_set_header Upgrade $http_upgrade ; proxy_set_header Connection $connection_upgrade ; 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_connect_timeout 3s ; proxy_read_timeout 3600s ; proxy_send_timeout 3600s ; proxy_pass http://app_backend; } location / { try_files $uri $uri / /index.html; } location ~* \.html$ { expires -1 ; add_header Cache-Control "no-cache, no-store, must-revalidate" ; } }
7.4 proxy_pass 末尾斜杠 以下配置:
1 2 3 location /api/ { proxy_pass http://app_backend; }
请求:
上游收到:
以下配置:
1 2 3 location /api/ { proxy_pass http://app_backend/; }
上游收到:
一个斜杠就能决定后端收到什么 URI。它个头不大,但制造 404 的能力相当成熟。
7.5 上传和流式接口 大文件上传:
1 2 3 4 5 6 7 8 location /api/upload/ { include /etc/nginx/snippets/proxy-common.conf; client_max_body_size 2g ; proxy_request_buffering off ; proxy_pass http://app_backend; }
关闭 Request Buffering 后,NGINX 会更早将请求体发送给上游,但一旦开始发送,请求通常不能安全切换到下一个上游。
SSE 或流式响应:
1 2 3 4 5 6 7 8 9 location /api/stream/ { include /etc/nginx/snippets/proxy-common.conf; proxy_buffering off ; proxy_cache off ; proxy_read_timeout 1h ; proxy_pass http://app_backend; }
不要对所有接口无脑关闭 Buffering。普通 JSON 接口开启 Buffering 往往能降低慢客户端对后端连接的长期占用。
八、反向代理 HTTPS 上游 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 location /partner-api/ { proxy_http_version 1 .1 ; proxy_set_header Host partner.internal.example.com; proxy_set_header Connection "" ; proxy_ssl_server_name on ; proxy_ssl_name partner.internal.example.com; proxy_ssl_verify on ; proxy_ssl_trusted_certificate /etc/pki/ca-trust/source/anchors/company-root-ca.crt; proxy_ssl_verify_depth 3 ; proxy_connect_timeout 3s ; proxy_read_timeout 60s ; proxy_pass https://partner.internal.example.com/; }
不要使用:
长期绕过上游证书校验。修好证书链、SAN、DNS 和内部 CA 才是正路。
九、gRPC 反向代理 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 upstream grpc_backend { server 192.168.9.31:9090 max_fails=3 fail_timeout=10s ; server 192.168.9.32:9090 max_fails=3 fail_timeout=10s ; }server { listen 443 ssl; http2 on ; server_name grpc.example.com; ssl_certificate /etc/nginx/ssl/grpc.example.com.crt; ssl_certificate_key /etc/nginx/ssl/grpc.example.com.key; location / { grpc_set_header Host $host ; grpc_set_header X-Real-IP $remote_addr ; grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; grpc_connect_timeout 3s ; grpc_read_timeout 60s ; grpc_send_timeout 60s ; grpc_pass grpc://grpc_backend; } }
后端启用 TLS 时使用:
1 grpc_pass grpcs://grpc_backend;
十、负载均衡算法 10.1 Round Robin 默认算法,不需要额外指令:
1 2 3 4 upstream backend { server 192.168.9.31:8080 ; server 192.168.9.32:8080 ; }
适合请求耗时比较均匀的无状态服务。
10.2 Weight 1 2 3 4 upstream backend { server 192.168.9.31:8080 weight=5 ; server 192.168.9.32:8080 weight=2 ; }
适合节点规格不同。
10.3 Least Connections 1 2 3 4 5 6 upstream backend { least_conn; server 192.168.9.31:8080 ; server 192.168.9.32:8080 ; }
适合请求耗时差异较大、长连接较多的服务。
10.4 IP Hash 1 2 3 4 5 6 upstream backend { ip_hash; server 192.168.9.31:8080 ; server 192.168.9.32:8080 ; }
可提供有限的客户端亲和性,但存在问题:
大量用户经过同一个 NAT;
CDN 或代理隐藏真实地址;
节点上下线造成映射变化;
后端故障后会重新分配;
不是可靠的 Session 高可用方案。
更推荐将 Session 放入 Redis、数据库或 Token 中,让业务服务真正无状态。
10.5 Consistent Hash 1 2 3 4 5 6 upstream backend { hash $cookie_tenant_id consistent; server 192.168.9.31:8080 ; server 192.168.9.32:8080 ; }
适合按租户、缓存 Key 或业务标识进行稳定分片。
10.6 Random Two Least Connections 1 2 3 4 5 6 7 8 upstream backend { random two least_conn; server 192.168.9.31:8080 ; server 192.168.9.32:8080 ; server 192.168.9.33:8080 ; server 192.168.9.34:8080 ; }
适合较大上游集群,先随机选择两个节点,再从中选择连接较少的节点。
10.7 Backup 1 2 3 4 5 upstream backend { server 192.168.9.31:8080 ; server 192.168.9.32:8080 ; server 192.168.9.33:8080 backup; }
Backup 只在普通节点都不可用时使用,不参与正常负载分配。
十一、上游连接复用 1 2 3 4 5 6 7 8 upstream app_backend { server 192.168.9.31:8080 ; server 192.168.9.32:8080 ; keepalive 64 ; keepalive_requests 1000 ; keepalive_timeout 60s ; }
Rocky Linux 10 的 NGINX 1.26.3 中,还需要明确:
1 2 proxy_http_version 1 .1 ;proxy_set_header Connection "" ;
keepalive 64 表示每个 Worker 保存的空闲上游连接上限,不是上游总连接上限,也不会阻止 Worker 创建更多活跃连接。
配置过大可能导致:
上游保持大量空闲连接;
新客户端无法建立连接;
后端文件句柄耗尽;
每个 NGINX Worker 都保存一份连接池。
应结合后端线程池、连接上限和 NGINX Worker 数量计算。
十二、健康检查的能力边界 NGINX Open Source 原生提供的是被动健康检查 :
1 2 3 4 真实请求访问上游 -> 连接失败、超时或返回指定错误 -> NGINX 记录失败 -> 在 fail_timeout 内暂时避免选择该节点
配置:
1 2 3 4 5 6 7 8 9 upstream app_backend { server 192.168.9.31:8080 max_fails=3 fail_timeout=10s ; server 192.168.9.32:8080 max_fails=3 fail_timeout=10s ; }
含义:
在 fail_timeout=10s 窗口内累计 3 次失败;
节点在接下来约 10 秒内被视为不可用;
后续会重新尝试该节点。
需要配合:
1 2 3 4 5 6 7 proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
12.1 单节点 upstream 的陷阱 如果 upstream 中只有一个 Server:
1 2 3 upstream backend { server 192.168.9.31:8080 max_fails=3 fail_timeout=10s ; }
max_fails 和 fail_timeout 不会像多节点场景那样把唯一节点摘除,因为摘除后没有任何节点可选。
12.2 开源版没有原生周期性主动健康检查 以下指令:
属于 NGINX Plus 主动健康检查能力,不是 Rocky Linux 仓库中 NGINX Open Source 的标准能力。
不要把网上的 Plus 配置直接复制到开源版,否则:
1 nginx: [emerg] unknown directive "health_check"
开源版可以通过以下方式补齐不同层级的检查:
检查对象
推荐实现
NGINX 进程崩溃
systemd Restart=on-failure
NGINX 本地 HTTP 可用性
systemd Timer + curl
NGINX 集群节点
Keepalived vrrp_script
后端业务实例
应用编排平台、服务发现、外部监控或 NGINX Plus
整条用户链路
Prometheus Blackbox、Zabbix、Nagios、云监控等外部探测
本机健康检查不能代替外部监控。本机认为自己很健康,并不代表 DNS、VIP、交换机、防火墙和公网都同意。
十三、配置本地健康与状态端点 创建 /etc/nginx/conf.d/10-status.conf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 server { listen 127.0.0.1:8088 ; server_name localhost; access_log off ; location = /healthz { default_type text/plain; return 200 "ok\n" ; } location = /nginx-status { stub_status; allow 127.0.0.1 ; deny all; } }
验证模块:
1 nginx -V 2>&1 | grep -o -- '--with-http_stub_status_module'
应用:
1 2 nginx -t systemctl reload nginx
检查:
1 2 curl -fsS http://127.0.0.1:8088/healthz curl -fsS http://127.0.0.1:8088/nginx-status
stub_status 输出示例:
1 2 3 4 Active connections: 12 server accepts handled requests 10567 10567 20331 Reading: 0 Writing: 2 Waiting: 10
指标含义:
指标
说明
Active
当前活跃客户端连接
accepts
已接受的连接总数
handled
已处理连接总数
requests
已处理请求总数
Reading
正在读取请求头
Writing
正在向客户端返回响应
Waiting
Keepalive 空闲连接
如果:
可能存在连接处理失败或资源限制。
十四、systemd 进程自愈 Rocky Linux RPM 已提供 nginx.service,不要直接修改 /usr/lib/systemd/system/nginx.service,升级包时会被覆盖。
创建 Override:
写入:
1 2 3 4 5 6 7 8 9 [Unit] StartLimitIntervalSec =30 StartLimitBurst =5 [Service] Restart =on -failureRestartSec =2 sLimitNOFILE =131072 TasksMax =65536
应用:
1 2 systemctl daemon-reload systemctl restart nginx
检查:
1 2 3 4 5 6 systemctl cat nginx systemctl show nginx \ -p Restart \ -p RestartUSec \ -p LimitNOFILE \ -p TasksMax
14.1 为什么不是 Restart=always Restart=always 会在管理员执行正常停止后仍尝试拉起服务,可能干扰维护。
推荐:
它主要处理:
Master 进程异常退出;
OOM 或信号终止;
未预期崩溃。
它无法判断:
NGINX 进程活着但 HTTP 无响应;
配置代理到了错误后端;
TLS 证书过期;
VIP 不在本机;
后端全部返回业务错误。
因此还需要应用层检查。
十五、使用 systemd Service + Timer 监测 NGINX 15.1 健康检查脚本 创建 /usr/local/sbin/nginx-healthcheck.sh:
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 cat > /usr/local/sbin/nginx-healthcheck.sh <<'SCRIPT' set -Eeuo pipefail LOCK_FILE="/run/nginx-healthcheck.lock" FAIL_FILE="/run/nginx-healthcheck.failures" HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:8088/healthz} " MAX_FAILURES="${MAX_FAILURES:-3} " exec 9>"${LOCK_FILE} " if ! flock -n 9; then exit 0fi current_failures=0if [[ -r "${FAIL_FILE} " ]]; then read -r current_failures < "${FAIL_FILE} " || current_failures=0fi is_healthy () { systemctl is-active --quiet nginx \ && curl \ --fail \ --silent \ --show-error \ --max-time 2 \ "${HEALTH_URL} " \ | grep -qx 'ok' }if is_healthy; then printf '0\n' > "${FAIL_FILE} " exit 0fi current_failures=$((current_failures + 1 ))printf '%s\n' "${current_failures} " > "${FAIL_FILE} " logger \ -t nginx-healthcheck \ "NGINX health check failed: ${current_failures} /${MAX_FAILURES} " if (( current_failures < MAX_FAILURES )); then exit 0fi if ! /usr/sbin/nginx -t; then logger \ -p daemon.err \ -t nginx-healthcheck \ "NGINX configuration is invalid; refusing automatic restart" exit 1fi logger \ -p daemon.warning \ -t nginx-healthcheck \ "Restarting NGINX after ${current_failures} consecutive failures" systemctl restart nginxsleep 2if is_healthy; then printf '0\n' > "${FAIL_FILE} " logger \ -t nginx-healthcheck \ "NGINX recovered after restart" exit 0fi logger \ -p daemon.err \ -t nginx-healthcheck \ "NGINX is still unhealthy after restart" exit 1 SCRIPTchmod 0750 /usr/local/sbin/nginx-healthcheck.shchown root:root /usr/local/sbin/nginx-healthcheck.sh
该脚本连续失败 3 次才重启,避免一次瞬时抖动就触发“重启疗法”。
15.2 systemd Service 创建 /etc/systemd/system/nginx-healthcheck.service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [Unit] Description =NGINX application-level health checkAfter =network-on line.target nginx.serviceWants =network-on line.target[Service] Type =on eshotExecStart =/usr/local/sbin/nginx-healthcheck.shNoNewPrivileges =true PrivateTmp =true ProtectHome =true ProtectSystem =strictReadWritePaths =/run
15.3 systemd Timer 创建 /etc/systemd/system/nginx-healthcheck.timer:
1 2 3 4 5 6 7 8 9 10 11 12 [Unit] Description =Run NGINX health check periodically[Timer] OnBootSec =30 sOnUnitActiveSec =30 sAccuracySec =5 sRandomizedDelaySec =3 sUnit =nginx-healthcheck.service[Install] WantedBy =timers.target
启用:
1 2 systemctl daemon-reload systemctl enable --now nginx-healthcheck.timer
查看:
1 2 3 4 5 systemctl list-timers nginx-healthcheck.timer systemctl status nginx-healthcheck.timer journalctl -u nginx-healthcheck.service -n 100 --no-pager journalctl -t nginx-healthcheck -n 100 --no-pager
手工执行:
1 systemctl start nginx-healthcheck.service
15.4 自动重启的边界 自动重启应当谨慎:
只能在配置通过 nginx -t 时执行;
必须限制重启频率;
必须产生告警;
不能掩盖持续性故障;
不能用来修复错误业务配置;
不能代替外部探测。
不停重启并不叫高可用,那叫服务在原地做开合跳。
十六、TLS 配置 创建 /etc/nginx/snippets/ssl-common.conf:
1 2 3 4 5 6 7 ssl_protocols TLSv1.2 TLSv1.3 ;ssl_session_cache shared:SSL:50m ;ssl_session_timeout 1d ;ssl_session_tickets off ;ssl_prefer_server_ciphers off ;
创建 /etc/nginx/snippets/security-headers.conf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;add_header X-Content-Type-Options "nosniff" always;add_header Referrer-Policy "strict-origin-when-cross-origin" always;add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
HTTPS Server:
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 server { listen 80 ; server_name app.example.com; return 301 https://$host $request_uri ; }server { listen 443 ssl; http2 on ; server_name app.example.com; ssl_certificate /etc/nginx/ssl/app.example.com.fullchain.crt; ssl_certificate_key /etc/nginx/ssl/app.example.com.key; include /etc/nginx/snippets/ssl-common.conf; include /etc/nginx/snippets/security-headers.conf; root /srv/www/app/current; location = /nginx-health { access_log off ; default_type text/plain; return 200 "ok\n" ; } location /api/ { include /etc/nginx/snippets/proxy-common.conf; proxy_pass http://app_backend; } location / { try_files $uri $uri / /index.html; } }
证书权限:
1 2 3 4 5 6 chown -R root:nginx /etc/nginx/ssl find /etc/nginx/ssl -type d -exec chmod 0750 {} \; find /etc/nginx/ssl -type f -name '*.key' -exec chmod 0640 {} \; find /etc/nginx/ssl -type f ! -name '*.key' -exec chmod 0644 {} \; restorecon -Rv /etc/nginx/ssl
测试:
1 2 3 4 5 6 7 nginx -t systemctl reload nginx openssl s_client \ -connect app.example.com:443 \ -servername app.example.com \ -showcerts
HSTS 一旦带 includeSubDomains 发布,所有子域名都必须长期支持 HTTPS。不要在没有盘点子域名时盲目开启。
十七、SELinux 配置 17.1 允许反向代理连接网络 1 setsebool -P httpd_can_network_connect 1
查看:
1 getsebool httpd_can_network_connect
否则可能出现:
1 connect() failed (13: Permission denied) while connecting to upstream
17.2 自定义监听端口 如果 NGINX 要监听 8443:
1 2 3 4 5 semanage port \ -a \ -t http_port_t \ -p tcp \ 8443
如果已经存在:
1 2 3 4 5 semanage port \ -m \ -t http_port_t \ -p tcp \ 8443
查看:
1 semanage port -l | grep http_port_t
17.3 自定义缓存目录 1 2 3 4 5 6 7 8 mkdir -p /var/cache/nginx/proxy semanage fcontext \ -a \ -t httpd_cache_t \ '/var/cache/nginx(/.*)?' restorecon -Rv /var/cache/nginx
17.4 排查 AVC 1 2 ausearch -m AVC -ts recent journalctl -t setroubleshoot --since today
不要看到 Permission Denied 就先执行:
那会把真正的问题藏起来,并给未来留下一颗“这台机器为什么和别人不一样”的定时炸弹。
十八、代理缓存 定义缓存区,在 http 中加入:
1 2 3 4 5 6 7 proxy_cache_path \ /var/cache/nginx/proxy \ levels=1 :2 \ keys_zone=api_cache:100m \ inactive=60m \ max_size=10g \ use_temp_path=off ;
缓存公开 GET 接口:
1 2 3 4 5 6 7 8 9 map $http_authorization $skip_auth_cache { default 1 ; "" 0; }map $cookie_session $skip_session_cache { default 1 ; "" 0; }
Location:
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 location /public-api/ { include /etc/nginx/snippets/proxy-common.conf; proxy_cache api_cache; proxy_cache_methods GET HEAD; proxy_cache_key "$scheme $request_method $host $request_uri " ; proxy_cache_valid 200 5m ; proxy_cache_valid 301 302 1m ; proxy_cache_valid 404 30s ; proxy_cache_bypass $skip_auth_cache $skip_session_cache ; proxy_no_cache $skip_auth_cache $skip_session_cache $upstream_http_set_cookie ; proxy_cache_lock on ; proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; add_header X-Proxy-Cache $upstream_cache_status always; proxy_pass http://app_backend; }
不要缓存:
登录接口;
带 Authorization 的私有请求;
用户个人数据;
支付和订单写接口;
未明确 Cache Key 的多租户接口;
会返回 Set-Cookie 的动态页面。
缓存做错以后,性能可能很好,数据隔离也可能好到大家都能看到彼此的数据。
十九、限流与连接控制 19.1 请求速率 主配置:
1 2 3 4 limit_req_zone \ $binary_remote_addr \ zone=api_rate:20m \ rate=20r/s;
业务配置:
1 2 3 4 5 6 location /api/ { limit_req zone=api_rate burst=40 nodelay; include /etc/nginx/snippets/proxy-common.conf; proxy_pass http://app_backend; }
19.2 并发连接 主配置:
1 2 3 limit_conn_zone \ $binary_remote_addr \ zone=per_ip_conn:20m ;
业务配置:
1 limit_conn per_ip_conn 50 ;
19.3 登录接口 1 2 3 4 5 6 7 8 9 10 11 limit_req_zone \ $binary_remote_addr \ zone=login_rate:10m \ rate=1r/s;location = /api/login { limit_req zone=login_rate burst=5 nodelay; include /etc/nginx/snippets/proxy-common.conf; proxy_pass http://app_backend; }
IP 限流不能代替:
账号级失败次数;
验证码;
MFA;
WAF;
风险识别;
应用层幂等和熔断。
大量用户经过同一 NAT 时,IP 限流可能误伤整个办公室。
二十、日志调优 推荐记录:
1 2 3 4 5 6 7 request_time upstream_addr upstream_status upstream_connect_time upstream_header_time upstream_response_time request_id
判断瓶颈:
表现
可能原因
request_time 大,upstream_response_time 小
客户端慢、响应体大、下行网络慢
upstream_connect_time 大
上游连接拥塞、网络、端口或 SYN 问题
upstream_header_time 大
应用开始处理慢
upstream_response_time 大
应用整体耗时大
多个 upstream_addr
请求发生了重试
upstream_status 为 502, 200
第一个节点失败,第二个节点成功
20.1 日志轮转 检查:
1 cat /etc/logrotate.d/nginx
手工重新打开日志:
不要直接:
1 rm -f /var/log/nginx/access.log
进程可能仍持有已删除文件的 FD,磁盘空间不会立即释放。
查看:
20.2 高流量日志 高 QPS 场景可以:
使用 buffer= 缓冲访问日志;
将日志输出到本地高速盘;
异步采集到日志系统;
对健康检查关闭 Access Log;
对静态资源按需关闭日志;
不在同步远程日志挂载上直接写入。
示例:
1 2 3 4 5 access_log \ /var/log/nginx/access.log \ main \ buffer=256k \ flush=1s ;
二十一、系统与 NGINX 调优 21.1 文件句柄 systemd:
NGINX:
1 worker_rlimit_nofile 131072 ;
检查:
1 2 3 4 systemctl show nginx -p LimitNOFILE NGINX_MASTER_PID="$(cat /run/nginx.pid) " cat "/proc/${NGINX_MASTER_PID} /limits"
21.2 内核参数基线 创建 /etc/sysctl.d/99-nginx.conf:
1 2 3 4 5 6 fs.file-max = 2097152 net.core.somaxconn = 65535 net.ipv4.tcp_max_syn_backlog = 16384 net.ipv4.ip_local_port_range = 10240 65535
应用:
这些是起点,不是所有服务器的标准答案。
修改前查看:
1 2 3 4 sysctl fs.file-max sysctl net.core.somaxconn sysctl net.ipv4.tcp_max_syn_backlog sysctl net.ipv4.ip_local_port_range
21.3 出口临时端口 反向代理到上游时,NGINX 作为客户端使用临时端口。
如果没有 Keepalive、请求量很高或上游连接很短,可能出现:
1 cannot assign requested address
检查:
1 2 3 ss -s ss -tan state time-wait | wc -lcat /proc/sys/net/ipv4/ip_local_port_range
优先顺序:
启用合理 upstream keepalive;
检查上游是否主动关闭连接;
扩大临时端口范围;
增加 NGINX 出口 IP;
优化连接生命周期;
再评估其他 TCP 参数。
不要先复制 tcp_tw_recycle。这个参数早已退出历史舞台,而且它当年也没少给 NAT 用户制造惊喜。
21.4 backlog NGINX Listen:
1 listen 443 ssl backlog=8192 ;
系统:
1 2 net.core.somaxconn net.ipv4.tcp_max_syn_backlog
三者应结合压测调整。只改其中一个,不会凭空得到无限连接能力。
21.5 multi_accept multi_accept on 会让 Worker 一次接受尽可能多的新连接。
它不总是更快:
突发流量可能被一个 Worker 大量拿走;
增加单次事件循环处理时间;
当前默认行为通常已经足够。
除非压测证明 Accept 是瓶颈,否则保留默认。
21.6 sendfile 1 2 sendfile on ;tcp_nopush on ;
适合本地文件系统上的静态资源。
如果静态目录位于:
NFS;
FUSE;
某些分布式文件系统;
特殊加密挂载;
需要验证 sendfile 的兼容性和一致性。
21.7 CPU 与 Worker Affinity 一般使用:
不必手工配置 worker_cpu_affinity。
只有在以下场景才评估绑核:
高 QPS;
NUMA;
IRQ 和网卡队列经过设计;
有稳定压测;
已确认调度迁移是瓶颈。
21.8 Buffer 调优 不要把网上的巨大 Buffer 配置直接搬过来。
总内存近似受到:
1 2 3 并发请求 × 每请求 Buffer × Worker
影响。
先看:
1 2 3 4 5 响应头大小 响应体大小 是否需要落临时文件 上游延迟 客户端速度
再调整:
1 2 3 4 proxy_buffer_size proxy_buffers proxy_busy_buffers_size proxy_max_temp_file_size
二十二、性能验证 22.1 基础工具 1 2 3 4 5 6 dnf install -y \ httpd-tools \ wrk \ sysstat \ iotop \ perf
如果仓库没有 wrk,可使用受控的压测主机或容器。
22.2 ab 1 2 3 4 ab \ -n 10000 \ -c 200 \ http://app.example.com/nginx-health
22.3 wrk 1 2 3 4 5 6 wrk \ -t8 \ -c500 \ -d60s \ --latency \ http://app.example.com/api/ping
不要在生产高峰期直接压测。
22.4 观察指标 1 2 3 4 5 6 7 8 watch -n 1 'curl -s http://127.0.0.1:8088/nginx-status' pidstat -p "$(cat /run/nginx.pid) " 1 sar -n DEV 1 sar -n TCP,ETCP 1 ss -s iostat -xz 1 vmstat 1
测试应同时观察:
QPS;
P50、P95、P99;
错误率;
CPU;
SoftIRQ;
网络吞吐;
文件句柄;
上游连接;
临时端口;
后端延迟;
日志 I/O。
只看平均响应时间,通常会把最痛苦的那批用户平均没了。
二十三、使用 Keepalived 搭建 NGINX 高可用集群 23.1 工作方式 两台 NGINX 使用 Keepalived 运行 VRRP 选举,共享一个 VIP:
sequenceDiagram
participant C as Client
participant VIP as VIP 192.168.9.100
participant N1 as NGINX 01 Priority 150
participant N2 as NGINX 02 Priority 100
C->>VIP: HTTPS Request
VIP->>N1: 正常由 NGINX 01 处理
N1--xN1: NGINX 健康检查失败
N1-->>N2: VRRP 优先级下降
N2->>N2: 获得 VIP
N2-->>C: 后续请求由 NGINX 02 处理
它解决的是:
1 2 3 单台 NGINX 进程故障 单台服务器故障 入口节点网络故障
它不自动解决:
1 2 3 4 5 6 交换机故障 整个网段故障 VIP 所在二层网络故障 后端全部故障 错误配置被同步到两台节点 证书同时过期
23.2 网络前提 Keepalived VIP 通常要求:
两台节点位于同一个二层网络;
网卡允许配置额外 IP;
网络允许 VRRP 或配置 Unicast VRRP;
上游交换机能够学习 VIP 对应的新 MAC;
安全组和防火墙允许 VRRP;
云平台允许 Floating IP 或辅助私有 IP 漂移。
很多公有云不支持传统 VRRP VIP。公有云中优先使用:
云负载均衡;
Floating IP API;
辅助私网 IP 漂移;
云路由切换;
DNS 健康检查。
23.3 安装 Keepalived 两台节点执行:
1 dnf install -y keepalived
检查:
1 2 3 keepalived --version rpm -q keepalived systemctl cat keepalived
23.4 NGINX 节点健康脚本 创建 /usr/local/sbin/check-nginx-vip.sh:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 cat > /usr/local/sbin/check-nginx-vip.sh <<'SCRIPT' set -Eeuo pipefail systemctl is-active --quiet nginx curl \ --fail \ --silent \ --max-time 1 \ http://127.0.0.1:8088/healthz \ | grep -qx 'ok' SCRIPTchown root:root /usr/local/sbin/check-nginx-vip.shchmod 0755 /usr/local/sbin/check-nginx-vip.sh
测试:
1 2 /usr/local/sbin/check-nginx-vip.shecho $?
不要让脚本本身做复杂修复。Keepalived 的职责是判断该节点是否还应持有 VIP,而不是把所有故障处理逻辑塞进一段 Shell。
23.5 NGINX 01 配置 确认网卡:
1 2 ip -br link ip -br address
假设接口为:
创建 /etc/keepalived/keepalived.conf:
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 global_defs { router_id NGINX_01 enable_script_security script_user root } vrrp_script chk_nginx { script "/usr/local/sbin/check-nginx-vip.sh" interval 2 timeout 2 fall 3 rise 2 weight -60 user root } vrrp_instance VI_NGINX { state BACKUP interface ens160 virtual_router_id 51 priority 150 advert_int 1 unicast_src_ip 192.168.9.10 unicast_peer { 192.168.9.11 } authentication { auth_type PASS auth_pass NgxVr51 } virtual_ipaddress { 192.168.9.100/24 dev ens160 } track_interface { ens160 } track_script { chk_nginx } }
23.6 NGINX 02 配置 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 global_defs { router_id NGINX_02 enable_script_security script_user root } vrrp_script chk_nginx { script "/usr/local/sbin/check-nginx-vip.sh" interval 2 timeout 2 fall 3 rise 2 weight -60 user root } vrrp_instance VI_NGINX { state BACKUP interface ens160 virtual_router_id 51 priority 100 advert_int 1 unicast_src_ip 192.168.9.11 unicast_peer { 192.168.9.10 } authentication { auth_type PASS auth_pass NgxVr51 } virtual_ipaddress { 192.168.9.100/24 dev ens160 } track_interface { ens160 } track_script { chk_nginx } }
两台都使用:
由 Priority 决定初始 Master,可以减少节点重启时某些强制 Master 行为带来的意外。
auth_pass 不是现代加密认证机制,只能作为基础识别。VRRP 网络仍需要通过隔离、ACL 和防火墙保护。
23.7 权重计算 正常:
1 2 NGINX 01:150 NGINX 02:100
NGINX 01 检查失败:
此时低于 NGINX 02 的 100,VIP 漂移到 NGINX 02。
如果 Weight 只配置为:
失败后的优先级仍然是 140,VIP 不会切换。
23.8 firewalld 放行 VRRP NGINX 01:
1 2 3 4 5 firewall-cmd \ --permanent \ --add-rich-rule='rule family="ipv4" source address="192.168.9.11/32" protocol value="vrrp" accept' firewall-cmd --reload
NGINX 02:
1 2 3 4 5 firewall-cmd \ --permanent \ --add-rich-rule='rule family="ipv4" source address="192.168.9.10/32" protocol value="vrrp" accept' firewall-cmd --reload
检查:
1 firewall-cmd --list-rich-rules
如果 firewalld 版本不识别 vrrp 名称,可根据当前系统语法允许 IP Protocol 112。
23.9 启动 Keepalived 1 systemctl enable --now keepalived
查看:
1 2 systemctl status keepalived --no-pager -l journalctl -u keepalived -n 200 --no-pager
检查 VIP:
1 ip address show dev ens160
在当前 Master 上应看到:
23.10 故障切换测试 在 NGINX 01:
观察:
1 watch -n 0.5 'ip -br address show ens160'
在 NGINX 02:
1 journalctl -u keepalived -f
客户端:
1 2 3 4 5 6 7 8 while true ; do date curl -sk \ -o /dev/null \ -w '%{http_code} %{remote_ip} %{time_total}\n' \ https://app.example.com/nginx-health sleep 1done
恢复:
23.11 是否自动抢回 VIP 当前配置中,NGINX 01 恢复后因为 Priority 更高,会重新成为 Master。
如果不希望恢复后立刻抢回,避免短时间内二次切换,可以使用 nopreempt 设计,但必须理解:
初始状态应使用 BACKUP;
高优先级节点恢复后不主动抢回;
只有当前 Master 失效才切换;
运维需要明确主节点长期可能不是 Priority 最高节点。
是否自动回切取决于业务对稳定性和主节点固定性的要求。
二十四、避免 Keepalived Split-Brain 如果两台节点互相收不到 VRRP 通告,可能同时认为自己是 Master,并同时持有 VIP。
典型原因:
防火墙阻断 VRRP;
Unicast Peer 写错;
VRID 不一致;
网卡配置错误;
网络单向中断;
交换机 ACL;
安全组不支持 VRRP。
检查两台节点:
1 ip address show dev ens160 | grep 192.168.9.100
VIP 不应同时存在于两台节点。
抓包:
1 2 tcpdump -ni ens160 \ 'ip proto 112'
Unicast 模式:
1 2 tcpdump -ni ens160 \ 'host 192.168.9.10 or host 192.168.9.11'
监控系统应告警:
1 2 3 4 VIP 同时存在于两个节点 VIP 在所有节点都不存在 Keepalived 进入 FAULT VRRP 报文长时间中断
二十五、集群配置同步 NGINX 集群最危险的状态之一:
1 2 两台机器都叫 NGINX 但配置、证书、静态资源和模块版本并不一样
推荐使用:
Git;
Ansible;
SaltStack;
Puppet;
RPM 包;
CI/CD 配置发布;
制品仓库中的版本化配置包。
25.1 发布原则
先更新备用节点;
执行 nginx -t;
Reload;
本机健康检查;
通过 VIP 或直连节点验证;
再更新当前主节点;
保留上一版本配置和静态资源;
证书在两台节点保持一致;
记录发布 Commit 与操作人。
25.2 简单同步脚本 在配置源机器创建 /usr/local/sbin/deploy-nginx-config.sh:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #!/usr/bin/env bash set -Eeuo pipefail TARGET_HOST="${1:?target host is required} " RELEASE_ID="$(date +%Y%m%d%H%M%S) " rsync \ -aH \ --delete \ --exclude='ssl/*.key' \ /etc/nginx/ \ "${TARGET_HOST} :/tmp/nginx-${RELEASE_ID} /" ssh "${TARGET_HOST} " \ "nginx -t -c /tmp/nginx-${RELEASE_ID} /nginx.conf" ssh "${TARGET_HOST} " \ "sudo rsync -aH --delete /tmp/nginx-${RELEASE_ID} / /etc/nginx/ \ && sudo nginx -t \ && sudo systemctl reload nginx \ && curl -fsS http://127.0.0.1:8088/healthz"
这只是基础示例。生产环境还应处理:
Sudo 权限;
原子目录切换;
私钥分发;
回滚;
审批;
Hash 校验;
并发锁;
审计日志。
不要使用:
1 rsync --delete /etc/nginx/
直接同时推到两台生产节点。配置错误也会高可用——高可用地一起坏。
二十六、Active/Active NGINX 集群 Keepalived 默认是 Active/Standby。
需要两台 NGINX 同时承担请求时:
flowchart LR
User[Client] --> LB[External L4 Load Balancer]
LB --> N1[NGINX 01]
LB --> N2[NGINX 02]
N1 --> Backend[Backend Cluster]
N2 --> Backend
可选方案:
26.1 云负载均衡 最省运维:
1 2 3 Cloud LB -> NGINX 01 -> NGINX 02
云 LB 负责:
节点主动健康检查;
多可用区;
VIP;
流量分配;
DDoS 基础能力;
节点摘除。
26.2 LVS/IPVS 适合自建机房高吞吐四层负载均衡。
26.3 DNS 轮询 1 2 app.example.com -> 192.168.9.10 app.example.com -> 192.168.9.11
局限:
客户端和递归 DNS 有缓存;
故障摘除不够实时;
客户端可能长期命中故障地址;
不能只靠低 TTL 解决全部问题。
26.4 BGP/ECMP/Anycast 适合大规模数据中心或多地域入口,需要成熟网络团队。
二十七、Keepalived 不适合的场景 以下场景应优先使用外部负载均衡:
NGINX 跨不同二层网络;
公有云禁止 VRRP;
需要多可用区;
需要 Active/Active;
需要应用层主动健康检查;
需要全球流量调度;
需要 DDoS 防护;
入口吞吐超过单节点能力;
VIP 漂移会受到网络安全策略限制。
二十八、TCP/UDP 代理 安装:
1 dnf install -y nginx-mod-stream
检查是否加载:
1 nginx -T 2>&1 | grep -i stream
创建 /etc/nginx/conf.d/stream.conf 是否生效取决于主配置如何 Include。更清晰的方式是在 /etc/nginx/nginx.conf 顶层、http 块之外加入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 stream { upstream mysql_backend { least_conn; server 192.168.9.41:3306 max_fails=3 fail_timeout=10s ; server 192.168.9.42:3306 backup; } server { listen 3307 ; proxy_connect_timeout 3s ; proxy_timeout 1h ; proxy_pass mysql_backend; } }
SELinux 自定义端口:
1 2 3 4 5 semanage port \ -a \ -t http_port_t \ -p tcp \ 3307
但代理数据库需要谨慎:
NGINX 不理解 SQL;
无法识别主从角色;
无法保证事务一致性;
不会自动进行数据库故障切换;
TCP 连接一旦建立不会在中途无损迁移。
数据库高可用应由数据库专用代理或集群组件处理。
二十九、备份和回滚 备份:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 mkdir -p /backup/nginx tar \ --xattrs \ --acls \ -czf "/backup/nginx/nginx-$(date +%F-%H%M%S) .tar.gz" \ /etc/nginx \ /srv/www/app \ /etc/systemd/system/nginx.service.d \ /etc/systemd/system/nginx-healthcheck.service \ /etc/systemd/system/nginx-healthcheck.timer \ /etc/keepalived \ /usr/local/sbin/check-nginx-vip.sh \ /usr/local/sbin/nginx-healthcheck.sh \ 2>/dev/null || true
回滚配置:
1 2 3 4 5 6 7 8 9 10 11 12 tar \ -xzf /backup/nginx/nginx-时间.tar.gz \ -C / restorecon -Rv /etc/nginx /srv/www/app /etc/keepalived systemctl daemon-reload nginx -t systemctl reload nginx curl -fsS http://127.0.0.1:8088/healthz
静态前端建议使用 Release 目录和软链接:
1 2 3 /srv/www/app/releases/20260731-01 /srv/www/app/releases/20260731-02 /srv/www/app/current -> releases/20260731-02
回滚:
1 2 3 4 5 ln -sfn \ /srv/www/app/releases/20260731-01 \ /srv/www/app/current nginx -t && systemctl reload nginx
纯静态目录切换通常不需要 Reload,但执行健康验证仍然必要。
三十、基于 Docker 搭建 NGINX Docker 方案分为两种:
1 2 3 4 5 6 7 8 单机实验: Docker Compose 启动 Edge NGINX 和两个 Backend 用于验证反向代理、负载均衡和健康检查 多主机生产: 每台 Rocky Linux 10 运行一个 NGINX 容器 宿主机 Keepalived 漂移 VIP 或前置云 LB/硬件 LB
不推荐在一个 Compose 项目里强行容器化 Keepalived,并通过:
1 2 3 network_mode: host CAP_NET_ADMIN CAP_NET_RAW
控制宿主机 VIP。这样做可以运行,但权限高、网络行为隐晦、排障复杂。Keepalived 更适合直接运行在 Rocky Linux 宿主机。
三十一、安装 Docker Engine 卸载可能冲突的软件包:
1 2 3 4 5 6 7 8 9 10 dnf remove -y \ docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine \ podman-docker
安装仓库管理工具:
1 dnf install -y dnf-plugins-core
添加 Docker 官方 RHEL 仓库:
1 2 3 dnf config-manager \ --add-repo \ https://download.docker.com/linux/rhel/docker-ce.repo
安装:
1 2 3 4 5 6 dnf install -y \ docker-ce \ docker-ce-cli \ containerd.io \ docker-buildx-plugin \ docker-compose-plugin
启动:
1 systemctl enable --now docker
验证:
1 2 3 docker version docker compose version docker info
不要开放未认证的:
它几乎等价于将宿主机 root 权限交给网络上的访问者。
三十二、Docker Compose 实验项目 32.1 目录结构 1 2 3 4 5 6 7 8 mkdir -p /opt/nginx-docker/edgemkdir -p /opt/nginx-docker/backend-amkdir -p /opt/nginx-docker/backend-bmkdir -p /opt/nginx-docker/staticmkdir -p /opt/nginx-docker/certsmkdir -p /opt/nginx-docker/cachecd /opt/nginx-docker
结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 /opt/nginx-docker/ ├── compose.yaml ├── edge/ │ ├── nginx.conf │ └── default.conf ├── backend-a/ │ └── index.html ├── backend-b/ │ └── index.html ├── static/ │ ├── index.html │ └── assets/ ├── certs/ └── cache/
32.2 Backend 页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 cat > backend-a/index.html <<'EOF' backend-a EOFcat > backend-b/index.html <<'EOF' backend-b EOFcat > static/index.html <<'EOF' <!doctype html> <html lang="zh-CN" > <head > <meta charset="UTF-8" > <title>Docker NGINX</title> </head> <body> <h1>Docker NGINX Static Page</h1> </body> </html> EOF
32.3 Edge 主配置 创建 edge/nginx.conf:
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 user nginx;worker_processes auto;error_log /var/log/nginx/error .log warn ;pid /var/run/nginx.pid;events { worker_connections 4096 ; }http { log_format main '$remote_addr [$time_local ] "$request " ' '$status $body_bytes_sent ' 'request_time=$request_time ' 'upstream_addr=$upstream_addr ' 'upstream_status=$upstream_status ' 'upstream_response_time=$upstream_response_time ' ; access_log /var/log/nginx/access.log main; include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on ; tcp_nopush on ; keepalive_timeout 65 ; keepalive_requests 10000 ; server_tokens off ; resolver 127.0.0.11 valid=10s ipv6=off ; include /etc/nginx/conf.d/*.conf ; }
Docker 内置 DNS 为:
NGINX 1.27.3 之后,开源版本的 upstream resolve 可动态更新后端域名对应的 IP。本文 Docker 使用稳定版 1.30.4,因此可以使用该能力。
Rocky Linux 10 原生 NGINX 1.26.3 不具备同样的开源动态 upstream Resolve 能力;原生部署的固定后端建议使用固定 IP、服务发现生成配置并 Reload,或升级到经过验证的新版本。
32.4 Edge 业务配置 创建 edge/default.conf:
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 upstream docker_backends { zone docker_backends 64k ; least_conn; server backend-a:80 resolve max_fails=3 fail_timeout=10s ; server backend-b:80 resolve max_fails=3 fail_timeout=10s ; keepalive 32 ; keepalive_requests 1000 ; keepalive_timeout 60s ; }server { listen 80 ; server_name _; root /usr/share/nginx/html; location = /healthz { access_log off ; default_type text/plain; return 200 "ok\n" ; } location = /nginx-status { access_log off ; stub_status; allow 127.0.0.1 ; allow 172.16.0.0 /12 ; deny all; } location /assets/ { try_files $uri =404 ; expires 30d ; add_header Cache-Control "public, max-age=2592000, immutable" ; } location /api/ { 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 X-Request-ID $request_id ; proxy_set_header Connection "" ; proxy_connect_timeout 3s ; proxy_send_timeout 60s ; proxy_read_timeout 60s ; proxy_next_upstream error timeout invalid_header http_502 http_503 http_504; proxy_next_upstream_tries 2 ; proxy_pass http://docker_backends/; } location / { try_files $uri $uri / /index.html; } }
32.5 compose.yaml 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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 services: backend-a: image: nginx:1.30.4-alpine container_name: nginx-backend-a restart: unless-stopped volumes: - ./backend-a:/usr/share/nginx/html:ro,Z networks: - nginx-backend healthcheck: test: - CMD-SHELL - wget -qO- http://127.0.0.1/ | grep -q backend-a interval: 10s timeout: 3s retries: 3 read_only: true tmpfs: - /var/cache/nginx - /var/run security_opt: - no -new-privileges:true mem_limit: 128m cpus: 0.5 backend-b: image: nginx:1.30.4-alpine container_name: nginx-backend-b restart: unless-stopped volumes: - ./backend-b:/usr/share/nginx/html:ro,Z networks: - nginx-backend healthcheck: test: - CMD-SHELL - wget -qO- http://127.0.0.1/ | grep -q backend-b interval: 10s timeout: 3s retries: 3 read_only: true tmpfs: - /var/cache/nginx - /var/run security_opt: - no -new-privileges:true mem_limit: 128m cpus: 0.5 edge: image: nginx:1.30.4-alpine container_name: nginx-edge restart: unless-stopped depends_on: backend-a: condition: service_healthy backend-b: condition: service_healthy ports: - "80:80" volumes: - ./edge/nginx.conf:/etc/nginx/nginx.conf:ro,Z - ./edge/default.conf:/etc/nginx/conf.d/default.conf:ro,Z - ./static:/usr/share/nginx/html:ro,Z - ./cache:/var/cache/nginx:Z networks: - nginx-frontend - nginx-backend healthcheck: test: - CMD-SHELL - nginx -t && wget -qO- http://127.0.0.1/healthz | grep -q ok interval: 15s timeout: 5s retries: 3 start_period: 10s security_opt: - no -new-privileges:true ulimits: nofile: soft: 65536 hard: 65536 mem_limit: 512m cpus: 2.0 networks: nginx-frontend: name: nginx-frontend nginx-backend: name: nginx-backend internal: true
32.6 启动 1 2 3 docker compose config docker compose pull docker compose up -d
查看:
1 2 docker compose ps docker compose logs -f edge
验证静态资源:
验证负载均衡:
1 2 3 4 for i in {1..10}; do curl -s http://127.0.0.1/api/ echo done
预期交替或根据连接数返回:
停止一个后端:
1 docker compose stop backend-a
再次请求:
1 2 3 4 for i in {1..5}; do curl -sS http://127.0.0.1/api/ echo done
恢复:
1 docker compose start backend-a
三十三、Docker 生产部署建议 33.1 固定镜像 不要长期使用:
应固定:
1 image: nginx:1.30.4-alpine
更严格的环境固定 Digest:
1 image: nginx@sha256:<digest>
33.2 配置进入镜像还是挂载 配置稳定、随版本发布:
1 2 3 4 5 FROM nginx:1.30 .4 -alpineCOPY nginx.conf /etc/nginx/nginx.conf COPY conf.d/ /etc/nginx/conf.d/ COPY static/ /usr/share/nginx/html/
优势:
镜像不可变;
可测试;
可回滚;
配置和版本绑定;
不依赖宿主机文件。
动态证书、环境差异和 Secret 可以挂载。
33.3 Dockerfile 1 2 3 4 5 6 7 FROM nginx:1.30 .4 -alpineCOPY nginx.conf /etc/nginx/nginx.conf COPY conf.d/ /etc/nginx/conf.d/ COPY static/ /usr/share/nginx/html/ RUN nginx -t
构建:
1 2 3 docker build \ -t registry.example.com/infra/nginx-edge:2026.07.31 \ .
推送:
1 2 docker push \ registry.example.com/infra/nginx-edge:2026.07.31
33.4 容器日志 官方镜像默认将 Access 和 Error Log 链接到 stdout/stderr,适合 Docker 日志采集。
配置 Docker 日志轮转:
1 2 3 4 5 6 7 8 9 10 11 cat > /etc/docker/daemon.json <<'EOF' { "log-driver" : "local" , "log-opts" : { "max-size" : "50m" , "max-file" : "5" } } EOF systemctl restart docker
33.5 Read-only 官方镜像以只读文件系统运行时,需要给以下目录写权限:
1 2 /var/cache/nginx /var/run
Compose:
1 2 3 4 5 read_only: true tmpfs: - /var/cache/nginx - /var/run
如果使用磁盘 Proxy Cache,应将缓存目录挂载为持久卷,而不是 Tmpfs。
33.6 非 Root 可以使用 NGINX Unprivileged 镜像,或将容器监听端口改为 8080,再映射:
以任意 UID 运行时,要同步调整:
1 2 3 4 5 6 7 pid /tmp/nginx.pid;client_body_temp_path /tmp/client_temp;proxy_temp_path /tmp/proxy_temp;fastcgi_temp_path /tmp/fastcgi_temp;uwsgi_temp_path /tmp/uwsgi_temp;scgi_temp_path /tmp/scgi_temp;
不要一边声明 user: 10001,一边让 NGINX 写 Root 所属目录,然后用“容器有问题”总结故障。
三十四、Docker 多主机 NGINX 集群 推荐架构:
flowchart LR
Client[Client] --> VIP[Keepalived VIP]
VIP --> H1[Rocky Host 1]
VIP -.Failover.-> H2[Rocky Host 2]
H1 --> C1[NGINX Container]
H2 --> C2[NGINX Container]
C1 --> Backend[Backend Cluster]
C2 --> Backend
每台宿主机:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 services: nginx: image: registry.example.com/infra/nginx-edge:2026.07.31 container_name: nginx-edge restart: unless-stopped network_mode: host volumes: - /etc/nginx/ssl:/etc/nginx/ssl:ro,Z healthcheck: test: - CMD-SHELL - curl -fsS http://127.0.0.1:8088/healthz >/dev/null interval: 15s timeout: 3s retries: 3 ulimits: nofile: soft: 131072 hard: 131072
宿主机 Keepalived 脚本改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/usr/bin/env bash set -Eeuo pipefail docker inspect \ --format '{{.State.Running}} {{if .State.Health}}{{.State.Health.Status}}{{end}}' \ nginx-edge \ | grep -q '^true healthy$' curl \ --fail \ --silent \ --max-time 1 \ http://127.0.0.1:8088/healthz \ | grep -qx 'ok'
生产多主机更推荐前置云 LB 或硬件 LB,以获得主动检查和 Active/Active。
三十五、常见故障排查 35.1 nginx -t 失败 1 2 3 nginx -t nginx -T journalctl -u nginx -n 200 --no-pager
常见问题:
指令放错 Context;
缺少分号;
Include 路径错误;
重复 Listen;
证书文件不存在;
动态模块未安装;
Plus 指令被写到 Open Source;
配置使用了高版本才支持的参数。
35.2 403 Forbidden 逐层检查:
1 2 3 4 5 6 7 8 9 namei -om /srv/www/app/current/index.htmlls -ldZ /srv/www/appls -lZ /srv/www/app/current/index.htmlsudo -u nginx \ test -r /srv/www/app/current/index.html ausearch -m AVC -ts recent
常见原因:
父目录没有执行权限;
文件不可读;
SELinux Context 错误;
root 或 alias 配错;
Index 不存在;
Location 被更高优先级规则匹配。
35.3 404 但文件存在 检查:
重点关注:
root 与 alias;
末尾斜杠;
正则 Location;
try_files;
SPA Fallback;
实际 Server Name;
请求是否进入了默认 Server。
35.4 502 Bad Gateway 1 2 3 4 5 6 7 curl -v http://192.168.9.31:8080/health nc -vz 192.168.9.31 8080 grep -n 'upstream' /var/log/nginx/error.log | tail -100 getsebool httpd_can_network_connect ausearch -m AVC -ts recent
常见原因:
后端没启动;
IP 或端口错误;
firewalld;
SELinux;
DNS;
上游主动关闭;
协议不匹配;
HTTP 代理到了 HTTPS 端口;
TLS SNI 错误;
容器 DNS 变化。
35.5 504 Gateway Timeout 检查:
1 2 3 upstream_connect_time upstream_header_time upstream_response_time
不要只调大:
1 proxy_read_timeout 600s ;
还要确认:
SQL;
线程池;
下游 API;
锁;
GC;
网络;
应用是否真的允许长请求。
超时时间调大只是让用户等更久,不会让慢 SQL突然产生羞耻心然后自行优化。
35.6 WebSocket 立即断开 必须:
1 2 3 4 proxy_http_version 1 .1 ;proxy_set_header Upgrade $http_upgrade ;proxy_set_header Connection $connection_upgrade ;proxy_read_timeout 3600s ;
还要检查:
后端路径;
Origin;
TLS;
空闲超时;
外层 LB;
应用心跳。
35.7 静态文件更新后仍是旧版本 检查:
1 curl -I https://app.example.com/assets/app.js
如果配置:
文件名必须带内容 Hash:
不要给固定文件名:
设置一年 Immutable,然后每次发布都覆盖它。
35.8 upstream 没有摘除故障节点 检查是否只有一个节点。
查看日志:
1 2 3 4 grep -E \ 'upstream timed out|connect\(\) failed|no live upstreams' \ /var/log/nginx/error.log \ | tail -100
确认:
1 2 max_fails=3 fail_timeout=10s;proxy_next_upstream error timeout http_502 http_503 http_504;
被动健康检查需要真实请求触发,不会主动定时访问 /health。
35.9 systemd 不自动重启 1 2 3 systemctl cat nginx systemctl show nginx -p Restart -p NRestarts journalctl -u nginx
如果配置错误,Restart 不会神奇修复配置。
35.10 VIP 不漂移 1 2 3 4 5 6 7 8 9 systemctl status keepalived journalctl -u keepalived -n 300 --no-pager /usr/local/sbin/check-nginx-vip.shecho $? ip address show ens160 tcpdump -ni ens160 'ip proto 112'
检查:
Priority;
Weight;
Interface;
Unicast Peer;
VRID;
防火墙;
健康脚本权限;
fall 和 rise;
VIP 掩码;
云平台限制。
35.11 两台节点同时持有 VIP 这是 Split-Brain。
立即检查 VRRP 通信,不要先重启两台机器。重启能暂时改变选举结果,但修不好被防火墙吃掉的协议 112。
35.12 Docker 容器循环重启 1 2 3 4 5 6 7 8 9 10 11 docker inspect nginx-edge \ --format '{{json .State}}' \ | jq docker logs --tail 300 nginx-edge docker run --rm \ -v "$PWD /edge/nginx.conf:/etc/nginx/nginx.conf:ro" \ -v "$PWD /edge/default.conf:/etc/nginx/conf.d/default.conf:ro" \ nginx:1.30.4-alpine \ nginx -t
常见原因:
配置错误;
挂载文件不存在;
Read-only 但临时目录不可写;
端口冲突;
SELinux;
证书权限;
上游域名无法解析;
Healthcheck 工具不存在。
35.13 Docker 后端重建后 NGINX 仍使用旧 IP Rocky 原生 NGINX 1.26.3 解析 upstream 域名通常发生在加载配置时。
Docker 使用 1.30.4 时,可配置:
1 2 3 4 5 6 resolver 127.0.0.11 valid=10s ;upstream backend { zone backend 64k ; server backend-a:80 resolve; }
如果没有使用动态 Resolve,重建后执行:
1 docker compose restart edge
或:
1 docker exec nginx-edge nginx -s reload
三十六、生产上线检查清单 安装和配置
反向代理
负载均衡
健康和监控
集群
Docker
三十七、总结 在 Rocky Linux 10 上搭建生产级 NGINX,需要同时处理四个层次:
1 2 3 4 5 6 7 8 9 10 11 第一层:NGINX 自身 静态资源、反向代理、TLS、缓存、限流 第二层:后端服务 负载均衡、连接复用、超时、被动健康检查 第三层:操作系统 systemd、SELinux、firewalld、文件句柄、TCP 和日志 第四层:入口高可用 Keepalived VIP、外部负载均衡、配置同步和故障演练
核心原则:
每次变更先执行 nginx -t,再平滑 Reload;
静态资源使用版本化文件名和明确缓存策略;
普通 API 保持 Buffering,流式和上传接口单独处理;
upstream keepalive 要与后端容量一起计算;
NGINX Open Source 默认是被动健康检查;
systemd 负责进程自愈,Timer 负责本机 HTTP 检测;
Keepalived 负责 VIP 漂移,不负责 Active/Active 流量分担;
反向代理必须正确配置 SELinux;
不要通过无限增大 Timeout 和 Buffer 掩盖后端问题;
Docker 配置应不可变、可扫描、可回滚;
本机健康不等于用户链路健康,必须有外部探测;
高可用不是“有两台机器”,而是故障真的被检测、隔离、切换并演练过。
NGINX 性能问题通常不是靠一份“百万并发配置”解决的。先找到限制发生在 CPU、连接、端口、网络、磁盘、日志还是后端,再改对应参数。否则配置文件会越来越像法术卷轴,服务器却依旧在 502 面前保持朴素和诚实。
参考资料