Fadey's Blog

arrow_back 返回首页
Nginx封面

Nginx 配置实战:反向代理、HTTPS 与性能调优

Nginx 大概是运维世界里「用得多、讲得少」的典型:装完能用,但遇到路径改写、WebSocket 断连、502 排查时就抓瞎。这篇把最常用的配置场景拆开讲清楚——每个片段都可以直接抄,但更重要的是理解它为什么这么写。

为什么是 Nginx

  • 事件驱动 + 异步非阻塞:几万并发连接只用几个 worker 进程,内存占用稳定;
  • 内置能力全:反向代理、负载均衡、缓存、限流、gzip、TLS 都开箱即用;
  • 配置即文档:一个 server 块描述一个站点,比代码里的路由更容易审阅。

文件结构与配置层次

# Ubuntu / Debian 系的典型结构
/etc/nginx/
├── nginx.conf          # 主配置(worker 数、日志、gzip 等全局项)
├── conf.d/             # 站点配置(*.conf 自动 include)
├── sites-available/    # 可选:站点仓库
└── sites-enabled/      # 可选:软链生效的站点

# 常用管理命令
nginx -t                  # 校验配置语法(改完必须先跑这个)
systemctl reload nginx    # 平滑重载(不中断连接)
systemctl status nginx

server 与 location:请求怎么被路由

一个请求进入 Nginx,先按 listen + server_name 找到 server 块,再按 location 的匹配规则决定处理方式。location 的优先级是面试高频,也是配置事故高发区:

# 匹配优先级(从高到低):
# 1. = 精确匹配:location = /health
# 2. ^~ 前缀匹配且不再尝试正则:location ^~ /static/
# 3. ~ 正则匹配(按出现顺序,第一个命中生效);~* 不区分大小写
# 4. 普通前缀匹配(最长前缀生效)
# 5. / 兜底

server {
    listen 80;
    server_name example.com www.example.com;

    location = /health {
        return 200 "ok\n";
        add_header Content-Type text/plain;
    }

    location ^~ /static/ {
        root /var/www/site;         # /static/a.css → /var/www/site/static/a.css
        expires 7d;
    }

    location ~* \.(png|jpg|webp|svg)$ {
        root /var/www/site;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

反向代理核心:proxy_pass 与转发头

最容易踩的坑是 proxy_pass 结尾有没有斜杠:带路径会替换匹配部分,不带路径会原样拼接。这条规则不理解,路径重写就会诡异丢前缀。

location /api/ {
    # http://host/api/users → http://127.0.0.1:8080/users   (吃掉 /api 前缀)
    proxy_pass http://127.0.0.1:8080/;

    # http://host/api/users → http://127.0.0.1:8080/api/users (保留原样)
    # proxy_pass http://127.0.0.1:8080;
}

# 一套通用的转发头(后端取真实信息全靠它们)
location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;

    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 5s;   # 连后端超时
    proxy_read_timeout    60s;  # 读后端超时(长任务要调大)
    proxy_send_timeout    60s;
}

后端做限流、审计、直链生成时,X-Real-IP / X-Forwarded-For 就是唯一可信来源;只在内网信任链上使用,外部进来的伪造头要在边缘层清掉。

WebSocket 升级:两个头缺一不可

WebSocket 断连最常见的根因就是 Nginx 没配升级头。Connection 和 Upgrade 必须成对出现:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

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 $connection_upgrade;

    proxy_read_timeout 300s;    # 心跳间隔要小于这个值,否则会被静默断开
}

静态资源与传输优化

# nginx.conf 全局层开启 gzip
gzip on;
gzip_comp_level 5;
gzip_min_length 1k;
gzip_types text/css application/javascript application/json image/svg+xml;

# 带 hash 的构建产物:长缓存 + immutable,靠文件指纹替换
location ~* \.[0-9a-f]{8,}\.(js|css)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

# sendfile 零拷贝(大文件/静态站收益明显)
sendfile on;
tcp_nopush on;

缓存策略一句话版:HTML 不缓存(协商缓存即可),带指纹的 JS/CSS/图片长缓存。发布新版本的唯一动作是把 HTML 里的引用换掉。

限流:单机版的「闸门」

# http 层定义限流区:按 IP 每秒 10 个请求,突发 20 个
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

location /api/ {
    limit_req zone=api burst=20 nodelay;
    limit_req_status 429;    # 超限返回 429 而不是默认的 503
    proxy_pass http://127.0.0.1:8080;
}

注意限流是单机维度的:多台机器各自限流等于总阈值 × 台数。真要做全局配额,得把计数放到 Redis 那类共享存储里。

HTTPS 与 HTTP/2

# 用 certbot 签证书并自动续期
apt install certbot python3-certbot-nginx
certbot --nginx -d example.com -d www.example.com
systemctl list-timers | grep certbot   # 确认自动续期定时器在跑
server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

    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;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    add_header Strict-Transport-Security "max-age=31536000" always;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

# 80 端口统一跳 443(Let's Encrypt 校验路径除外)
server {
    listen 80;
    server_name example.com;
    location /.well-known/acme-challenge/ { root /var/www/certbot; }
    location / { return 301 https://$host$request_uri; }
}

502 / 504 排查清单

  1. nginx -t 和 systemctl status:先确认配置生效、进程健康;
  2. 看 /var/log/nginx/error.log 的具体 errno:Connection refused(后端没起/端口错)、upstream timed out(处理太慢)、no live upstreams(都被判死);
  3. curl -v http://127.0.0.1:3000/:绕过 Nginx 直连后端,确认锅在谁头上;
  4. SELinux 没放行 httpd_can_network_connect 时,Nginx 连本机后端也会被拒;
  5. 504 大多是真慢:调大 proxy_read_timeout 只是缓解,根本方案是把慢接口改成异步任务;
  6. 日志里加 $upstream_response_time $request_time,用数据分清「后端慢」还是「网络慢」。
Nginx 的配置没有魔法:理解匹配优先级和代理语义这两个模型,剩下的都是可查的指令拼装。

总结

本文覆盖了 Nginx 日常运维的完整拼图:server/location 路由 → 反向代理与转发头 → WebSocket 升级 → 缓存与 gzip → 限流 → HTTPS/HTTP2 → 故障排查。建议把自己的站点配置对照文中的片段过一遍,尤其是 proxy_pass 斜杠、升级头、超时这三处——线上 90% 的「玄学问题」都出在这里。