1006 lines
29 KiB
Markdown
1006 lines
29 KiB
Markdown
---
|
||
tags: [tech, infrastructure, web-server, proxy]
|
||
create time: 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 镜像,体积小且更新及时。开发环境直接用系统包管理器即可。
|
||
|
||
### 验证安装
|
||
|
||
```bash
|
||
nginx -v # 查看版本
|
||
nginx -t # 测试配置语法
|
||
systemctl status nginx # 查看服务状态
|
||
```
|
||
|
||
## 核心概念
|
||
|
||
### 架构模型
|
||
|
||
```mermaid
|
||
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`,采用**嵌套块结构**:
|
||
|
||
```nginx
|
||
# 全局块
|
||
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. 反向代理
|
||
|
||
最经典的用法——将客户端请求转发到后端服务。
|
||
|
||
```nginx
|
||
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 工作原理
|
||
> ```mermaid
|
||
> 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 还可以作为**反向缓存代理**,将后端的响应缓存在本地磁盘。
|
||
|
||
```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 可以将流量分发到不同节点。
|
||
|
||
```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)支持主动探测。
|
||
|
||
```nginx
|
||
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 层做配套设置:
|
||
|
||
```nginx
|
||
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 处理静态资源的优势,大幅减轻后端压力。
|
||
|
||
```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
|
||
|
||
```nginx
|
||
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 可以一键申请和续期免费证书:
|
||
> ```bash
|
||
> sudo certbot --nginx -d example.com
|
||
> ```
|
||
> Certbot 会自动帮你写入 Nginx 配置并完成 SSL 设置。
|
||
|
||
### 6. 读写分离 & 限流
|
||
|
||
```nginx
|
||
# 请求速率限制
|
||
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 的白名单和黑名单。
|
||
|
||
```nginx
|
||
# ── 场景 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 认证
|
||
|
||
为特定接口添加用户名密码登录。
|
||
|
||
```bash
|
||
# 生成密码文件(首次)
|
||
sudo htpasswd -c /etc/nginx/.htpasswd admin
|
||
# 后续添加用户去掉 -c 参数即可
|
||
sudo htpasswd /etc/nginx/.htpasswd developer
|
||
```
|
||
|
||
```nginx
|
||
location /api/internal/ {
|
||
auth_basic "Restricted Area"; # 弹窗提示标题
|
||
auth_basic_user_file /etc/nginx/.htpasswd;
|
||
|
||
proxy_pass http://backend;
|
||
}
|
||
```
|
||
|
||
## 安全加固
|
||
|
||
生产环境中的 Nginx 是第一道防线,需要做好基础安全设置。
|
||
|
||
```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;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 防盗链
|
||
|
||
```nginx
|
||
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 匹配优先级
|
||
|
||
```mermaid
|
||
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 匹配遵循严格优先级:
|
||
|
||
| 优先级 | 类型 | 写法 | 说明 |
|
||
|--------|------|------|------|
|
||
| ① | 精确 | `=` | 完全相等才命中 |
|
||
| ② | 最佳前缀 | `^~` | 最长前缀匹配后立即停止搜索正则 |
|
||
| ③ | 正则 | `~` / `~*` | 按配置文件中的**顺序**匹配,第一个命中就停止 |
|
||
| ④ | 普通前缀 | (无前缀符) | 记录最长匹配,但最终可能被正则覆盖 |
|
||
|
||
```nginx
|
||
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 压缩
|
||
|
||
```nginx
|
||
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; # 需要压缩的内容类型
|
||
}
|
||
```
|
||
|
||
### 日志配置
|
||
|
||
```nginx
|
||
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 四层负载均衡**。
|
||
|
||
```nginx
|
||
# 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 页面中嵌入其他页面的片段。
|
||
|
||
```nginx
|
||
location / {
|
||
ssi on; # 启用 SSI
|
||
ssi_silent_errors on; # 忽略子请求错误,不影响主响应
|
||
root /var/www;
|
||
}
|
||
```
|
||
|
||
HTML 中使用 `<!--#include -->` 指令:
|
||
|
||
```html
|
||
<!--# include virtual="/includes/header.html" -->
|
||
<div class="content">主内容区域</div>
|
||
<!--# include virtual="/includes/footer.html" -->
|
||
```
|
||
|
||
> [!CAUTION] 性能注意
|
||
> SSI 会为每个 include 发起内部子请求,频繁使用会增加响应延迟。现代架构中更推荐前端组件化方案(React/Vue)。仅在遗留系统维护时考虑 SSI。
|
||
|
||
---
|
||
|
||
## 性能调优
|
||
|
||
### 核心参数参考值
|
||
|
||
```nginx
|
||
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` | 避免频繁打开文件描述符 |
|
||
|
||
## 故障排查
|
||
|
||
### 常用命令
|
||
|
||
```bash
|
||
# 查看 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`,或优化后端查询性能。
|
||
> ```nginx
|
||
> proxy_connect_timeout 5s;
|
||
> proxy_send_timeout 10s;
|
||
> proxy_read_timeout 60s;
|
||
> ```
|
||
|
||
> [!ERROR] 413 Request Entity Too Large
|
||
> **原因**:上传文件超过 `client_max_body_size`。
|
||
> **解决**:在对应 location 中增大限制。
|
||
> ```nginx
|
||
> 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
|
||
# 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 + 后端服务的开发环境。
|
||
|
||
```yaml
|
||
# 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
|
||
# 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 可以实现基于权重或客户端特征的渐进式流量切分。
|
||
|
||
```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)。
|
||
|
||
```bash
|
||
# 安装 njs 模块
|
||
apt install nginx-module-njs # Debian/Ubuntu
|
||
```
|
||
|
||
```nginx
|
||
# 加载 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;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
```javascript
|
||
// 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 配合工作:
|
||
|
||
```mermaid
|
||
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)
|
||
|
||
## 关联笔记
|
||
|
||
- [[hhs/DEV/Docker/Docker.md]]
|
||
- [[hhs/DEV/Linux/Linux.md]]
|