关于 nginx-ops
Nginx Operations 是用于生产部署中配置和维护 Nginx 的参考技能,涵盖反向代理、TLS、负载均衡与性能调优。它还提供实用部署配置,并说明配置行为及常见问题。
主要功能
- 01反向代理配置模式
- 02SSL/TLS 设置与加固
- 03负载均衡策略
- 04性能调优指南
- 05Docker 和 SPA 配置
- 06配置陷阱与继承规则
适用场景
- 01配置 Nginx 作为反向代理
- 02为部署设置 TLS
- 03托管单页应用
- 04排查代理与请求头行为
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ 0xdarkmatter/ claude-mods --skill nginx-ops安装后在 Claude Code 中输入/nginx-ops确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/.gitkeepreferences/performance.mdreverse-proxy.mdssl-security.mdscripts/.gitkeep详细介绍
Nginx 运维
面向生产环境部署的全面 Nginx 配置、反向代理模式、SSL/TLS 加固、负载均衡策略和性能优化。
配置架构速查
nginx.conf (main context)
├── worker_processes auto;
├── worker_rlimit_nofile 65535;
│
├── events { # Connection handling
│ ├── worker_connections 4096;
│ └── multi_accept on;
│ }
│
├── http { # HTTP server settings
│ ├── include mime.types;
│ ├── default_type application/octet-stream;
│ ├── sendfile on;
│ ├── gzip on;
│ │
│ ├── upstream backend { # Load balancing pool
│ │ └── server 127.0.0.1:3000;
│ │ }
│ │
│ ├── server { # Virtual host
│ │ ├── listen 443 ssl;
│ │ ├── server_name example.com;
│ │ │
│ │ ├── location / { # Request routing
│ │ │ └── proxy_pass http://backend;
│ │ │ }
│ │ │
│ │ └── location /static/ {
│ │ └── root /var/www;
│ │ }
│ │ }
│ │
│ └── include /etc/nginx/conf.d/*.conf;
│ }
│
└── stream { # TCP/UDP proxying (optional)
└── server { ... }
}
指令继承规则
| 规则 | 行为 | 示例 |
|---|---|---|
| 向下继承 | 子块继承父级指令 | http 中的 gzip on; 适用于所有 server 块 |
| 覆盖 | 子指令覆盖父级指令 | location 中的 gzip off; 会覆盖 http 级别的 gzip on; |
| 数组指令 | 不会继承,必须重新声明 | location 中的 proxy_set_header 会替换 server 中的所有请求头 |
| 不向上继承 | 内部块永远不会影响外部块 | location 级别的设置不会影响 server |
重要: 在子块中重新定义数组类型指令(proxy_set_header、add_header、proxy_hide_header)时,会完全替换而非合并。如果在某个 location 中设置了一条 proxy_set_header,就必须重新声明所有这类指令。
反向代理决策树
Need to proxy requests?
│
├─ Single backend server?
│ └─ Use simple proxy_pass
│ proxy_pass http://127.0.0.1:3000;
│
├─ Multiple backend servers?
│ │
│ ├─ Need session persistence?
│ │ ├─ By client IP → ip_hash
│ │ └─ By cookie → sticky cookie (Nginx Plus)
│ │
│ ├─ Backends have unequal capacity?
│ │ └─ Use weight parameter
│ │ server backend1:3000 weight=3;
│ │ server backend2:3000 weight=1;
│ │
│ ├─ Want fewest active connections?
│ │ └─ least_conn
│ │
│ ├─ Want even random distribution?
│ │ └─ random two least_conn
│ │
│ └─ Default (no special needs)?
│ └─ round-robin (default, no directive needed)
│
├─ WebSocket connections?
│ └─ Add Upgrade + Connection headers
│ proxy_set_header Upgrade $http_upgrade;
│ proxy_set_header Connection "upgrade";
│
├─ gRPC backend?
│ └─ Use grpc_pass grpc://backend;
│
└─ Streaming / Server-Sent Events?
└─ Disable buffering
proxy_buffering off;
SSL/TLS 快速入门
使用 Certbot 配置 Let's Encrypt
# Install certbot
sudo apt install certbot python3-certbot-nginx # Debian/Ubuntu
sudo dnf install certbot python3-certbot-nginx # RHEL/Fedora
# Obtain certificate (nginx plugin - easiest)
sudo certbot --nginx -d example.com -d www.example.com
# Obtain certificate (webroot - no nginx restart)
sudo certbot certonly --webroot -w /var/www/html -d example.com
# Test auto-renewal
sudo certbot renew --dry-run
最简生产环境 SSL 配置
server {
listen 443 ssl http2;
server_name example.com;
# Certificates
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Modern TLS (1.2 + 1.3)
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;
ssl_prefer_server_ciphers off;
# HSTS (2 years)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# Session caching
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
root /var/www/example.com;
index index.html;
}
# HTTP → HTTPS redirect
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
Location 匹配顺序
Nginx 会按照特定的优先级顺序评估 location 块,而不是按照它们在配置文件中的出现顺序。
| 优先级 | 修饰符 | 类型 | 示例 | 行为 |
|---|---|---|---|---|
| 1 | = | 精确匹配 | location = /favicon.ico | 匹配后立即停止搜索 |
| 2 | ^~ | 前缀(不使用正则表达式) | location ^~ /static/ | 如果此前缀匹配,则停止搜索(跳过正则表达式) |
| 3 | ~ | 正则表达式(区分大小写) | location ~ \.php$ | 首个匹配的正则表达式生效 |
| 3 | ~* | 正则表达式(不区分大小写) | location ~* \.(jpg|png)$ | 首个匹配的正则表达式生效 |
| 4 | (无) | 前缀 | location /api/ | 最长前缀优先(但仅在检查正则表达式之后) |
评估算法
- 检查所有前缀
location,记下最长的匹配项 - 如果最长匹配项带有
^~修饰符 → 使用该匹配项并停止 - 按配置文件中的顺序检查正则表达式
location→ 首个匹配项生效 - 如果没有正则表达式匹配 → 使用步骤 1 中的最长前缀
- 优先检查
= /path,如果匹配则立即生效
示例
location = / { } # Only exact "/"
location / { } # Catch-all prefix
location /api/ { } # Prefix: /api/*
location ^~ /static/ { } # Prefix, skip regex: /static/*
location ~ \.php$ { } # Regex: any .php file
location ~* \.(gif|jpg)$ { } # Case-insensitive regex: images
| 请求 URI | 匹配的 Location | 原因 |
|---|---|---|
/ | = / | 精确匹配(优先级 1) |
/index.html | / | 最长前缀,没有正则表达式匹配 |
/api/users | /api/ | 最长前缀,没有正则表达式匹配 |
/static/logo.png | ^~ /static/ | ^~ 会跳过正则表达式检查 |
/app/index.php | ~ \.php$ | 正则表达式优先于前缀 |
/photos/cat.jpg | ~* \.(gif|jpg)$ | 正则表达式优先于前缀 |
常见配置
SPA 路由(React、Vue、Angular)
server {
listen 80;
server_name app.example.com;
root /var/www/app/dist;
index index.html;
# Serve static files directly, fall back to index.html for SPA routes
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets aggressively
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
WebSocket 代理
location /ws/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400s; # Keep WebSocket alive for 24h
proxy_send_timeout 86400s;
}
速率限制
# Define zone: 10MB shared memory, 10 requests/second per IP
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
# Allow burst of 20, process excess without delay up to burst
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
}
Gzip 压缩
http {
gzip on;
gzip_comp_level 5; # Balance CPU vs compression (1-9)
gzip_min_length 256; # Don't compress tiny responses
gzip_vary on; # Vary: Accept-Encoding header
gzip_proxied any; # Compress proxied responses too
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml;
}
静态文件服务
location /static/ {
alias /var/www/static/; # Note: alias, not root (includes /static/ path)
expires 30d;
add_header Cache-Control "public, no-transform";
# Disable access log for static files
access_log off;
# Enable open file cache
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
}
CORS 请求头
location /api/ {
# CORS headers
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
add_header Access-Control-Max-Age 86400 always;
# Handle preflight requests
if ($request_method = OPTIONS) {
return 204;
}
proxy_pass http://backend;
}
Docker 模式
在 Docker Compose 中将 Nginx 用作反向代理
# docker-compose.yml
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- app
networks:
- webnet
app:
build: .
expose:
- "3000" # Internal only, not published to host
networks:
- webnet
networks:
webnet:
# nginx.conf for docker-compose (use service name as hostname)
upstream app_backend {
server app:3000; # Docker DNS resolves service name
}
server {
listen 80;
location / {
proxy_pass http://app_backend;
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;
}
}
使用静态资源的多阶段构建
# Stage 1: Build frontend
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Serve with nginx
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
# nginx.conf for containerized SPA
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
# SPA routing
location / {
try_files $uri $uri/ /index.html;
}
# Health check endpoint
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
# Cache busted assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
常见陷阱
| 陷阱 | 原因 | 修复方法 |
|---|---|---|
proxy_pass 中的尾部斜杠 | proxy_pass http://backend 会原样保留 /api/users;proxy_pass http://backend/ 会移除匹配的 location 前缀 | 有意选择:要移除前缀就加 /,要保留前缀就不加 |
| 缺少代理请求头 | 后端看到的是 nginx 的 IP,而不是客户端的 IP。这会影响身份验证、日志记录和地理位置检测 | 始终设置 X-Real-IP、X-Forwarded-For、X-Forwarded-Proto 和 Host |
| 缓冲区大小错误(502) | 较大的请求头(Cookie、JWT)超出默认缓冲区大小 | 增大 proxy_buffer_size 8k; 和 proxy_buffers 4 16k; |
worker_connections 设置过低 | 默认值为 512 或 1024;每个客户端会使用 2 个连接(客户端 + 上游) | 设置 worker_connections 4096;,并提高 worker_rlimit_nofile |
try_files 与 proxy_pass 配合使用 | 在同一个 location 中同时使用 try_files 和 proxy_pass 时,效果可能不符合预期 | 使用 try_files $uri @backend;,并通过命名位置进行代理 |
| “if 有害” | 在 location 中使用 if 会创建隐式嵌套位置,从而破坏指令的行为 | 使用 map 实现基于变量的逻辑;仅在 return/rewrite 中使用 if |
| 动态上游的解析器 | proxy_pass 中的变量(例如 $upstream)会绕过启动时的 DNS 解析 | 添加 resolver 127.0.0.11 valid=30s;(Docker)或 resolver 1.1.1.1; |
缺少 index 指令 | 访问目录而不是索引文件时会返回 403 Forbidden | 在 server 或 location 块中添加 index index.html; |
| 套接字权限被拒绝 | Nginx 工作进程无法读取上游 Unix 套接字 | 确保 nginx 用户属于套接字所在的组;对套接字执行 chmod 660 |
gzip 导致 Content-Encoding 重复 | 上游已进行压缩,而 nginx 的 gzip 又进行了压缩 | 谨慎使用 gzip_proxied,或设置 proxy_set_header Accept-Encoding ""; |
add_header 不会继承 | 在 location 中添加任何 add_header 都会丢弃父级的所有 add_header 指令 | 在子块中重新声明所有请求头,或使用 include 共享请求头配置 |
混淆 alias 和 root | root 会附加 location 路径;alias 会替换该路径。/img/ + root /data = /data/img/;alias /data/ = /data/ | 当 location 路径不应出现在文件系统路径中时,使用 alias |
参考文件
| 文件 | 内容 | 行数 |
|---|---|---|
| reverse-proxy.md | 上游块、负载均衡、代理缓存、WebSocket/gRPC、超时、实际配置示例 | ~650 |
| ssl-security.md | TLS 配置、Let's Encrypt、HSTS、OCSP、安全请求头、速率限制、mTLS | ~550 |
| performance.md | 工作进程调优、压缩、缓存、HTTP/2+3、静态文件、监控 | ~550 |
另请参阅
- docker-ops - 容器编排、docker-compose 模式
- security-ops - 应用安全、身份验证模式
- ci-cd-ops - 部署流水线、零停机部署
- Nginx 官方文档
- Mozilla SSL 配置生成器
- Nginx 配置生成器
评论
支持 Markdown