Skip to content

Nginx

反向代理、location 匹配、HTTPS 配置、性能调优等核心知识。

基本概念

Nginx 配置由指令组成,分为简单指令(以 ; 结尾)和块指令(以 {} 包裹)。配置文件层级如下:

作用范围常见指令
main全局(文件顶层)worker_processeserror_log
events连接处理模型worker_connectionsuse epoll
httpHTTP 服务全局gzipssl_protocolslimit_req_zone
server单个虚拟主机listenserver_nameroot
location匹配特定请求路径proxy_passreturnrewrite
upstream后端服务器组serverkeepaliveip_hash

子块可以继承父块指令,但 add_header 等部分指令不继承,需在子块重新声明。

安装

bash
# Debian / Ubuntu
sudo apt update
sudo apt install nginx

# CentOS / RHEL(先添加官方源,获取新版本)
sudo yum install epel-release
sudo yum install nginx

# 启动并设置开机自启
sudo systemctl enable --now nginx

# 验证安装
nginx -v

location 匹配规则

精确匹配 (=)

只有当请求的 URI 完全匹配指定字符串时才生效,优先级最高,匹配后立即停止搜索。

nginx
location = / {
    # 只匹配根路径 "/"
    root /usr/share/nginx/html;
    index index.html index.htm;
}

请求示例:http://example.com/ ✓   http://example.com/index.html


前缀匹配 (^~)

URI 以指定字符串开头时匹配,匹配成功后停止搜索正则规则(不会被正则覆盖)。

nginx
location ^~ /img/ {
    # 匹配以 "/img/" 开头的请求,停止搜索其他规则
    root /data;
}

请求示例:/img/a.jpg ✓   /img/b.mp4 ✓   /Img/a.jpg ✗(大小写敏感)


正则匹配 (~, ~*)

nginx
# 区分大小写(~)
location ~ /Example/ {
    # 匹配路径中含 "/Example/" 的请求
    root /data;
}
# http://example.com/Example/logo.png ✓
# http://example.com/example/logo.png ✗

# 不区分大小写(~*)
location ~* /Example/ {
    root /data;
}
# http://example.com/Example/logo.png ✓
# http://example.com/example/logo.png ✓

多个正则规则按配置文件顺序依次匹配,先匹配到的先生效。


通用匹配 (/)

匹配任何请求,优先级最低,其他规则都不匹配时才走此规则。

nginx
location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
}

内部重定向 (@)

通常用于错误处理的内部跳转,@名称不对外暴露,客户端不可直接访问。

nginx
location /img/ {
    error_page 404 = @img_err;  # 找不到资源时跳转
}

location @img_err {
    return 503;
}

优先级总结

类型符号优先级
精确匹配=最高
前缀匹配(停止搜索)^~次高
正则匹配(大小写敏感)~按顺序
正则匹配(大小写不敏感)~*按顺序
普通前缀匹配无符号次低
通用匹配/最低

rewrite 规则

正则常用符号

rewrite 规则依赖正则表达式匹配和捕获分组,常用符号:

符号含义
.匹配除换行符外的任意单个字符
*匹配前面元素零次或多次
+匹配前面元素一次或多次
?匹配前面元素零次或一次
^匹配字符串开头
$匹配字符串结尾
()捕获分组,在替换串中用 $1$2 引用
[]定义字符集,如 [0-9]
\转义特殊字符

标志位

标志位说明
last停止当前 location 的其他指令,重新查找 location
break终止当前 rewrite 规则,不重新查找 location
redirect返回 302 临时重定向(客户端可见地址变化)
permanent返回 301 永久重定向(浏览器会缓存,慎用)

rewrite 指令与 if 指令

nginx
# 语法
rewrite <正则> <替换URL> [标志位];

# if 按条件触发 rewrite
if (条件) {
    rewrite ...;
}

实践案例

nginx
# URL 美化:将查询参数转为静态风格 URL
# /article/123/my-title → /index.php?id=123&title=my-title
location /article/ {
    rewrite ^/article/([0-9]+)/([^/]+)$ /index.php?id=$1&title=$2 last;
}

# 移动端重定向:检测 UA 跳转到 m. 子域
if ($http_user_agent ~* "(android|iphone|ipad)") {
    rewrite ^(.*)$ http://m.example.com$1 permanent;
}

# 隐藏 .html 扩展名(所有 .html 请求 301 到无扩展名版本)
location / {
    rewrite ^/(.*)\.html$ /$1 permanent;
}

# 维护页面:所有请求返回 503 并展示维护页
location / {
    error_page 503 = @maintenance;
    return 503;
}
location @maintenance {
    rewrite ^(.*)$ /maintenance.html break;
}

HTTPS / TLS 配置

证书申请(Let's Encrypt)

bash
# 安装 certbot
sudo apt install certbot python3-certbot-nginx

# 申请证书(--nginx 参数自动修改 nginx 配置)
sudo certbot --nginx -d example.com -d www.example.com

# 测试自动续期(certbot 会自动配置 cron/systemd timer)
sudo certbot renew --dry-run

TLS 推荐配置

nginx
# 在 http 块中定义全局 SSL 参数
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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;   # TLS 1.3 下应为 off

# Session 缓存(减少握手开销)
ssl_session_cache   shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;         # 禁用以增强前向保密

# OCSP Stapling(加速证书验证)
ssl_stapling        on;
ssl_stapling_verify on;
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 5s;

# DH 参数(防止 Logjam 攻击)
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
# 生成:openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048

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

    ssl_certificate         /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key     /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
}

# HTTP 强制跳转 HTTPS
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

安全响应头

nginx
server {
    # 防止点击劫持
    add_header X-Frame-Options           "SAMEORIGIN"    always;
    # 防止 MIME 类型嗅探
    add_header X-Content-Type-Options    "nosniff"       always;
    # XSS 过滤(旧浏览器兼容)
    add_header X-XSS-Protection          "1; mode=block" always;
    # Referrer 策略
    add_header Referrer-Policy           "strict-origin-when-cross-origin" always;
    # 内容安全策略(按业务调整)
    add_header Content-Security-Policy   "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" always;
    # HSTS(仅 HTTPS,max-age 单位秒)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    # 禁用不必要的浏览器特性
    add_header Permissions-Policy        "camera=(), microphone=(), geolocation=()" always;
}

add_header 在子 location 不继承父块配置,建议统一在 server 块使用 always 参数。

HTTP/2 与 HTTP/3

启用 HTTP/2

nginx
server {
    listen 443 ssl http2;
    # ...
}

启用 HTTP/3(需要 nginx >= 1.25.0)

nginx
server {
    listen 443 ssl;
    listen 443 quic reuseport; # UDP,用于 HTTP/3
    http2 on;
    http3 on;

    ssl_certificate     /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    # 告知客户端支持 HTTP/3
    add_header Alt-Svc 'h3=":443"; ma=86400';
}

负载均衡

基本配置

nginx
upstream backend {
    # 默认轮询(Round Robin)
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

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

负载均衡策略

nginx
upstream backend {
    # 加权轮询
    server 192.168.1.10:8080 weight=3;
    server 192.168.1.11:8080 weight=1;

    # ip_hash(同一 IP 始终转发到同一后端,适合有状态应用)
    # ip_hash;

    # least_conn(转发到当前连接数最少的后端)
    # least_conn;

    # 被动健康检查
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;

    # 备用节点(主节点全部不可用时才启用)
    server 192.168.1.12:8080 backup;

    # keepalive 连接池(减少 TCP 握手开销)
    keepalive 32;
}

反向代理

基本配置

nginx
server {
    location /api/ {
        proxy_pass http://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;

        # 超时设置
        proxy_connect_timeout 10s;
        proxy_send_timeout    60s;
        proxy_read_timeout    60s;

        # 缓冲设置(减少后端压力)
        proxy_buffering         on;
        proxy_buffer_size       4k;
        proxy_buffers           8 16k;
        proxy_busy_buffers_size 32k;

        # 隐藏后端响应头
        proxy_hide_header X-Powered-By;
    }
}

代理缓存

nginx
http {
    # 缓存路径(在 http 块定义)
    proxy_cache_path /var/cache/nginx
                     levels=1:2
                     keys_zone=my_cache:10m
                     max_size=1g
                     inactive=60m
                     use_temp_path=off;

    server {
        location /api/ {
            proxy_pass             http://backend;
            proxy_cache            my_cache;
            proxy_cache_valid      200 302 10m;
            proxy_cache_valid      404 1m;
            proxy_cache_use_stale  error timeout updating http_500 http_502 http_503 http_504;
            proxy_cache_lock       on;
            add_header             X-Cache-Status $upstream_cache_status;
        }
    }
}

WebSocket 代理

nginx
http {
    # WebSocket 升级映射
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

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

            # WebSocket 必需头部
            proxy_http_version 1.1;
            proxy_set_header   Upgrade    $http_upgrade;
            proxy_set_header   Connection $connection_upgrade;

            # 长连接超时
            proxy_read_timeout 3600s;
            proxy_send_timeout 3600s;

            proxy_set_header   Host            $host;
            proxy_set_header   X-Real-IP       $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}

性能调优

nginx
# 工作进程数(建议与 CPU 核心数一致)
worker_processes auto;

# 每个 worker 允许打开的最大文件描述符数
worker_rlimit_nofile 65535;

events {
    worker_connections 4096; # 每个 worker 最大并发连接数
    use          epoll;      # Linux 下使用 epoll 提升性能
    multi_accept on;         # 允许 worker 一次接受多个连接
}

http {
    sendfile           on;    # 零拷贝传输
    tcp_nopush         on;    # 配合 sendfile,批量发送
    tcp_nodelay        on;    # 减少延迟(长连接场景)
    keepalive_timeout  75s;
    keepalive_requests 1000;

    # 打开文件缓存(减少磁盘 I/O)
    open_file_cache          max=10000 inactive=30s;
    open_file_cache_valid    60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors   on;

    # 客户端请求限制
    client_max_body_size        50m;
    client_body_buffer_size     128k;
    client_header_buffer_size   1k;
    large_client_header_buffers 4 8k;
    client_body_timeout         15s;
    client_header_timeout       15s;
    send_timeout                15s;

    # 隐藏版本信息
    server_tokens off;
}

Gzip / Brotli 压缩

Gzip

nginx
http {
    gzip              on;
    gzip_vary         on;
    gzip_proxied      any;
    gzip_comp_level   6;       # 1-9,6 是速度与压缩比的平衡点
    gzip_min_length   1024;    # 小于 1KB 的响应不压缩
    gzip_buffers      16 8k;
    gzip_http_version 1.1;
    gzip_disable      "msie6";
    gzip_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        application/xml+rss
        image/svg+xml
        font/truetype
        font/opentype
        application/vnd.ms-fontobject;
}

Brotli(需编译 ngx_brotli 模块)

nginx
http {
    brotli            on;
    brotli_comp_level 6;
    brotli_static     on; # 优先使用预压缩的 .br 文件
    brotli_types
        text/plain
        text/css
        application/javascript
        application/json
        image/svg+xml;
}

Brotli 压缩率比 Gzip 高约 20-30%,推荐在支持的环境中启用。

限速与连接限制

limit_req_zonelimit_conn_zone 必须定义在 http 块,不能放在 serverlocation 块内。

nginx
http {
    # 基于 IP 的请求速率限制(10MB 内存,每 IP 每秒 10 个请求)
    limit_req_zone  $binary_remote_addr zone=req_limit:10m  rate=10r/s;
    # 基于 IP 的并发连接限制
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    # 限速状态码改为 429(默认 503)
    limit_req_status  429;
    limit_conn_status 429;

    server {
        # API 接口限速
        location /api/ {
            limit_req  zone=req_limit burst=20 nodelay;
            limit_conn conn_limit 10;
            proxy_pass http://backend;
        }

        # 登录接口更严格
        location /auth/login {
            limit_req zone=req_limit burst=5 nodelay;
            proxy_pass http://backend;
        }
    }
}

OpenAI 接口转发

代理 OpenAI API 时需注意 SNI、Host 头部、流式响应(SSE)的缓冲设置。

nginx
location ^~ /v1/ {
    proxy_pass             https://api.openai.com;
    proxy_ssl_server_name  on;           # 启用 SNI,多域名共享 IP 时必须开启
    proxy_ssl_protocols    TLSv1.2 TLSv1.3;
    proxy_redirect         off;

    proxy_set_header Host            api.openai.com;
    proxy_set_header X-Real-IP       $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-NginX-Proxy   true;

    # 流式响应(SSE)需关闭缓冲
    proxy_buffering    off;
    proxy_cache        off;
    proxy_read_timeout 300s;
    proxy_send_timeout 300s;
}

防盗链

nginx
server {
    location ~* \.(jpg|jpeg|png|gif|webp|svg|mp4|mp3|pdf)$ {
        valid_referers none blocked server_names
                       *.yourdomain.com yourdomain.com;

        if ($invalid_referer) {
            return 403;
            # 也可替换为占位图:
            # rewrite ^/.*$ /403.png break;
        }

        expires 7d;
        add_header Cache-Control "public";
    }
}
  • none:允许直接在地址栏访问(Referer 为空)
  • blocked:允许防火墙过滤后 Referer 被清除的请求
  • server_names:允许来自本域的请求

隐藏版本信息

nginx
http {
    server_tokens off; # 隐藏响应头中的 nginx 版本号
}

若使用 nginx-headers-more 模块,可进一步隐藏 Server 字段:

nginx
more_clear_headers Server;

日志切割(logrotate)

配置文件

创建 /etc/logrotate.d/nginx

bash
/var/log/nginx/*.log {
    daily           # 每天轮转
    rotate 30       # 保留 30 天
    compress        # gzip 压缩旧日志
    delaycompress   # 延迟一天压缩,确保写入完整
    missingok       # 文件不存在时不报错
    notifempty      # 空文件不轮转
    dateext         # 文件名附加日期(如 access.log-20260101.gz)
    create 0640 nginx nginx  # 新日志文件权限及属主
    sharedscripts   # 多文件时只运行一次 postrotate
    postrotate
        # 发送 USR1 信号让 nginx 重新打开日志文件(不重启进程)
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 $(cat /var/run/nginx.pid)
        fi
    endscript
}

测试与触发

bash
# 测试配置语法(不执行实际轮转)
logrotate -d /etc/logrotate.d/nginx

# 强制立即执行一次
sudo logrotate -f /etc/logrotate.d/nginx

# 确认 systemd timer 是否已配置
systemctl status logrotate.timer

最佳实践

前端项目部署(Vue / React SPA)

Vue 和 React 构建产物结构相同(index.html + 静态资源),配置可以共用。核心要解决两个问题:静态资源长期缓存 + 前端路由支持。

nginx
# ───────────────────────────────────────────────────────────────────────────
# 方案一:不使用 certbot webroot(手动续签 / 使用 certbot --standalone 模式)
# 优点:配置简单。缺点:续签时需先停止 nginx。
# ───────────────────────────────────────────────────────────────────────────
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri; # 所有 HTTP 请求直接跳 HTTPS
}

# ───────────────────────────────────────────────────────────────────────────
# 方案二:使用 certbot webroot(推荐,自动续签不停机)
# certbot 续签时通过 HTTP 访问 /.well-known/acme-challenge/ 验证域名所有权。
# 如果 server 块顶层写 return 301,顶层 return 在 location 匹配前执行,
# 会把验证请求也一并 301,导致续签失败。
# 正确做法:把验证路径展开为独立 location,其余才跳转。
# ───────────────────────────────────────────────────────────────────────────
server {
    listen 80;
    server_name example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot; # certbot --webroot -w 指定的目录与此一致
    }

    location / {
        return 301 https://$host$request_uri; # 验证路径之外才跳 HTTPS
    }
}

server {
    listen 443 ssl http2;            # 启用 HTTPS,http2 减少请求延迟
    server_name example.com;

    # TLS 证书(Let's Encrypt 路径,certbot 申请后自动生成)
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;          # 禁用 TLS 1.0/1.1,老版本有已知漏洞
    ssl_ciphers   HIGH:!aNULL:!MD5;          # 排除空加密和 MD5 弱算法

    root  /usr/share/nginx/html;    # 前端构建产物目录(vite 默认为 dist/)
    index index.html;

    # 带 hash 的静态资源(JS/CSS/图片/字体)—— 永久缓存
    # Vite/Webpack 构建的文件名自带 hash,内容变则文件名变,可永久缓存
    location ~* \.(js|css|woff2?|ttf|svg|png|jpg|jpeg|gif|webp|ico)$ {
        expires    1y;
        add_header Cache-Control "public, immutable"; # immutable 告知浏览器跳过 revalidation
        access_log off;                               # 静态资源无需记录访问日志
    }

    # SPA 前端路由支持
    # 若请求的文件/目录不存在,统一返回 index.html,由前端路由接管
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 若后端 API 与前端同域,在此代理转发(不同域可省略)
    location /api/ {
        proxy_pass         http://127.0.0.1:3000; # 后端服务地址
        proxy_http_version 1.1;                   # 使用 HTTP/1.1 以支持 keepalive
        proxy_set_header   Connection    "";       # 清空 Connection 头,启用 upstream keepalive
        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; # 告知后端原始协议(https)
    }
}

Node.js 服务部署

Node.js 服务以反向代理方式部署,Nginx 负责 TLS 终止、安全响应头和请求转发,Node.js 只需监听本地端口。

nginx
# upstream 定义后端服务节点(单节点也建议写 upstream,方便后续扩展)
upstream node_app {
    server 127.0.0.1:3000;
    keepalive 16;              # 与 upstream 保持长连接池,减少连接建立开销
}

# ───────────────────────────────────────────────────────────────────────────
# 方案一:不使用 certbot webroot(手动续签 / 使用 certbot --standalone 模式)
# 优点:配置简单。缺点:续签时需先停止 nginx。
# ───────────────────────────────────────────────────────────────────────────
server {
    listen 80;
    server_name api.example.com;
    return 301 https://$host$request_uri; # 所有 HTTP 请求直接跳 HTTPS
}

# ───────────────────────────────────────────────────────────────────────────
# 方案二:使用 certbot webroot(推荐,自动续签不停机)
# certbot 续签时通过 HTTP 访问 /.well-known/acme-challenge/ 验证域名所有权。
# 如果 server 块顶层写 return 301,顶层 return 在 location 匹配前执行,
# 会把验证请求也一并 301,导致续签失败。
# 正确做法:把验证路径展开为独立 location,其余才跳转。
# ───────────────────────────────────────────────────────────────────────────
server {
    listen 80;
    server_name api.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot; # certbot --webroot -w 指定的目录与此一致
    }

    location / {
        return 301 https://$host$request_uri; # 验证路径之外才跳 HTTPS
    }
}

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

    # TLS 证书(Let's Encrypt 路径)
    ssl_certificate     /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;

    # 安全响应头(防点击劫持、XSS、MIME 嗅探)
    add_header X-Frame-Options        "SAMEORIGIN"  always;
    add_header X-Content-Type-Options "nosniff"     always;
    add_header X-XSS-Protection       "1; mode=block" always;
    add_header Referrer-Policy        "strict-origin-when-cross-origin" always;
    # 仅 HTTPS 场景才启用 HSTS(max-age=1年,浏览器不再发送 HTTP 请求)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # 限制请求体大小(Node.js 上传接口按业务调整,默认 1m 太小)
    client_max_body_size 50m;

    location / {
        proxy_pass         http://node_app;
        proxy_http_version 1.1;                   # 使用 HTTP/1.1 配合 keepalive
        proxy_set_header   Connection    "";       # 清空后可复用 keepalive 连接
        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;

        # 超时设置(默认 60s,可根据接口最长响应时间调整)
        proxy_read_timeout 60s;

        # 隐藏后端框架信息,避免泄漏技术栈
        proxy_hide_header X-Powered-By;
    }

    # 若服务有 WebSocket 端点(如 /ws 或 /socket.io)
    location /ws {
        proxy_pass         http://node_app;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade    $http_upgrade; # WebSocket 升级协议
        proxy_set_header   Connection "upgrade";
        proxy_set_header   Host       $host;
        # WebSocket 是长连接,超时需要设长(否则 Nginx 会断开)
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

持续学习,持续成长