This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/DEV/Nginx/Nginx.md
T
2026-05-18 21:09:55 +08:00

29 KiB
Raw Blame History

tags, create time
tags create time
tech
infrastructure
web-server
proxy
2026-05-18 20:30

Nginx

概述

Nginx(读作 "engine-x")是一个高性能的 HTTP 服务器和反向代理服务器,同时也支持 IMAP/POP3/SMTP 协议。由 Igor Sysoev 开发,以其高并发、低内存消耗闻名。

[!QUESTION] 为什么选择 Nginx?

  • 异步事件驱动架构:能轻松支撑数万并发连接
  • 轻量级:处理静态文件只需 Apache 1/5 的内存
  • 热部署:配置文件修改后可无缝 reload,不中断现有请求
  • 生态成熟:大量第三方模块和社区方案

核心用途:反向代理、负载均衡、静态资源服务、SSL/TLS 终止、API 网关。

安装

主流方式

方式 适用场景 命令示例
系统包管理器 快速部署、稳定优先 sudo apt install nginx / sudo yum install nginx
官方仓库 需要最新版本 添加 nginx.org 源后安装
Docker 容器化部署 docker run -d --name nginx -p 80:80 nginx:alpine
源码编译 需要自定义模块 ./configure --with-http_ssl_module && make && sudo make install

[!TIP] 生产环境推荐 使用官方仓库或 Docker Alpine 镜像,体积小且更新及时。开发环境直接用系统包管理器即可。

验证安装

nginx -v        # 查看版本
nginx -t        # 测试配置语法
systemctl status nginx   # 查看服务状态

核心概念

架构模型

graph LR
    Master["Master Process 管理进程"] --> WorkerA["Worker Process 1 处理请求"]
    Master --> WorkerB["Worker Process 2 处理请求"]
    Master --> WorkerC["Worker Process 3 处理请求"]
    WorkerA --> Conn1["Connection A"]
    WorkerA --> Conn2["Connection B"]
    WorkerB --> Conn3["Connection C"]
    WorkerB --> Conn4["Connection D"]
    WorkerC --> Conn5["Connection E"]
    style Master fill:#f9d,stroke:#333
    style WorkerA fill:#ccf,stroke:#333
    style WorkerB fill:#ccf,stroke:#333
    style WorkerC fill:#ccf,stroke:#333

Nginx 采用 Master-Worker 多进程模型:

  • Master 进程:负责读取配置、管理 Worker、平滑重载
  • Worker 进程:每个 Worker 独立运行,用事件驱动方式处理请求。一个请求不会在其他 Worker 中排队

[!NOTE] 关键问题 为什么不用多线程?因为线程切换开销大、锁竞争严重。Nginx 的事件驱动模型(epoll/kqueue)在 Linux/BSD 上能做到单个 Worker 处理上万并发连接,这就是著名的 **"C10K 问题"**解决方案之一。

配置文件结构

主配置文件通常是 /etc/nginx/nginx.conf,采用嵌套块结构:

# 全局块
worker_processes auto;          # Worker 数量(auto = CPU 核数)
error_log /var/log/nginx/error.log warn;

events {                         # events 块
    worker_connections 1024;     # 每个 Worker 的最大连接数
}

http {                           # http 块
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;

    # upstream 块 — 定义后端服务器池
    upstream backend {
        server 127.0.0.1:8080;
    }

    server {                     # server 块 — 虚拟主机
        listen 80;
        server_name example.com;

        location / {            # location 块 — URL 匹配路由
            proxy_pass http://backend;
        }
    }
}

配置文件加载顺序:全局块 → events 块 → http 块 → server 块 → location 块。外层指令对内层生效(继承关系)。

常用配置实战

1. 反向代理

最经典的用法——将客户端请求转发到后端服务。

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

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

        # 必须传递的信息头
        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 支持
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # ── Proxy Buffering(缓冲控制)──
        proxy_buffering on;               # 默认开启,先接收后端完整响应再发给客户端
        proxy_buffer_size 4k;             # 第一块缓冲区大小(通常存响应头)
        proxy_buffers 8 4k;               # 后续缓冲区数量和大小
        proxy_busy_buffers_size 8k;       # 忙缓冲区(一边发客户端一边收后端)
        # proxy_buffering off;            # 关闭缓冲 = 流式转发,适合 SSE/WebSocket
    }
}

[!NOTE] 反向代理 vs 正向代理

  • 正向代理:代理客户端,帮内网用户访问外网(如 VPN)
  • 反向代理:代理服务端,对外隐藏后端真实地址(Nginx 的主要角色)

[!NOTE] Proxy Buffering 工作原理

sequenceDiagram
    participant Client as 客户端
    participant Nginx as Nginx (Buffer)
    participant Backend as 后端服务
    Client->>Nginx: 请求
    Nginx->>Backend: 转发请求
    Backend-->>Nginx: 响应数据写入 Buffer
    Note over Nginx: 整个响应写完后...
    Nginx-->>Client: 从 Buffer 发送给客户端
    alt buffering off
        Nginx-->>Client: 实时转发 (Stream)
    end
  • 开启 buffering(默认):后端可以先完成处理再发送,Nginx 承担内存压力,提升吞吐
  • 关闭 buffering:实时转发,内存占用低但要求后端尽快返回;适合 SSE、大文件下载

Proxy Cache — 反向缓存

Nginx 还可以作为反向缓存代理,将后端的响应缓存在本地磁盘。

http {
    # 定义缓存区域
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:10m
                     max_size=1g inactive=60m use_temp_path=off;

    server {
        location /api/ {
            proxy_pass http://backend;

            # 启用缓存
            proxy_cache app_cache;                    # 使用哪个缓存区
            proxy_cache_key "$scheme$request_method$host$request_uri";  # 缓存 key
            proxy_cache_valid 200 302 10m;            # 200/302 响应缓存 10 分钟
            proxy_cache_valid 404 1m;                 # 404 缓存 1 分钟
            proxy_cache_valid any 5m;                 # 其他状态码缓存 5 分钟

            # 缓存命中时添加头部(方便调试)
            add_header X-Cache-Status $upstream_cache_status;

            # 绕过缓存的条件(未登录用户不走缓存)
            proxy_cache_bypass $http_authorization;
            proxy_no_cache $http_authorization;

            # 过期后仍向客户端提供旧缓存(stale),后台异步刷新
            proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
            proxy_cache_background_update on;
            proxy_cache_revalidate on;
        }
    }
}

X-Cache-Status 返回值说明:

值 含义
HIT 缓存命中,直接返回
MISS 缓存未命中,回源获取并缓存
EXPIRED 缓存过期,向后端重新验证
UPDATING 后台正在更新缓存,返回旧版本
BYPASS 被配置主动跳过

2. 负载均衡

当后端有多台服务器时,Nginx 可以将流量分发到不同节点。

upstream frontend {
    # 三种常用策略:

    # 轮询(默认)— 按顺序依次分配
    # server 10.0.0.1:8080;
    # server 10.0.0.2:8080;

    # 权重 — 数值越大分到的请求越多
    server 10.0.0.1:8080 weight=3;
    server 10.0.0.2:8080 weight=1;

    # IP Hash — 同一客户端 IP 固定分配到同一台后端
    # ip_hash;

    # 最少连接 — 分配到当前活跃连接最少的节点
    # least_conn;
}

server {
    listen 80;
    location / {
        proxy_pass http://frontend;
    }
}

常见策略对比:

策略 适用场景 注意
轮询 (round-robin) 通用场景 各节点性能相近时效果最好
加权轮询 (weight) 节点性能差异大 可根据硬件能力分配权重
IP Hash 有 Session 粘性需求 Cookie 登录可能失效
最少连接 (least_conn) 请求处理时间不均 长连接场景需留意偏差

3. 健康检查与后端管理

Nginx 社区版提供被动式健康检查(通过 fail_timeout 和 max_fails 参数),商业版(Plus)支持主动探测。

upstream backend {
    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;

    # 标记为 down — 不参与任何流量(常用于灰度发布逐步放量)
    # server 10.0.0.3:8080 down;

    # max_conns — 限制分配到该节点的并发数,超限时返回错误
    # server 10.0.0.4:8080 max_conns=100;

    # keepalive — 保持与后端的长连接,避免频繁建连开销
    keepalive 32;       # 每个 Worker 最多保持的空闲连接数
    keepalive_timeout 60s;
    keepalive_requests 100;
}

[!NOTE] passive health check 机制 max_fails=3 fail_timeout=30s 含义:在 30 秒内,某个后端失败超过 3 次,则该后端在 30 秒内被剔除。失败次数计数是从后端恢复后重新累加的。

使用 keepalive 时需要在 proxy 层做配套设置:

http {
    upstream backend {
        server 127.0.0.1:8080;
        keepalive 32;
    }

    server {
        location / {
            proxy_pass http://backend;

            # 关键:使用 HTTP/1.1 + Connection 头告诉 Nginx 复用连接
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
    }
}

[!QUESTION] 为什么需要 proxy_set_header Connection ""?

HTTP/1.0 默认关闭 keepalive,如果后端期望长连接,客户端(Nginx)发送的 Connection: close 会导致连接立即关闭,使 keepalive 配置形同虚设。清空该头部即可让 TCP 连接持续复用。

4. 静态文件服务

利用 Nginx 处理静态资源的优势,大幅减轻后端压力。

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

    # 根目录指向
    root /var/www/html;
    index index.html index.htm;

    # 精确匹配
    location /assets/ {
        expires 30d;                  # 浏览器缓存 30 天
        add_header Cache-Control "public, immutable";
        gzip_static on;               # 提供 .gz 预压缩文件
    }

    # 错误页面
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;
}

[!TIP] 静态文件优化要点

  • gzip_static on:如果预先生成了 .gz 文件,直接发送,节省 CPU
  • sendfile on:启用内核零拷贝,静态文件传输效率极高
  • expires:合理设置缓存头部,减少重复请求

5. HTTPS / SSL

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

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

    # 推荐的现代 TLS 配置
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

# HTTP → HTTPS 自动跳转
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

[!NOTE] Let's Encrypt 免费证书 用 Certbot 可以一键申请和续期免费证书:

sudo certbot --nginx -d example.com

Certbot 会自动帮你写入 Nginx 配置并完成 SSL 设置。

6. 读写分离 & 限流

# 请求速率限制
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

# 连接数限制
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

server {
    location /api/ {
        limit_req zone=api_limit burst=20 nodelay;  # 突发 20 个请求
        limit_conn conn_limit 10;                    # 每个 IP 最多 10 连接

        proxy_pass http://backend;
    }
}

7. IP 访问控制

通过 allow / deny 指令实现基于客户端 IP 的白名单和黑名单。

# ── 场景 1:管理后台仅允许内网访问 ──
location /admin/ {
    allow 10.0.0.0/8;       # 允许内网段
    allow 192.168.1.0/24;   # 允许办公网
    deny all;               # 拒绝其余所有 IP

    proxy_pass http://admin-backend;
}

# ── 场景 2:特定接口封禁恶意 IP ──
map $remote_addr $blocked_ip {
    default         0;
    192.168.1.100   1;    # 已知攻击者
    10.99.0.0/16    1;    # 整个 C 段封禁
}

server {
    location / {
        if ($blocked_ip) {
            return 403;     # 直接返回,不转发到后端
        }

        proxy_pass http://backend;
    }
}

[!TIP] 推荐做法 allow/deny 是在 Nginx 层面直接拦截(return 403),比让请求到达后端更安全高效。对于大名单封禁场景,使用 geo / map 模块配合数组匹配更灵活。

8. HTTP Basic Auth 认证

为特定接口添加用户名密码登录。

# 生成密码文件(首次)
sudo htpasswd -c /etc/nginx/.htpasswd admin
# 后续添加用户去掉 -c 参数即可
sudo htpasswd /etc/nginx/.htpasswd developer
location /api/internal/ {
    auth_basic "Restricted Area";          # 弹窗提示标题
    auth_basic_user_file /etc/nginx/.htpasswd;

    proxy_pass http://backend;
}

安全加固

生产环境中的 Nginx 是第一道防线,需要做好基础安全设置。

http {
    # ── 隐藏版本信息 ──
    server_tokens off;                     # response header 中不显示 Nginx 版本
    more_clear_headers Server;             # 需 headers-more 模块,完全抹除 Server 头

    # ── 安全响应头 ──
    add_header X-Frame-Options "SAMEORIGIN" always;           # 防止点击劫持
    add_header X-Content-Type-Options "nosniff" always;       # 禁止 MIME 嗅探
    add_header X-XSS-Protection "1; mode=block" always;      # XSS 过滤器
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # ── HSTS(强制 HTTPS)──
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # ── CORS 跨域支持 ──
    set $cors_origin "";
    if ($http_origin ~* "^https://(app\.example\.com|cdn\.example\.com)$") {
        set $cors_origin $http_origin;
    }

    add_header Access-Control-Allow-Origin $cors_origin always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With" always;

    location /api/ {
        # 处理浏览器预检请求 (OPTIONS)
        if ($request_method = OPTIONS) {
            return 204;
        }

        proxy_pass http://backend;
    }
}

防盗链

location ~* \.(jpg|jpeg|png|gif|webp|svg|ico)$ {
    root /var/www/static;

    # valid_referers — 只允许指定域名引用图片
    valid_referers none blocked server_names
                   *.example.com example.com;

    if ($invalid_referer) {
        # 非法引用时返回 403 或一张占位图
        return 403;
        # rewrite ^/ https://example.com/no-hotlink.png break;
    }
}

高级特性

Location 匹配优先级

graph TD
    A["URL请求到达"] --> B{"精确匹配\\n^~ ?"}
    B -->|"是"| E["使用精确匹配"]
    B -->|"否"| C{"正则匹配\\n按顺序?"}
    C -->|"第一个匹配"| F["使用正则匹配 \\n立即停止"]
    C -->|"无匹配"| D{"前缀最长匹配\\n是否有^~ ?"}
    D -->|"是"| G["使用前缀匹配 \\n跳过正则"]
    D -->|"否"| H["使用最长前缀匹配"]
    style E fill:#9f9,stroke:#333
    style F fill:#f99,stroke:#333
    style G fill:#9cf,stroke:#333
    style H fill:#ff9,stroke:#333

Location 匹配遵循严格优先级:

优先级 类型 写法 说明
① 精确 = 完全相等才命中
② 最佳前缀 ^~ 最长前缀匹配后立即停止搜索正则
③ 正则 ~ / ~* 按配置文件中的顺序匹配,第一个命中就停止
④ 普通前缀 (无前缀符) 记录最长匹配,但最终可能被正则覆盖
location = /exact-match {      # ① 精确匹配
    return 200 "exact";
}

location ^~ /static/ {         # ② 最佳前缀,跳过正则
    root /var/www;
}

location ~* \.(jpg|png|gif)$ { # ③ 正则,忽略大小写
    expires 7d;
}

location /images {             # ④ 普通前缀
    alias /var/www/images;
}

[!QUESTION] 思考题 请求 /images/logo.png 会被哪个 location 处理?

  • ① /images 和普通前缀
  • ② ~* \.(jpg|png|gif)$ 正则

答案:②。虽然 /images 的前缀更长,但正则匹配的优先级高于普通前缀。只有 ^~ 才能阻止正则匹配。

Gzip 压缩

http {
    gzip on;
    gzip_vary on;                      # 添加 Vary: Accept-Encoding
    gzip_proxied any;                  # 对所有代理请求都压缩
    gzip_comp_level 6;                 # 压缩级别 1-9,6 是性价比最优
    gzip_min_length 1k;                # 小于 1k 的不压缩

    gzip_types text/plain text/css application/json application/javascript
               text/xml application/xml application/xml+rss text/javascript
               image/svg+xml;           # 需要压缩的内容类型
}

日志配置

http {
    # 自定义日志格式
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    '$request_time $upstream_response_time';

    access_log /var/log/nginx/access.log main;
    error_log  /var/log/nginx/error.log warn;

    # 按小时分割日志
    map $time_iso8601 $logdate {
        ~^(\d{4}-\d{2}-\d{2}) %Y-%m-%d;
    }
}

[!NOTE] 常用变量速查

变量 含义
$remote_addr 客户端真实 IP
$request_uri 原始请求 URI(含参数)
$status 响应状态码
$body_bytes_sent 发送给客户端的字节数
$request_time 请求处理总耗时(秒)
$upstream_response_time 后端响应耗时
$http_host Host 请求头
$https 是否 HTTPS(on/off)

Stream 模块 — L4 四层代理

Nginx 不仅可以代理 HTTP(L7),通过 stream 块还可以做 TCP/UDP 四层负载均衡。

# stream 块在 http 块同级
stream {
    # SSH 连接到内网跳板机
    upstream ssh_backend {
        server 10.0.0.5:22;
        server 10.0.0.6:22;
    }

    # Redis / MySQL 等数据库代理
    upstream redis_backend {
        least_conn;
        server 10.0.1.10:6379;
        server 10.0.1.11:6379;
    }

    server {
        listen 8022;           # 对外暴露 SSH 端口
        proxy_pass ssh_backend;
        proxy_timeout 30s;
    }

    server {
        listen 6379;           # 对外暴露 Redis 端口
        proxy_pass redis_backend;
    }
}

http {
    # ... HTTP 配置照常
}

[!NOTE] Stream vs HTTP

维度 http {} 块 stream {} 块
工作层级 L7 应用层 L4 传输层
协议支持 HTTP、HTTPS TCP、UDP
可解析内容 URL、Header、Cookie 无法解析应用数据
典型场景 Web 代理、静态服务 SSH/DB 代理、游戏服务器

SSI — 服务端包含

Nginx 内置 SSI 支持,可以在 HTML 页面中嵌入其他页面的片段。

location / {
    ssi on;                          # 启用 SSI
    ssi_silent_errors on;            # 忽略子请求错误,不影响主响应
    root /var/www;
}

HTML 中使用 <!--#include --> 指令:

<!--# include virtual="/includes/header.html" -->
<div class="content">主内容区域</div>
<!--# include virtual="/includes/footer.html" -->

[!CAUTION] 性能注意 SSI 会为每个 include 发起内部子请求,频繁使用会增加响应延迟。现代架构中更推荐前端组件化方案(React/Vue)。仅在遗留系统维护时考虑 SSI。


性能调优

核心参数参考值

worker_processes auto;                    # 等于 CPU 核心数

events {
    worker_connections 4096;               # 根据预期并发调整
    use epoll;                             # Linux 默认就是 epoll,可显式指定
}

http {
    sendfile on;                           # 零拷贝传输静态文件
    tcp_nopush on;                         # 与 sendfile 配合,减少数据包数量
    tcp_nodelay on;                        # 禁用 Nagle 算法,降低延迟

    keepalive_timeout 65;                  # 保持连接超时
    client_max_body_size 20m;              # 上传文件大小限制

    open_file_cache max=10000 inactive=20s;  # 文件描述符缓存
    open_file_cache_valid 30s;
    open_file_cache_min_uses 2;
}

调优原则

方向 方法 效果
并发提升 worker_connections + worker_processes 最大并发 ≈ workers × connections
I/O 优化 sendfile + tcp_nopush 静态文件传输减少 30%~50%
连接复用 keepalive_timeout + proxy_keepalive 减少 TCP 握手开销
磁盘 I/O open_file_cache 避免频繁打开文件描述符

故障排查

常用命令

# 查看 Nginx 进程信息
ps aux | grep nginx

# 重新加载配置(零停机)
sudo nginx -s reload

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

# 查看监听端口
sudo ss -tlnp | grep nginx

# 实时查看错误日志
sudo tail -f /var/log/nginx/error.log

# 检查配置中包含的所有 server_name
grep -r server_name /etc/nginx/

常见问题速查

[!ERROR] 502 Bad Gateway 原因:后端服务未启动或无法连接。 排查:确认后端服务正常运行;检查 proxy_pass 的地址和端口;查看后端日志。

[!ERROR] 504 Gateway Timeout 原因:后端响应超时(默认 60 秒)。 解决:增加 proxy_read_timeout,或优化后端查询性能。

proxy_connect_timeout 5s;
proxy_send_timeout    10s;
proxy_read_timeout    60s;

[!ERROR] 413 Request Entity Too Large 原因:上传文件超过 client_max_body_size。 解决:在对应 location 中增大限制。

client_max_body_size 100m;

[!WARNING] 403 Forbidden(静态文件) 常见原因:权限不足或 index 文件不存在。 解决:确认 Nginx 用户(www-data/nginx)对目录有读权限;确认文件存在。

配置管理与部署

配置文件拆分

生产环境中,不建议把所有配置写在一个大文件中。利用 include 实现模块化拆分。

/etc/nginx/
├── nginx.conf              # 主配置(全局 + events + http)
├── conf.d/
│   ├── api.conf            # API 代理配置
│   ├── web.conf            # Web 前端配置
│   └── static.conf         # 静态资源服务配置
├── sites-available/        # 可用站点配置(软链到 enabled)
│   ├── example.com
│   └── staging.example.com
├── sites-enabled/          # 已启用站点
├── upstream/
│   ├── backend-pool.conf   # 后端服务器池定义
│   └── cache.conf
├── snippets/               # 可复用片段
│   ├── ssl-params.conf     # SSL 安全参数
│   ├── rate-limit.conf     # 限流规则
│   └── security-headers.conf
└── mime.types
# nginx.conf — 主入口保持精简
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log warn;

events { worker_connections 1024; }

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

    # 引入所有 upstream 定义
    include /etc/nginx/upstream/*.conf;

    # 引入所有 server 配置
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

[!TIP] 为什么这样设计?

  • conf.d/ 下的 .conf 文件按功能划分,每个文件一个主题
  • sites-available / sites-enabled 模式源自 Apache:修改后通过 ln -s 启用,方便回滚
  • snippets/ 存放通用模板,避免多处复制粘贴同一份 SSL 或安全配置

Docker Compose 完整示例

用 Docker Compose 一键拉起 Nginx + 后端服务的开发环境。

# docker-compose.yml
services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/certs:/etc/nginx/certs:ro
      - ./frontend/dist:/usr/share/nginx/html:ro
      - nginx-cache:/var/cache/nginx
      - nginx-run:/var/run
    depends_on:
      - api
      - webapp
    restart: unless-stopped

  api:
    build: ./api
    environment:
      - NODE_ENV=production
      - DATABASE_URL=${DATABASE_URL}
    expose:
      - "3000"
    restart: unless-stopped

  webapp:
    build: ./webapp
    expose:
      - "8080"
    restart: unless-staged

volumes:
  nginx-cache:
  nginx-run:
# nginx/conf.d/default.conf
upstream api_backend {
    server api:3000;
}

server {
    listen 80;
    server_name _;

    location /api/ {
        proxy_pass http://api_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;  # SPA 路由兜底
    }
}

[!NOTE] Docker Nginx 注意事项

  • 挂载配置时使用 :ro 只读标志,防止容器内被篡改
  • /var/run 和 /var/cache/nginx 需要用 volume,否则重载时会因缺少 pid 文件而报错
  • SPA 应用需要 try_files 兜底到 index.html,否则直接刷新子路由会 404

灰度发布(Canary Release)

Nginx 可以实现基于权重或客户端特征的渐进式流量切分。

http {
    # 基于请求头实现灰度(前端在特定版本时携带 x-canary: true)
    map $http_x_canary $canary_upstream {
        default "";
        "true"  canary_group;
    }

    # 基于用户 IP 的哈希分流(同一 IP 始终访问同一版本)
    geo $hash_upstream {
        default     production_pool;
        10.0.0.0/8  canary_group;     # 仅内部 IP 走新版
    }

    upstream production_pool {
        server 10.1.0.1:8080;
        server 10.1.0.2:8080;
    }

    upstream canary_group {
        server 10.2.0.1:8080;         # 新版本仅一台机器测试
    }

    server {
        location / {
            # 优先使用 header 分流,其次 IP hash,最后默认生产
            set $target $canary_upstream;
            if ($target = "") {
                set $target $hash_upstream;
            }
            if ($target = "") {
                set $target production_pool;
            }

            proxy_pass http://$target;
        }
    }
}

[!TIP] 灰度发布常见策略

阶段 流量分配 适用场景
内部测试 100% 内部 IP → 新版 开发团队自测
小流量灰度 1% ~ 5% 随机 → 新版 验证核心指标
逐步放量 逐步提升 10% → 50% → 100% A/B 结果良好
全量上线 100% → 新版 灰度结束

NJS — Nginx JavaScript 脚本

Nginx 支持嵌入 JavaScript 代码,实现动态配置逻辑(无需 reload)。

# 安装 njs 模块
apt install nginx-module-njs   # Debian/Ubuntu
# 加载 JS 脚本
load_module /usr/lib/nginx/modules/ngx_http_js_module.so;

http {
    js_import filter.js;

    server {
        location / {
            js_content filter.add_custom_header;
            proxy_pass http://backend;
        }
    }
}
// filter.js
export function addCustomHeader(r) {
    r.headersOut["X-Custom-Version"] = "v2.1";
    r.headersOut["X-Served-By"] = r.serverName;
    r.sendHeader();
    r.finish();
}

export default { addCustomHeader };

[!NOTE] NJS vs OpenResty

  • NJS:轻量级,适合简单逻辑(改 Header、条件路由),语法受限但性能极高
  • OpenResty(Nginx + Lua):完整的编程平台,可以操作数据库、调用外部 API,适合复杂网关场景

与 CDN 的配合

典型的生产架构中,Nginx 常作为 边缘节点 或 回源层 与 CDN 配合工作:

sequenceDiagram
    participant Client as 客户端
    participant CDN as CDN 边缘节点
    participant Nginx as Nginx 边缘/回源
    participant App as 应用后端

    Client->>CDN: 请求静态资源
    CDN->>CDN: 缓存命中?
    alt 命中
        CDN-->>Client: 返回缓存内容
    else 未命中
        CDN->>Nginx: 回源请求
        Nginx->>App: 转发请求
        App-->>Nginx: 响应数据
        Nginx-->>CDN: 返回内容并缓存
        CDN-->>Client: 返回内容
    end

[!TIP] Nginx + CDN 的最佳实践

  • 静态资源走 CDN,动态请求回源到 Nginx + 后端
  • Nginx 可以作为 CDN 的边缘服务器,做一层统一入口
  • 通过 X-Forwarded-For 追踪真实的用户 IP(即使经过了 CDN)

关联笔记