用 Nginx 反代一个静态站点:从配置到排查

单机放多个站点,最省事的做法是让 Nginx 直接读文件;需要跑应用时,再用反向代理把它接在后面。这篇记录我常用的一套配置,以及 502、504 出现时的排查顺序。

先分清两件事

静态站点和反向代理解决的是不同问题,配置之前先确认自己属于哪一种:

  • 静态托管:页面是构建好的 HTML、CSS、JS 和图片,Nginx 用 root 指向目录即可。没有进程常驻,重启机器不影响访问。
  • 反向代理:后端是 Node、Python 或 Go 写的服务,监听 127.0.0.1:3000 之类的本地端口,Nginx 负责 TLS、压缩、缓存和访问控制。

两者可以共存:同一个域名下,/ 走静态文件,/api/ 转给后端。

一个够用的 server 块

下面这份配置覆盖了静态站点最常见的需求:HTTP/2、404 页面、静态资源长缓存。证书路径按实际替换。

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;

    root  /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ $uri.html =404;
    }

    location ~* \.(css|js|svg|png|jpg|webp|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    error_page 404 /404.html;
    location = /404.html {
        internal;
    }
}

try_files $uri $uri/ $uri.html =404 的意思是:先找同名文件,再找同名目录,最后尝试补一个 .html 后缀,都没有就返回 404。这样 /about 可以直接对应到 about.html,不必把每个链接都写成带后缀的形式。

关于缓存 HTML 不要设长缓存,否则改完内容用户看到的还是旧页面。静态资源可以放心设一年,但前提是文件名带内容哈希;如果文件名固定,30 天是更稳妥的选择。

把请求转给后端

后端只监听本地回环地址,外网访问统一经过 Nginx。除了 proxy_pass,几个请求头也要一并带上,否则后端拿到的客户端 IP 全是 127.0.0.1。

location /api/ {
    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_read_timeout 30s;
}

如果后端用了 WebSocket,还需要补上 Upgrade 与 Connection 两个头,并把 proxy_read_timeout 调大,否则连接会在空闲一段时间后被切断。

出问题时按这个顺序查

先把范围缩小到“Nginx 到后端”这一段,再决定改哪里。

  1. 502 Bad Gateway:后端没起、端口写错,或者进程崩了。先在服务器上执行 curl -I http://127.0.0.1:3000,再用 ss -lntp 确认端口确实在监听。
  2. 504 Gateway Timeout:连接建立了,但后端在超时时间内没有返回。看后端日志里最慢的那条请求,再决定是调大 proxy_read_timeout 还是优化查询。
  3. 404(静态):多半是 root 指错,或者 Nginx 运行用户没有目录的读权限。用 namei -l /var/www/example.com/index.html 逐级检查权限。
  4. 413 Request Entity Too Large:上传被默认的 1 MB 限制挡住了,按需调整 client_max_body_size。

改完先验证再重载

每次改动后先跑 nginx -t,通过之后再 reload。直接 restart 会把现有连接全部断掉,reload 则是平滑加载新配置。

sudo nginx -t
sudo systemctl reload nginx
curl -I https://example.com/
curl -I https://example.com/api/health

习惯上我会把旧配置复制一份带日期的备份,例如 site.conf.20261028.bak。回滚只需要改回文件名再 reload,比凭记忆撤销快得多。

小结

静态站点优先静态托管,只有确实需要计算时才引入后端。配置尽量少,出错的位置也就少;剩下的工作交给日志和 curl,比反复猜测有效。