mcpField
N
nginx-ops

nginx-ops

github.com/0xdarkmatter云与 DevOps7837 次安装2 周前更新
代码仓库

关于 nginx-ops

Nginx Operations 是用于生产部署中配置和维护 Nginx 的参考技能,涵盖反向代理、TLS、负载均衡与性能调优。它还提供实用部署配置,并说明配置行为及常见问题。

主要功能

  1. 01反向代理配置模式
  2. 02SSL/TLS 设置与加固
  3. 03负载均衡策略
  4. 04性能调优指南
  5. 05Docker 和 SPA 配置
  6. 06配置陷阱与继承规则

适用场景

  1. 01配置 Nginx 作为反向代理
  2. 02为部署设置 TLS
  3. 03托管单页应用
  4. 04排查代理与请求头行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/0xdarkmatter/claude-mods --skill nginx-ops

安装后在 Claude Code 中输入/nginx-ops确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

assets/
.gitkeep
references/
performance.md
reverse-proxy.md
ssl-security.md
scripts/
.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/最长前缀优先(但仅在检查正则表达式之后)
评估算法
  1. 检查所有前缀 location,记下最长的匹配项
  2. 如果最长匹配项带有 ^~ 修饰符 → 使用该匹配项并停止
  3. 按配置文件中的顺序检查正则表达式 location → 首个匹配项生效
  4. 如果没有正则表达式匹配 → 使用步骤 1 中的最长前缀
  5. 优先检查 = /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 和 rootroot 会附加 location 路径;alias 会替换该路径。/img/ + root /data = /data/img/;alias /data/ = /data/当 location 路径不应出现在文件系统路径中时,使用 alias

参考文件

文件内容行数
reverse-proxy.md上游块、负载均衡、代理缓存、WebSocket/gRPC、超时、实际配置示例~650
ssl-security.mdTLS 配置、Let's Encrypt、HSTS、OCSP、安全请求头、速率限制、mTLS~550
performance.md工作进程调优、压缩、缓存、HTTP/2+3、静态文件、监控~550

另请参阅

评论

支持 Markdown