--- 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 中使用 `` 指令: ```html