前阵子我想在博客旁边再挂个小服务(就是上一篇里提到的 Uptime Kuma),Nginx 配置写了半天:http 能打开,https 报证书错;修好证书,后端又说收到的全是 http 请求,页面里的跳转全乱套。我对着配置文件发呆了二十分钟,感觉跟看 HOA 的规章手册一样头大。

这篇的起因就是想给 Uptime Kuma 一个体面的域名,它是我 2核2G 机器上跑的三个轻量服务之一,清单记在另一篇里。

冷静下来,把配置一段段拆开看,才发现全是小问题。这篇笔记就是把那次的配置掰开揉碎,逐段解释清楚。

第一段:HTTP 跳 HTTPS,每个域名都要有

server {
    listen 80;
    server_name opusmia.com www.opusmia.com;
    return 301 https://$host$request_uri;
}

这是最简单的一个 server 块,干一件事:所有走 80 端口(HTTP)来的请求,301 永久重定向到 HTTPS。$host 是用户访问的域名,$request_uri 是后面的路径,拼起来就是"原样跳到 https 版本"。

为什么每个域名都要写?因为 certbot 当初 --nginx 时只给我主域名配了跳转,www 的 80 端口没人管。有段时间 http://www.opusmia.com 打开是 Nginx 默认页,尴尬。检查方法很简单:

curl -I http://www.opusmia.com

看返回是不是 301 和 Location: https://...。不是,就把这个 server 块补上。

第二段:反向代理,把请求转发给后端服务

Uptime Kuma 跑在服务器本机的 3001 端口,我不想让访客记 http://我的VPS:3001 这种地址,就用 Nginx 反向代理,给它一个体面的域名:

server {
    listen 443 ssl;
    server_name status.opusmia.com;

    ssl_certificate /etc/letsencrypt/live/status.opusmia.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/status.opusmia.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3001;
        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;
    }
}

逐段说。listen 443 ssl 是 HTTPS 端口,ssl_certificate 两行是证书路径——子域名要单独申请证书,certbot --nginx -d status.opusmia.com 再跑一次就行,流程跟主域名一模一样。

子域名要单独申请证书,流程跟主域名一模一样,certbot 申请那篇里有完整记录,灰云申请、验证通过再开代理这个顺序别搞反。

location / 里面是核心。proxy_pass http://127.0.0.1:3001 把请求转发给本机 3001 端口的后端服务,访客看到的还是 https://status.opusmia.com,完全不知道后面藏了个 3001。这就是反向代理:Nginx 站在前面当门面,后端服务在后面干活。

下面四行 proxy_set_header 是精髓,也是我当时翻车的地方。默认情况下,Nginx 转发请求时,后端收到的 Host 是 127.0.0.1:3001,拿到的客户端 IP 也是 127.0.0.1。这四行的作用:

  • Host $host:把访客访问的真实域名传给后端,不然有些服务会认不出自己是谁。
  • X-Real-IP $remote_addr 和 X-Forwarded-For:把访客的真实 IP 传过去。不然 Uptime Kuma 的日志里全是 127.0.0.1,出问题查日志等于看天书。
  • X-Forwarded-Proto $scheme:告诉后端"访客用的是 https"。这一行最重要。我当时漏了它,后端以为所有请求都是 http,自己又配了"强制 https",结果无限重定向,页面转圈圈。那二十分钟的发呆,一大半是它造成的。

补充:如果后端用 WebSocket,再加三行

Uptime Kuma 的实时状态更新走的是 WebSocket,反向代理默认不转发它,页面能打开,但状态不会自动刷新,看着像卡住了。解法是在 location 里再加三行:

location / {
    proxy_pass http://127.0.0.1:3001;
    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_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

proxy_http_version 1.1 加上 Upgrade 和 Connection 这两个头,是 WebSocket 握手要用的。没有它们,Nginx 和后端之间只能走普通 HTTP,WebSocket 连不上。这个坑我踩过:页面一切正常,就是数据不刷新,查了半天才定位到这三行。凡是反代带实时功能的应用(监控面板、聊天工具),先把这三行加上,八成用得上。

第三段:别忘了 DNS 和防火墙

Nginx 配好了,status.opusmia.com 还得能解析。在 Cloudflare 给 status 加一条 A 记录,指向我的 VPS。然后确认防火墙 443 是开的(之前安全加固时开过,检查一下):

sudo ufw status verbose

给 status 加 A 记录这事我已经吃过一次亏:www 的旧 CNAME让我折腾了一下午,现在改 DNS 前我都先截图。

最后检查 Nginx 语法,重载:

sudo nginx -t
sudo systemctl reload nginx

验证跳转链是否完整:

curl -IL http://status.opusmia.com

-L 是跟随跳转,一眼能看到 http → https → 200 的完整链条。链条完整,这事才算完。

想再进一步,可以加 HSTS 头,告诉浏览器"这个域名以后只走 HTTPS":

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

但这行要谨慎:HSTS 一旦生效,浏览器会记住一年,期间哪怕证书出了问题,用户也进不去 http 应急页面。我的建议是,博客稳定跑半年、确认证书续期没出过岔子,再加不迟。安全措施和 HOA 的新规一样,上之前先想好退路。

排错小抄:我遇到过的四种报错

  • 502 Bad Gateway:后端服务没起来。先 curl http://127.0.0.1:3001 看看本机能不能通,再查 Nginx。
  • 无限重定向:八成是 X-Forwarded-Proto 没设,后端在 http/https 之间打架。把那行加上。
  • 证书错误:子域名的证书没申请,或者 ssl_certificate 路径写错了。certbot certificates 看一眼。
  • 页面能开但数据不刷新:WebSocket 没透过去,检查上面那三行加了没有。

一句话总结

反向代理就记住这个模板:proxy_pass 转发,四行 proxy_set_header 传真实信息(特别是 X-Forwarded-Proto),HTTP 用 return 301 跳 HTTPS。配完 curl -IL 走一遍跳转链,通了再收工。