找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1160

积分

0

好友

148

主题
发表于 昨天 22:04 | 查看: 6| 回复: 0

一、概述

1.1 背景介绍

Nginx 是目前使用最广泛的反向代理和负载均衡器。它采用事件驱动的异步非阻塞模型,单机即可处理数万并发连接,内存占用极低——实测 10000 个非活跃 HTTP keep-alive 连接仅消耗约 2.5MB 内存。

从 2018 年开始在生产环境全面使用 Nginx 替代 Apache,目前管理着超过 200 台 Nginx 实例,日均处理请求量超过 5 亿次。本文将基于这些实战经验,系统梳理反向代理、负载均衡、SSL/TLS、缓存、限流等核心场景的配置与最佳实践。

1.2 技术特点

  • 事件驱动异步非阻塞:基于 epoll(Linux)/ kqueue(BSD)实现,单个 worker 进程可处理数千并发连接,避免了类似 Apache prefork 模式每个连接占用一个进程的资源消耗模式。
  • 模块化架构:由核心模块(ngx_http_core_module)、官方模块及丰富的第三方模块(如 ngx_http_geoip2_module、lua-nginx-module)组成,支持按需编译,不加载多余功能。
  • 热加载配置:通过 nginx -s reload 命令可实现配置热更新,不中断正在处理的连接,是线上变更实现零停机时间的关键。
  • 低资源消耗:实测对比,在同等 QPS 下,Nginx 的内存占用约为 Apache 的 1/10,CPU 使用率低 30%-50%。

1.3 适用场景

  • Web 服务反向代理:将外部客户端请求转发到内部应用服务器(如 Tomcat、Node.js、Go 服务等),隐藏后端拓扑结构,实现统一入口管理。
  • API 网关:基于 location 路由规则将不同 API 路径分发到对应的微服务,配合 limit_req 等模块可实现接口级的精细限流。
  • 静态资源服务:直接托管 HTML、CSS、JS、图片等静态文件,配合 gzip 压缩和 open_file_cache 缓存,其吞吐量远超各类应用服务器。
  • SSL/TLS 终结:在 Nginx 层统一处理 HTTPS 的加解密工作,后端服务只需处理明文的 HTTP 流量,有效降低了后端的复杂度和 CPU 开销。
  • 流量分发与灰度发布:通过 upstream 权重、split_clients 模块或 Lua 脚本,可以实现按比例分流、A/B 测试等高级流量管理功能。

1.4 环境要求

组件 版本要求 说明
操作系统 CentOS 7+ / Ubuntu 20.04+ 推荐 Ubuntu 22.04 LTS 或 Rocky Linux 9,内核 5.x+ 支持更好的 epoll 特性
Nginx 1.24.0+ / 1.26.0+(mainline) 1.24 为当前稳定版,1.26 为主线版本,生产环境建议使用稳定版
OpenSSL 1.1.1+ (推荐 3.0+) TLS 1.3 支持需要 OpenSSL 1.1.1+,Ubuntu 22.04 自带 OpenSSL 3.0
GCC 4.8+ 编译安装时需要,通过包管理器安装可忽略此项
PCRE 8.x+ 正则表达式支持,location 匹配依赖此库
硬件配置 2C4G 起步 纯代理场景 2C4G 可应对约 5000 QPS,若涉及 SSL 终结建议 4C8G 起步

二、详细步骤

2.1 准备工作

2.1.1 系统检查

在安装前,建议对系统进行基础检查。

# 检查系统版本
cat /etc/os-release

# 检查内核版本(建议 5.x+)
uname -r

# 检查可用内存和磁盘
free -h
df -h

# 检查当前是否已安装 Nginx
nginx -v 2>/dev/null && echo "Nginx 已安装" || echo "Nginx 未安装"

# 检查 80/443 端口是否被占用
ss -tlnp | grep -E ':80|:443'

2.1.2 安装依赖

根据不同的操作系统安装编译和运行依赖。

Ubuntu/Debian 系统:

sudo apt update && sudo apt upgrade -y

# 编译安装所需依赖
sudo apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev \
    libssl-dev libgd-dev libgeoip-dev libmaxminddb-dev \
    curl wget gnupg2 ca-certificates lsb-release

CentOS/Rocky Linux 系统:

sudo yum install -y epel-release
sudo yum groupinstall -y "Development Tools"
sudo yum install -y pcre pcre-devel zlib zlib-devel openssl openssl-devel \
    gd gd-devel GeoIP GeoIP-devel libmaxminddb-devel \
    curl wget

2.2 安装 Nginx

2.2.1 方式一:包管理器安装(推荐快速部署)

适合不需要自定义模块的快速部署场景,安装便捷,升级管理方便。

Ubuntu 添加 Nginx 官方源:

# 导入 Nginx 官方 GPG 密钥
curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg

# 添加官方源(稳定版)
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx.list

# 设置优先使用官方源而非系统自带源
echo -e "Package: *\nPin: origin nginx.org\nPin-Priority: 900" | sudo tee /etc/apt/preferences.d/99nginx

# 安装
sudo apt update
sudo apt install -y nginx

# 验证版本
nginx -v

CentOS/Rocky Linux 添加 Nginx 官方源:

cat > /etc/yum.repos.d/nginx.repo << 'EOF'
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
EOF

sudo yum install -y nginx
nginx -v

2.2.2 方式二:编译安装(需要自定义模块时使用)

在需要集成 GeoIP2、Brotli 压缩、VTS 监控等第三方模块时,选择编译安装。

# 下载 Nginx 源码
cd /usr/local/src
wget https://nginx.org/download/nginx-1.24.0.tar.gz
tar -zxvf nginx-1.24.0.tar.gz

# 下载第三方模块(按需)
git clone https://github.com/leev/ngx_http_geoip2_module.git
git clone https://github.com/google/ngx_brotli.git
cd ngx_brotli && git submodule update --init && cd ..
git clone https://github.com/vozlt/nginx-module-vts.git

# 编译安装
cd nginx-1.24.0

./configure \
    --prefix=/etc/nginx \
    --sbin-path=/usr/sbin/nginx \
    --modules-path=/usr/lib64/nginx/modules \
    --conf-path=/etc/nginx/nginx.conf \
    --error-log-path=/var/log/nginx/error.log \
    --http-log-path=/var/log/nginx/access.log \
    --pid-path=/var/run/nginx.pid \
    --lock-path=/var/run/nginx.lock \
    --with-http_ssl_module \
    --with-http_v2_module \
    --with-http_realip_module \
    --with-http_gzip_static_module \
    --with-http_stub_status_module \
    --with-http_sub_module \
    --with-http_auth_request_module \
    --with-stream \
    --with-stream_ssl_module \
    --with-stream_realip_module \
    --with-pcre \
    --with-threads \
    --with-file-aio \
    --add-module=/usr/local/src/ngx_http_geoip2_module \
    --add-module=/usr/local/src/ngx_brotli \
    --add-module=/usr/local/src/nginx-module-vts

make -j$(nproc)
sudo make install

编译安装后创建 systemd 服务文件:

cat > /etc/systemd/system/nginx.service << 'EOF'
[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/var/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t
ExecStart=/usr/sbin/nginx
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
LimitNOFILE=65535

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable nginx
sudo systemctl start nginx

2.3 反向代理配置

2.3.1 基础反向代理

反向代理的核心指令是 proxy_pass,但生产环境的配置远不止这一行。以下是经过线上验证的完整配置模板:

server {
    listen 80;
    server_name api.example.com;

    # 日志
    access_log /var/log/nginx/api_access.log main;
    error_log  /var/log/nginx/api_error.log warn;

    location / {
        proxy_pass http://127.0.0.1:8080;

        # 传递真实客户端信息(这几个 header 必须配置,否则后端应用无法获取真实客户端 IP)
        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;

        # 超时设置(单位:秒)
        # 生产环境建议根据后端服务的实际响应时间进行调整,默认 60s 对某些慢接口可能不够
        proxy_connect_timeout 10;    # 与后端服务器建立连接的超时时间,10s 通常足够,太长往往说明后端有问题
        proxy_read_timeout    60;    # 等待后端服务器响应的超时时间,报表类接口可能需要设置到 120s
        proxy_send_timeout    30;    # 向后端服务器发送请求的超时时间

        # Buffer 配置
        # 默认 buffer 大小(4k/8k)可能过小,若后端返回大 header(如带长 JWT token 的 Set-Cookie)会导致 502 错误
        proxy_buffer_size        16k;     # 用于读取后端响应头的 buffer,生产环境建议 16k
        proxy_buffers            4 32k;   # 用于读取后端响应体的 buffer 数量和大小
        proxy_busy_buffers_size  64k;     # 在响应未完全读取时,可以发送给客户端的最大 buffer 大小

        # HTTP 1.1 长连接(减少与后端服务器的 TCP 握手开销)
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

特别注意proxy_pass 指令末尾是否有斜杠 / 会导致完全不同的转发行为,这是最常见的配置陷阱:

# 假设客户端请求是 GET /api/users

# 写法一:proxy_pass 没有 URI 部分
location /api/ {
    proxy_pass http://backend;
}
# 转发到后端服务器的路径:/api/users(原样转发)

# 写法二:proxy_pass 带了 URI 部分(末尾有 /)
location /api/ {
    proxy_pass http://backend/;
}
# 转发到后端服务器的路径:/users(location 匹配的 `/api/` 被替换为 `/`)

# 写法三:proxy_pass 带了其他 URI
location /api/ {
    proxy_pass http://backend/v2/;
}
# 转发到后端服务器的路径:/v2/users(location 匹配的 `/api/` 被替换为 `/v2/`)

这个规则一旦搞错就会导致后端大量 404 错误,线上曾因此多次出事故,务必牢记。

2.3.2 proxy_pass 与 upstream 配合

单独使用 proxy_pass http://127.0.0.1:8080 只能代理到单台后端。生产环境必须与 upstream 模块配合,实现负载均衡和高可用。

upstream backend_api {
    server 10.0.1.10:8080 weight=5 max_fails=3 fail_timeout=30s;
    server 10.0.1.11:8080 weight=5 max_fails=3 fail_timeout=30s;
    server 10.0.1.12:8080 weight=3 max_fails=3 fail_timeout=30s;

    # 长连接池(减少 TCP 握手开销,实测可提升 QPS 15%-20%)
    keepalive 64;
    keepalive_timeout 60s;
    keepalive_requests 1000;
}

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://backend_api;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        # ... 其他 proxy 配置同上文基础反向代理部分
    }
}

2.4 负载均衡策略

2.4.1 轮询(Round Robin)—— 默认策略

upstream backend {
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

最简单,请求依次分配到每台后端服务器。适合后端服务器配置相同、无状态的场景。

2.4.2 加权轮询(Weighted Round Robin)

upstream backend {
    server 10.0.1.10:8080 weight=5;   # 配置较高(如 8C16G)的机器,分配更多权重
    server 10.0.1.11:8080 weight=5;   # 配置较高的机器
    server 10.0.1.12:8080 weight=2;   # 配置较低(如 4C8G)的机器,分配较少权重
}

当后端机器配置不同时,使用权重进行差异化分配。weight 值越大,分到的请求越多。实践中,通常会按 CPU 核数比例来设置权重。

2.4.3 IP Hash(会话保持)

upstream backend {
    ip_hash;
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

根据客户端 IP 计算哈希值,确保同一客户端的请求始终被转发到同一台后端服务器。适用于未使用 Redis 等集中存储 Session 的老系统,需要会话保持的场景。

注意:如果客户端请求前方存在 CDN 或其他代理层,所有请求的源 IP 都会变成代理服务器的 IP,此时 ip_hash 会导致所有流量被打到同一台后端。在这种情况下,应使用 hash $http_x_forwarded_for consistent; 来替代。

2.4.4 最少连接(Least Connections)

upstream backend {
    least_conn;
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

将新请求分配给当前活跃连接数最少的后端服务器。适合后端请求处理时间差异较大的场景(例如,有的接口 50ms 返回,有的需要 5s)。

2.4.5 一致性哈希

upstream backend {
    hash $request_uri consistent;
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

根据 $request_uri(或其他变量)计算哈希值,确保相同 URI 的请求始终路由到同一台后端。这适用于后端服务器有本地缓存(非共享缓存)的场景。添加 consistent 参数启用一致性哈希算法,在增减后端节点时,只有少量请求的路由会发生变化,影响面更小。

2.4.6 健康检查配置

Nginx 开源版默认提供被动式健康检查,通过 max_failsfail_timeout 参数实现:

upstream backend {
    server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
    server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
    server 10.0.1.12:8080 max_fails=3 fail_timeout=30s backup;  # 备用节点
}

参数说明:

  • max_fails=3:在 fail_timeout 时间窗口内,连接失败 3 次后,标记该节点为不可用。
  • fail_timeout=30s:有两层含义:① 统计失败次数的时间窗口长度;② 节点被标记为不可用后,等待恢复的时间。
  • backup:标记为备用节点,只有在所有主节点都不可用时才会启用。

注意:被动健康检查存在延迟,节点故障后,最初的几个请求仍会失败。如果需要主动健康检查,要么使用 Nginx Plus(商业版),要么编译第三方模块 nginx_upstream_check_module

upstream backend {
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;

    # 主动健康检查(需要编译 nginx_upstream_check_module)
    check interval=3000 rise=2 fall=3 timeout=2000 type=http;
    check_http_send "GET /health HTTP/1.0\r\n\r\n";
    check_http_expect_alive http_2xx http_3xx;
}

2.5 SSL/TLS 配置

2.5.1 Let's Encrypt 证书申请

# 安装 certbot
sudo apt install -y certbot python3-certbot-nginx

# 申请证书(自动修改 Nginx 配置)
sudo certbot --nginx -d example.com -d www.example.com

# 或者只申请证书不修改配置(推荐,手动控制配置更安全)
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com

# 测试自动续期
sudo certbot renew --dry-run

# 设置自动续期定时任务
echo "0 3 * * * root certbot renew --quiet --post-hook 'systemctl reload nginx'" | sudo tee /etc/cron.d/certbot-renew

2.5.2 TLS 1.3 + 安全配置

server {
    listen 443 ssl http2;
    server_name example.com;

    # 证书路径
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # TLS 协议版本(只启用安全的 TLS 1.2 和 1.3,禁用 1.0/1.1)
    ssl_protocols TLSv1.2 TLSv1.3;

    # 加密套件(TLS 1.3 的套件由客户端和服务端自动协商,这里主要配置 TLS 1.2 的套件)
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers on;

    # SSL 会话缓存(减少 TLS 握手开销)
    ssl_session_cache shared:SSL:20m;    # 20MB 缓存空间约可存储 80000 个会话
    ssl_session_timeout 1d;
    ssl_session_tickets off;             # 禁用 session ticket,安全性更高

    # OCSP Stapling(加速客户端证书验证)
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # HSTS(强制浏览器使用 HTTPS 访问,max-age 建议至少 1 年)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}

# HTTP 强制跳转 HTTPS
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

2.6 缓存配置

2.6.1 proxy_cache 配置

# 在 http 块中定义缓存路径和参数
http {
    proxy_cache_path /var/cache/nginx/proxy_cache
        levels=1:2                    # 两级目录结构,避免单目录文件过多影响性能
        keys_zone=my_cache:50m        # 缓存 key 的共享内存存储区,50MB 约可存储 40 万个 key
        max_size=10g                  # 缓存最大占用磁盘空间
        inactive=60m                  # 60 分钟内未被访问的缓存条目自动清除
        use_temp_path=off;            # 直接写入缓存目录,不用临时目录(减少一次 IO 操作)
}

server {
    listen 80;
    server_name static.example.com;

    location / {
        proxy_pass http://backend;

        # 启用缓存
        proxy_cache my_cache;
        proxy_cache_valid 200 302 10m;    # 200/302 响应缓存 10 分钟
        proxy_cache_valid 301     1h;     # 301 响应缓存 1 小时
        proxy_cache_valid 404     1m;     # 404 响应缓存 1 分钟(防止缓存穿透攻击)

        # 缓存 key(默认是 $scheme$proxy_host$request_uri)
        proxy_cache_key $scheme$host$request_uri;

        # 添加响应头,方便排查缓存是否命中
        add_header X-Cache-Status $upstream_cache_status;

        # 后端故障时,允许使用过期的缓存进行兜底
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;

        # 防止缓存击穿:同一个 key 只允许一个请求回源,其他请求等待
        proxy_cache_lock on;
        proxy_cache_lock_timeout 5s;
    }

    # 不缓存带有 Cookie 或 Authorization 头的请求(通常为个性化请求)
    location /api/ {
        proxy_pass http://backend_api;
        proxy_cache my_cache;
        proxy_no_cache $http_authorization $cookie_session;
        proxy_cache_bypass $http_authorization $cookie_session;
    }
}

X-Cache-Status 响应头的值含义:

  • HIT:缓存命中
  • MISS:缓存未命中,已回源
  • EXPIRED:缓存已过期,已回源更新
  • STALE:使用了过期缓存(后端故障兜底)
  • BYPASS:跳过缓存直接回源

2.7 限流配置

2.7.1 请求速率限制(漏桶算法)

http {
    # 定义限流区域:按客户端 IP 限流,每秒 10 个请求
    # zone=api_limit:10m 表示分配 10MB 内存存储限流状态(约可存储 16 万个 IP 的状态)
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

    # 针对 API 接口的更严格限流
    limit_req_zone $binary_remote_addr zone=login_limit:5m rate=1r/s;

    server {
        listen 80;
        server_name api.example.com;

        location /api/ {
            # burst=20:允许突发 20 个请求排队
            # nodelay:排队的请求立即处理,不延迟(超过 burst 数量的请求直接返回 429)
            limit_req zone=api_limit burst=20 nodelay;
            limit_req_status 429;    # 超过限制时返回 429 状态码,而非默认的 503

            proxy_pass http://backend_api;
        }

        # 登录接口严格限流,防止暴力破解
        location /api/login {
            limit_req zone=login_limit burst=5 nodelay;
            limit_req_status 429;

            proxy_pass http://backend_api;
        }
    }
}

burstnodelay 参数的区别

  • 不加 burst:超过 rate 的请求被直接拒绝。
  • burst 不加 nodelay:超过 rate 的请求进入队列,按 rate 速率依次处理。
  • burstnodelay:超过 rate 但在 burst 范围内的请求立即处理(不排队),超过 burst 的直接拒绝。

生产环境通常推荐使用 burst=N nodelay 模式,既能应对合理的突发流量,又不会让用户等待过久。

2.7.2 连接数限制

http {
    # 按 IP 限制并发连接数
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    server {
        # 每个 IP 最多 50 个并发连接
        limit_conn conn_limit 50;
        limit_conn_status 429;

        # 限制下载速度(防止单个用户占满带宽)
        location /download/ {
            limit_conn conn_limit 5;       # 下载目录每 IP 最多 5 个并发连接
            limit_rate_after 10m;          # 前 10MB 不限速
            limit_rate 1m;                 # 10MB 之后限速 1MB/s
        }
    }
}

三、示例代码和配置

3.1 完整配置示例

3.1.1 生产级 nginx.conf 主配置文件

这份配置在线上环境稳定运行超过两年,单机曾扛住超过 30000 QPS 的流量压力。

# 文件路径:/etc/nginx/nginx.conf

# worker 进程数,auto 会自动设为 CPU 核数
worker_processes auto;

# 绑定 CPU 亲和性(减少 CPU 上下文切换,4 核示例)
# auto 参数在 1.9.10+ 可用,自动绑定
worker_cpu_affinity auto;

# 单个 worker 进程可打开的最大文件描述符数
# 这个值必须 >= worker_connections,否则高并发时会报 "too many open files"
worker_rlimit_nofile 65535;

# 错误日志级别,生产环境用 warn,排查问题时临时改为 info 或 debug
error_log /var/log/nginx/error.log warn;
pid       /var/run/nginx.pid;

events {
    # 单个 worker 的最大并发连接数
    # 总并发 = worker_processes × worker_connections
    # 作为反向代理时,每个客户端连接会占用 2 个连接(客户端→Nginx + Nginx→后端)
    # 所以实际可服务的客户端数 = worker_processes × worker_connections / 2
    worker_connections 65535;

    # 一次性接受所有新连接(高并发场景建议开启)
    multi_accept on;

    # Linux 下用 epoll,性能最好
    use epoll;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    # 自定义日志格式(包含 upstream 响应时间,排查慢请求必备)
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'rt=$request_time urt=$upstream_response_time '
                    'ua=$upstream_addr us=$upstream_status '
                    'cs=$upstream_cache_status';

    # 日志缓冲写入(减少磁盘 IO,实测 IOPS 降低 60%)
    access_log /var/log/nginx/access.log main buffer=32k flush=5s;

    # 零拷贝发送文件(静态文件场景性能提升明显)
    sendfile on;

    # 配合 sendfile 使用,数据包攒够再发(减少网络包数量)
    tcp_nopush on;

    # 小数据包立即发送(与 tcp_nopush 不冲突,Nginx 会在最后一个包时启用 nodelay)
    tcp_nodelay on;

    # 长连接超时
    keepalive_timeout 65;
    keepalive_requests 1000;

    # 隐藏 Nginx 版本号(安全加固,防止针对特定版本的攻击)
    server_tokens off;

    # 请求体大小限制(默认 1m,上传文件场景需要调大)
    client_max_body_size 50m;

    # 请求头 buffer(默认 1k,带大 Cookie 或长 URL 时会报 414/400 错误)
    client_header_buffer_size 4k;
    large_client_header_buffers 4 32k;

    # Gzip 压缩
    gzip on;
    gzip_min_length 1k;          # 小于 1k 的内容不压缩(压缩后可能更大)
    gzip_comp_level 4;           # 压缩级别 1-9,4 是性价比最高的(实测)
    gzip_types text/plain text/css text/javascript
               application/json application/javascript application/xml
               application/xml+rss image/svg+xml;
    gzip_vary on;                # 添加 Vary: Accept-Encoding 头
    gzip_proxied any;            # 对代理请求也启用压缩
    gzip_disable "MSIE [1-6]\."; # IE6 不支持 gzip

    # 文件描述符缓存(频繁访问的静态文件不用每次 open/stat)
    open_file_cache max=65535 inactive=60s;
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;

    # 加载虚拟主机配置
    include /etc/nginx/conf.d/*.conf;
}

3.1.2 多后端路由配置

实际项目中,一个域名下通常有多个后端服务。以下是典型的前后端分离 + API 网关配置示例:

# 文件路径:/etc/nginx/conf.d/app.example.com.conf

# 后端 API 服务集群
upstream backend_api {
    least_conn;
    server 10.0.1.10:8080 weight=5 max_fails=3 fail_timeout=30s;
    server 10.0.1.11:8080 weight=5 max_fails=3 fail_timeout=30s;
    server 10.0.1.12:8080 weight=3 max_fails=3 fail_timeout=30s;
    keepalive 64;
    keepalive_timeout 60s;
    keepalive_requests 1000;
}

# 用户服务集群
upstream user_service {
    server 10.0.2.10:9001 max_fails=3 fail_timeout=30s;
    server 10.0.2.11:9001 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

# 订单服务集群
upstream order_service {
    server 10.0.3.10:9002 max_fails=3 fail_timeout=30s;
    server 10.0.3.11:9002 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name app.example.com;

    ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
    include /etc/nginx/snippets/ssl-params.conf;

    # 前端静态文件服务
    location / {
        root /var/www/app/dist;
        index index.html;
        try_files $uri $uri/ /index.html;    # 支持 SPA 单页应用路由

        # 静态资源缓存
        location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
            expires 30d;
            add_header Cache-Control "public, immutable";
        }
    }

    # API 总入口
    location /api/ {
        proxy_pass http://backend_api;
        include /etc/nginx/snippets/proxy-params.conf;
    }

    # 用户服务路由
    location /api/users/ {
        proxy_pass http://user_service/;
        include /etc/nginx/snippets/proxy-params.conf;
    }

    # 订单服务路由
    location /api/orders/ {
        proxy_pass http://order_service/;
        include /etc/nginx/snippets/proxy-params.conf;
        proxy_read_timeout 120s;    # 订单服务有慢查询,超时时间设长一些
    }

    # 健康检查端点(供负载均衡器或监控系统探测)
    location /health {
        access_log off;
        return 200 "OK\n";
        add_header Content-Type text/plain;
    }

    # 禁止访问隐藏文件
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

公共代理参数抽取为 snippet,避免重复配置:

# 文件路径:/etc/nginx/snippets/proxy-params.conf

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 10;
proxy_read_timeout    60;
proxy_send_timeout    30;

proxy_buffer_size        16k;
proxy_buffers            4 32k;
proxy_busy_buffers_size  64k;

proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 10s;

3.1.3 WebSocket 代理配置

WebSocket 代理是个高频需求,配置不对会导致连接建立后立刻断开。关键在于正确传递 UpgradeConnection 头。

# 文件路径:/etc/nginx/conf.d/ws.example.com.conf

# 用 map 指令动态设置 Connection 头
# 当客户端请求头包含 `Upgrade: websocket` 时,Connection 设为 "upgrade"
# 否则设为 "close"(普通 HTTP 请求使用长连接)
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

upstream websocket_backend {
    server 10.0.4.10:3000;
    server 10.0.4.11:3000;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name ws.example.com;

    ssl_certificate     /etc/letsencrypt/live/ws.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ws.example.com/privkey.pem;
    include /etc/nginx/snippets/ssl-params.conf;

    location /ws/ {
        proxy_pass http://websocket_backend;
        proxy_http_version 1.1;

        # WebSocket 代理必须设置的两个头
        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;

        # WebSocket 连接超时设置
        # 默认 60s 没有数据传输就断开,生产环境建议设长一些
        # 需配合客户端心跳机制使用
        proxy_read_timeout 3600s;    # 1 小时
        proxy_send_timeout 3600s;
    }

    # 普通 HTTP 请求
    location / {
        proxy_pass http://websocket_backend;
        include /etc/nginx/snippets/proxy-params.conf;
    }
}

注意proxy_read_timeout 对 WebSocket 连接至关重要。如果客户端和服务端之间没有心跳机制,Nginx 会在 proxy_read_timeout 设定的时间后断开连接。建议客户端每 30 秒发送一次 ping 帧,并将 proxy_read_timeout 设置为 3600s。

3.1.4 多虚拟主机 HTTPS 配置(SNI)

在同一台 Nginx 服务器上托管多个 HTTPS 站点,依赖 SNI(Server Name Indication)扩展。现代浏览器和操作系统均已支持 SNI,无需担心兼容性问题。

# 文件路径:/etc/nginx/conf.d/multi-vhost.conf

# 站点一:主站
server {
    listen 443 ssl http2;
    server_name www.example.com example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/nginx/snippets/ssl-params.conf;

    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

# 站点二:管理后台(限制内网访问)
server {
    listen 443 ssl http2;
    server_name admin.example.com;

    ssl_certificate     /etc/letsencrypt/live/admin.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/admin.example.com/privkey.pem;
    include /etc/nginx/snippets/ssl-params.conf;

    # 管理后台限制 IP 访问
    allow 10.0.0.0/8;
    allow 172.16.0.0/12;
    allow 192.168.0.0/16;
    deny all;

    location / {
        proxy_pass http://127.0.0.1:8081;
        include /etc/nginx/snippets/proxy-params.conf;
    }
}

# 站点三:API 服务
server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    include /etc/nginx/snippets/ssl-params.conf;

    location / {
        proxy_pass http://backend_api;
        include /etc/nginx/snippets/proxy-params.conf;
    }
}

# 默认站点(未匹配到任何 server_name 的请求直接拒绝)
server {
    listen 443 ssl http2 default_server;
    server_name _;

    ssl_certificate     /etc/nginx/ssl/default.pem;
    ssl_certificate_key /etc/nginx/ssl/default.key;

    return 444;    # Nginx 特殊状态码,直接关闭连接,不发送任何响应
}

SSL 公共参数 snippet:

# 文件路径:/etc/nginx/snippets/ssl-params.conf

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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers on;

ssl_session_cache shared:SSL:20m;
ssl_session_timeout 1d;
ssl_session_tickets off;

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

3.2 实际应用案例

案例一:灰度发布(按权重分流)

场景描述:新版本上线前,先将 10% 的流量切到新版本(v2),观察一段时间确认无问题后再全量切换。

实现代码

# 新旧版本 upstream 定义
upstream backend_v1 {
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;
}

upstream backend_v2 {
    server 10.0.2.10:8080;
    server 10.0.2.11:8080;
}

# 按比例分流:10% 流量到 v2,90% 流量到 v1
split_clients $request_id $backend_version {
    10%  backend_v2;
    *    backend_v1;
}

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://$backend_version;
        include /etc/nginx/snippets/proxy-params.conf;

        # 添加响应头标识版本,方便排查问题
        add_header X-Backend-Version $backend_version;
    }
}

运行验证

# 多次请求查看分流效果
for i in $(seq 1 100); do
    curl -s -o /dev/null -w "%{http_code} " -H "Host: app.example.com" http://localhost
done
# 输出结果中,大约有 90 次请求走 v1 版本,10 次走 v2 版本

场景描述:内部测试人员通过设置特定的 Cookie 来访问新版本,普通用户继续访问稳定版本。

实现步骤

map $cookie_canary $target_backend {
    "true"  backend_v2;
    default backend_v1;
}

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://$target_backend;
        include /etc/nginx/snippets/proxy-params.conf;
    }
}

测试人员在浏览器中设置 Cookie canary=true 后,其所有请求将被路由到新版本后端,无需修改任何前端代码或配置。

四、最佳实践和注意事项

4.1 最佳实践

4.1.1 Worker 调优

  • worker_processes:建议设置为 auto,Nginx 会自动将其设置为 CPU 核数。手动设置时不应超过 CPU 核数,否则会增加不必要的上下文切换开销。

    # 查看 CPU 核数
    nproc
    # 查看当前 Nginx worker 进程数
    ps aux | grep "nginx: worker" | grep -v grep | wc -l
  • worker_rlimit_nofile:必须设置为 65535 或更高。如果未设置,worker 进程能打开的文件描述符数将受系统默认限制(通常为 1024),高并发时会导致 "too many open files" 错误。

    # 同时修改系统级限制
    cat >> /etc/security/limits.conf << 'EOF'
    nginx soft nofile 65535
    nginx hard nofile 65535
    * soft nofile 65535
    * hard nofile 65535
    EOF
    # 修改 systemd 服务限制:在 [Service] 段添加 `LimitNOFILE=65535`
  • worker_connections 计算方法

    • 纯静态文件服务:最大并发客户端数 = worker_processes × worker_connections
    • 反向代理服务:最大并发客户端数 = worker_processes × worker_connections / 2 (每个客户端连接占用 2 个连接:客户端到 Nginx,Nginx 到后端)
    • 生产环境通常设置为 65535 即可,瓶颈通常不在此处。

4.1.2 性能优化

  • sendfile + tcp_nopush + tcp_nodelay 三件套

    sendfile on;       # 启用零拷贝,静态文件传输不经过用户态
    tcp_nopush on;     # 在数据包被填满后再发送,减少网络包数量(需配合 sendfile 使用)
    tcp_nodelay on;    # 最后一个数据包立即发送,减少延迟(与 tcp_nopush 不冲突)

    Nginx 内部会协调这两个参数:发送大文件时利用 tcp_nopush 减少包数量,发送最后一个小包时启用 tcp_nodelay 降低延迟。实测可提升静态文件吞吐量 20%-30%。

  • Gzip 压缩配置

    gzip on;
    gzip_min_length 1k;
    gzip_comp_level 4;          # 实测压缩级别 4 和 9 的压缩率差距不到 5%,但 CPU 开销相差 3 倍
    gzip_types text/plain text/css text/javascript
             application/json application/javascript
             application/xml application/xml+rss
             image/svg+xml;
    gzip_vary on;
    gzip_proxied any;
    gzip_buffers 16 8k;

    注意:不要对图片(jpg/png/gif)和已压缩文件(zip/gz)开启 gzip,因为它们本身已是压缩格式,再次压缩反而可能增加体积并浪费 CPU。

  • open_file_cache 文件缓存

    # 缓存文件描述符、文件大小、修改时间等信息
    open_file_cache max=65535 inactive=60s;
    open_file_cache_valid 30s;       # 每 30s 检查一次缓存条目是否过期
    open_file_cache_min_uses 2;      # 文件至少被访问 2 次后才被缓存
    open_file_cache_errors on;       # 缓存文件不存在的查询结果(减少无效的磁盘查找)

    对于静态资源服务器,此配置能显著减少高并发下的 stat()open() 系统调用。实测可降低 CPU 使用率 10%-15%。

4.1.3 安全 Header 配置

# 文件路径:/etc/nginx/snippets/security-headers.conf

# 防止页面被嵌入 iframe(防点击劫持)
add_header X-Frame-Options "SAMEORIGIN" always;

# 防止浏览器猜测 MIME 类型(防 XSS)
add_header X-Content-Type-Options "nosniff" always;

# 启用浏览器的 XSS 过滤(现代浏览器已内置,但加上无害)
add_header X-XSS-Protection "1; mode=block" always;

# 控制 Referer 头的发送策略,保护用户隐私
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# 内容安全策略(需根据实际业务资源调整)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;" always;

# 权限策略(禁用不需要的浏览器功能)
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

在 server 块中引用:

server {
    # ...
    include /etc/nginx/snippets/security-headers.conf;
}

4.1.4 高可用方案:Keepalived + VIP 双机热备

单台 Nginx 是单点故障。生产环境至少应部署两台,并使用 Keepalived 实现虚拟 IP(VIP)漂移,构成双机热备。

主节点配置(假设 IP 为 10.0.0.10):

  1. 安装 Keepalived:sudo apt install -y keepalived
  2. 配置 /etc/keepalived/keepalived.conf
    
    global_defs {
    router_id nginx_master
    script_user root
    enable_script_security
    }

Nginx 健康检查脚本

vrrp_script check_nginx {
script "/etc/keepalived/check_nginx.sh"
interval 2        # 每 2 秒检查一次
weight -20        # 检查失败时优先级减 20
fall 3            # 连续失败 3 次才判定为故障
rise 2            # 连续成功 2 次才判定为恢复
}

vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 100                # 主节点优先级设为 100
advert_int 1                # VRRP 通告间隔 1 秒
authentication {
auth_type PASS
auth_pass K8s@Nginx2026    # 主备节点密码必须一致
}
virtual_ipaddress {
10.0.0.100/24           # 虚拟 IP(VIP)地址
}
track_script {
check_nginx
}
}

3. 创建健康检查脚本 `/etc/keepalived/check_nginx.sh`:
```bash
#!/bin/bash
if ! pidof nginx > /dev/null 2>&1; then
    # Nginx 进程不存在,尝试重启
    systemctl restart nginx
    sleep 2
    if ! pidof nginx > /dev/null 2>&1; then
        # 重启失败,退出码非 0 将触发 VIP 漂移
        exit 1
    fi
fi
exit 0
chmod +x /etc/keepalived/check_nginx.sh
sudo systemctl enable keepalived
sudo systemctl start keepalived

备节点配置(假设 IP 为 10.0.0.11):与主节点配置基本相同,仅需修改以下参数:

  • state BACKUP
  • priority 90 (优先级低于主节点)
  • router_id nginx_backup

实测 VIP 漂移耗时约为 3-5 秒,对于大部分业务场景是可接受的。

4.1.5 日志优化

  • 缓冲写入:默认每条请求都会立即写入磁盘,高并发下磁盘 IO 易成瓶颈。

    # buffer=32k:攒够 32KB 日志数据再写入磁盘
    # flush=5s:最多等待 5 秒强制刷盘(防止日志延迟太久)
    access_log /var/log/nginx/access.log main buffer=32k flush=5s;
  • 自定义 log_format(务必包含上下游时间)

    log_format main '$remote_addr - $remote_user [$time_local] '
                  '"$request" $status $body_bytes_sent '
                  '"$http_referer" "$http_user_agent" '
                  'rt=$request_time '
                  'urt=$upstream_response_time '
                  'ua=$upstream_addr '
                  'us=$upstream_status '
                  'cs=$upstream_cache_status';

    $request_time 是从 Nginx 收到请求到返回响应的总耗时,$upstream_response_time 是后端服务器的处理耗时。两者的差值即为 Nginx 自身的处理开销。排查慢请求时这两个字段必不可少。

  • 按天切割日志(使用 logrotate)

    # 文件路径:/etc/logrotate.d/nginx
    /var/log/nginx/*.log {
      daily
      missingok
      rotate 30
      compress
      delaycompress
      notifempty
      create 0640 nginx adm
      sharedscripts
      postrotate
          [ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
      endscript
    }
  • 关闭不必要的访问日志

    # 健康检查、favicon 等请求不记录访问日志
    location /health {
      access_log off;
      return 200 "OK\n";
    }
    location = /favicon.ico {
      access_log off;
      log_not_found off;
    }

4.2 注意事项

4.2.1 配置注意事项

proxy_pass 末尾斜杠问题极易导致线上事故,修改配置前务必在测试环境充分验证。

  • location 匹配路径与 proxy_pass 的 URI 拼接规则容易混淆,修改前务必使用 curl -v 在测试环境验证实际转发路径。
  • nginx -t 命令仅检查配置文件语法,不检查后端服务器是否可达。修改配置后,应先执行 nginx -t,确认语法无误后再执行 nginx -s reload
  • reload 操作不会中断正在处理的连接,但如果新配置存在语法错误,reload 会失败且不会影响当前正在运行的配置。

4.2.2 常见错误与排查

错误现象 原因分析 解决方案
502 Bad Gateway 后端服务进程挂掉 / proxy_buffer_size 设置过小 / 后端连接数耗尽 检查后端服务进程状态;增大 proxy_buffer_size 至 16k 或 32k;检查并调整后端服务的最大连接数配置
504 Gateway Timeout proxy_read_timeout 设置过短 / 后端服务处理过慢或有慢查询 适当增大 proxy_read_timeout;优化后端应用代码或数据库慢查询
413 Request Entity Too Large 请求体大小超过 client_max_body_size 默认值 (1m) 根据业务需求调大,例如 client_max_body_size 50m;
414 Request-URI Too Large 请求 URI 过长,超过 large_client_header_buffers 限制 增大配置,例如 large_client_header_buffers 4 32k;
upstream timed out (110) 后端服务器响应超时 检查后端服务负载情况;适当增大 proxy_read_timeout
no live upstreams upstream 中所有后端节点均被标记为不可用 检查后端服务健康状态;调整 max_failsfail_timeout 参数

4.2.3 兼容性问题

  • HTTP/2 与 proxy_pass:Nginx 作为反向代理时,到后端服务器的连接只支持 HTTP/1.1,不支持 HTTP/2。proxy_http_version 1.1; 是必须的配置项,不要尝试设置为 2.0。
  • WebSocket 与 HTTP/2:在 HTTP/2 连接下代理 WebSocket 需要 Nginx 1.25.1+ 版本支持。低版本若需同时支持 HTTP/2 和 WebSocket,可能需要为 WebSocket 单独监听一个非 HTTP/2 的端口。
  • TLS 1.3 与旧客户端:Android 4.x、IE 10 及以下版本不支持 TLS 1.2。如果业务必须兼容这些老旧设备,则需要在 ssl_protocols 中保留 TLSv1.2
  • gzip 与 ETag:Nginx 对响应内容进行 gzip 压缩后,会移除强验证器 ETag(因为内容已被改变)。如果客户端或下游服务依赖 ETag 进行缓存验证,需要注意这一行为。

五、故障排查和监控

5.1 故障排查

5.1.1 日志查看命令

# 实时查看错误日志(故障排查第一步)
sudo tail -f /var/log/nginx/error.log

# 实时查看访问日志
sudo tail -f /var/log/nginx/access.log

# 查看最近 50 条 5xx 错误请求
awk '$9 ~ /^5/' /var/log/nginx/access.log | tail -50

# 统计各 HTTP 状态码的数量
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

# 查看慢请求(响应时间超过 3 秒)
awk -F'rt=' '{split($2,a," "); if(a[1]+0 > 3) print $0}' /var/log/nginx/access.log | tail -20

# 查看后端响应时间排序(找出最慢的后端服务器)
awk -F'urt=' '{split($2,a," "); if(a[1]+0 > 0) print a[1], $0}' /var/log/nginx/access.log | sort -rn | head -20

5.1.2 常见问题排查流程

问题一:502 Bad Gateway
这是最常见的问题,原因通常有三类:

# 1. 检查后端服务进程是否存活
curl -v http://10.0.1.10:8080/health

# 2. 检查后端服务器连接数是否耗尽
ss -s
ss -tnp | grep 8080 | wc -l

# 3. 检查 Nginx 错误日志中的具体报错信息
grep "502" /var/log/nginx/error.log | tail -20

解决方案

  1. 后端服务挂掉:重启后端服务,并检查其后端应用日志和系统日志,查找根本原因。
  2. 连接数耗尽:增大后端应用服务的最大连接数配置;在 Nginx 侧适当增大 upstream 的 keepalive 连接池大小。
  3. Buffer 不足:后端返回的 HTTP 头部过大(常见于携带大型 JWT 的 Set-Cookie 头),需增大 proxy_buffer_size 至 16k 或 32k。
    proxy_buffer_size 32k;
    proxy_buffers 4 32k;
    proxy_busy_buffers_size 64k;

问题二:upstream timed out (110: Connection timed out)

# 检查后端服务的实际响应时间
curl -o /dev/null -s -w "time_total: %{time_total}s\n" http://10.0.1.10:8080/api/slow-endpoint

# 检查 Nginx 当前的超时配置
nginx -T 2>/dev/null | grep -E "proxy_(connect|read|send)_timeout"

# 检查后端数据库或服务是否有慢查询
# 例如检查 MySQL 慢查询日志
tail -20 /var/log/mysql/slow.log

解决方案

  1. 后端服务处理过慢:优化后端应用代码、SQL 查询或引入缓存,这是根本解决之道。
  2. 临时调整:适当增大 proxy_read_timeout,但切勿无限制调大,以免掩盖真实问题。
  3. 差异化配置:对于报表生成等已知的慢接口,可单独设置更长的超时时间。
    location /api/reports/ {
        proxy_pass http://backend_api;
        proxy_read_timeout 300s;    # 报表类接口允许 5 分钟超时
    }

问题三:413 Request Entity Too Large

# 查看当前 Nginx 配置中 client_max_body_size 的值
nginx -T 2>/dev/null | grep client_max_body_size

解决方案

# 在 http 或 server 块进行全局设置
client_max_body_size 50m;

# 或针对特定的上传接口单独设置
location /api/upload {
    client_max_body_size 500m;
    proxy_pass http://backend_api;
    proxy_read_timeout 300s;     # 大文件上传耗时较长
    proxy_request_buffering off; # 禁用请求体缓冲,边接收边转发(节省 Nginx 内存)
}

问题四:SSL 证书问题排查

  • 症状:浏览器提示“证书不受信任”或“证书链不完整”。
  • 排查命令

    # 检查证书链是否完整
    openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | openssl x509 -noout -dates -subject -issuer
    
    # 检查证书链深度
    openssl s_client -connect example.com:443 -servername example.com -showcerts < /dev/null 2>/dev/null
    
    # 检查证书是否过期
    echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate
  • 解决方案
    1. 证书链不完整:确保 ssl_certificate 指令指向的是包含中间证书的 fullchain.pem 文件,而不是仅包含站点证书的 cert.pem
    2. SNI 配置错误:在托管多个 HTTPS 站点时,确保每个 server 块的 server_name 与证书签发的域名完全匹配。
    3. 证书过期:检查 Certbot 的自动续期服务是否正常运行:systemctl status certbot.timer

5.1.3 调试模式

# 临时开启 debug 级别日志(会产生大量日志,排查完毕后请立即关闭)
# 注意:Nginx 需在编译时已添加 --with-debug 参数
# 在 nginx.conf 的 main 段设置:
error_log /var/log/nginx/error.log debug;

# 仅对特定 IP 或网段开启 debug 连接日志(推荐,减少日志量)
events {
    debug_connection 10.0.0.100;
    debug_connection 192.168.1.0/24;
}

# 查看完整的 Nginx 配置(包含所有 include 文件展开后的结果)
nginx -T

# 测试配置文件语法
nginx -t

5.2 性能监控

5.2.1 stub_status 基础监控

Nginx 内置的 stub_status 模块可提供基础的连接状态信息,成本为零。

# 在 server 块中添加(务必限制只允许内网或本机访问)
server {
    listen 8080;
    server_name localhost;

    allow 10.0.0.0/8;
    allow 127.0.0.1;
    deny all;

    location /nginx_status {
        stub_status on;
        access_log off;
    }
}

访问验证:

curl http://127.0.0.1:8080/nginx_status

输出示例:

Active connections: 291
server accepts handled requests
 16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106

各字段含义

  • Active connections:当前活跃连接总数(包含 Waiting 状态的连接)。
  • accepts:Nginx 已经接受的总连接数。
  • handled:Nginx 已经处理的总连接数(正常情况下应等于 accepts)。
  • requests:Nginx 已经处理的总请求数(一个连接上可以发起多个请求)。
  • Reading:正在读取请求头的连接数。
  • Writing:正在向客户端发送响应的连接数。
  • Waiting:保持活跃(keep-alive)且当前空闲等待请求的连接数。

5.2.2 Prometheus + Grafana 监控

生产环境推荐使用 nginx-prometheus-exporter 采集指标,并集成到 Prometheus 和 Grafana 中。

  1. 安装并启动 nginx-prometheus-exporter

    wget https://github.com/nginxinc/nginx-prometheus-exporter/releases/download/v1.1.0/nginx-prometheus-exporter_1.1.0_linux_amd64.tar.gz
    tar -zxvf nginx-prometheus-exporter_1.1.0_linux_amd64.tar.gz
    sudo mv nginx-prometheus-exporter /usr/local/bin/
    # 启动 exporter,指向 stub_status 地址
    nginx-prometheus-exporter -nginx.scrape-uri=http://127.0.0.1:8080/nginx_status -web.listen-address=:9113
  2. 创建 systemd 服务文件 (/etc/systemd/system/nginx-exporter.service):

    [Unit]
    Description=Nginx Prometheus Exporter
    After=network.target
    
    [Service]
    Type=simple
    ExecStart=/usr/local/bin/nginx-prometheus-exporter \
        -nginx.scrape-uri=http://127.0.0.1:8080/nginx_status \
        -web.listen-address=:9113
    Restart=always
    RestartSec=5
    
    [Install]
    WantedBy=multi-user.target
  3. 在 Prometheus 配置中新增抓取任务 (prometheus.yml):

    scrape_configs:
      - job_name: 'nginx'
        static_configs:
          - targets: ['10.0.0.10:9113', '10.0.0.11:9113']
            labels:
              env: 'production'

5.2.3 关键监控指标说明

指标名称 正常范围 告警阈值 说明
Active connections < worker_connections 总数的 70% > 80% 接近上限时需要扩容服务器或优化配置
5xx 错误率 < 0.1% > 1% 持续高于 1% 通常表明后端服务存在严重问题
请求延迟 P99 < 500ms > 2s 通过 access_log 中的 $request_time 统计
upstream 响应时间 P99 < 300ms > 1s 通过 access_log 中的 $upstream_response_time 统计
连接丢弃率 0 > 0 accepts - handled > 0 说明有连接被异常丢弃

5.2.4 Prometheus 告警规则示例

# 文件路径:/etc/prometheus/rules/nginx-alerts.yml
groups:
  - name: nginx_alerts
    rules:
      - alert: NginxDown
        expr: nginx_up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Nginx 实例 {{ $labels.instance }} 宕机"

      - alert: NginxHighConnections
        expr: nginx_connections_active / nginx_connections_accepted > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Nginx 活跃连接数过高: {{ $value | humanizePercentage }}"

      - alert: NginxHighRequestRate
        expr: rate(nginx_http_requests_total[5m]) > 10000
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Nginx QPS 超过 10000: {{ $value | humanize }}"

六、总结

6.1 技术要点回顾

  • 反向代理配置proxy_pass 配合正确的 proxy_set_header 是基础,proxy_buffer_size 和各种超时参数是生产环境必须调优的项,proxy_pass 末尾斜杠的 URI 替换规则必须清晰掌握。
  • 负载均衡策略选择:无状态服务优先使用 least_conn;需要会话保持可选用 ip_hashhash;若后端有本地缓存,则一致性哈希是更优选择。健康检查参数 max_failsfail_timeout 应根据业务对故障的容忍度进行设置。
  • SSL/TLS 安全配置:只启用 TLS 1.2 和 1.3,配置 HSTS 和 OCSP Stapling,证书文件务必使用包含完整证书链的 fullchain.pem
  • 性能调优组合worker_processes auto + worker_connections 65535 + sendfile/tcp_nopush/tcp_nodelay 三件套 + 合理的 gzip 压缩 + open_file_cache,这套组合拳实测能让单机 QPS 提升 50% 以上。
  • 高可用架构:采用 Keepalived + VIP 实现双机热备,VIP 漂移可在 3-5 秒内完成,再结合 upstream 的健康检查机制,可实现后端故障的自动摘除与恢复。

6.2 进阶学习方向

  1. OpenResty / Lua 扩展:基于 lua-nginx-module 可以实现复杂的业务逻辑,如动态路由、自定义鉴权、复杂限流等,比纯 Nginx 配置灵活得多。
    • 学习资源:OpenResty 官方文档。
    • 实践建议:从简单的 access_by_lua_block 实现鉴权开始,逐步深入。
  2. Nginx Plus 商业版特性:主动健康检查、动态 upstream 管理、JWT 鉴权、实时监控 Dashboard 等高级功能。
    • 学习资源:Nginx Plus 官方文档。
    • 实践建议:如果团队规模较大、对系统稳定性和可观测性要求极高,商业版的特性值得投入。
  3. Service Mesh 与 Nginx:在 Kubernetes 环境中,Nginx Ingress Controller 是最常用的入口网关之一。理解其与 Service Mesh(如 Istio、Linkerd)的协作模式是云原生架构下的重要技能。

6.3 参考资料

  • Nginx 官方文档 nginx.org - 最权威的配置指令参考。
  • Mozilla SSL 配置生成器 ssl-config.mozilla.org - 可根据兼容性需求自动生成 SSL 配置。
  • 本文涉及的相关配置与脚本,可在 云栈社区 等技术论坛交流获取。

参考资料

[1] Nginx 反向代理与负载均衡配置详解(2026 最新版), 微信公众号:https://mp.weixin.qq.com/s/Z8zIe8JZk0RzQdfTKHuQ0g

版权声明:本文由 云栈社区 整理发布,版权归原作者所有。




上一篇:Spring IoC内核解析:基于DefaultListableBeanFactory构建最小依赖容器
下一篇:C++ lambda捕获列表实战解析:避坑按值[=]与按引用[&]陷阱
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-10 03:31 , Processed in 0.385496 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表