Spot Monitor

安装

反向代理

一键脚本装出的 hub 只监听 127.0.0.1,公网访问不到,域名由反向代理接进来。下面任选一种。

caddy

证书、X-Forwarded-Proto 和 WebSocket 都由 caddy 自动处理,请求体大小默认不限:

hub.example.com {
    reverse_proxy 127.0.0.1:28080
}
caddyfile

nginx

map $http_upgrade $connection_upgrade { default upgrade; '' close; }
 
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name hub.example.com;
    ssl_certificate     /etc/letsencrypt/live/hub.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/hub.example.com/privkey.pem;
 
    location / {
        proxy_pass http://127.0.0.1:28080;
        proxy_http_version 1.1;
 
        # 导入备份、上传主题按 4 MiB 分片上传,8m 足够,与数据库大小无关
        client_max_body_size 8m;
 
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
 
        # /api/agent/ws 与 /api/ws 是 WebSocket 长连接
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_buffering off;
        proxy_read_timeout  1h;
        proxy_send_timeout  1h;
    }
}
nginx

80 端口跳转到 HTTPS:

server {
    listen 80;
    listen [::]:80;
    server_name hub.example.com;
    return 301 https://$host$request_uri;
}
nginx

宝塔 / aapanel

面板里添加的反向代理可以直接用,目标地址填 http://127.0.0.1:28080。它生成的配置里没有 X-Forwarded-Proto,会话 cookie 因此拿不到 Secure 标志,要在站点配置文件的 location ^~ / 块里补一行:

proxy_set_header X-Forwarded-Proto $scheme;
nginx

改的是站点配置文件。改完不要再在反向代理的表单里点保存,那会重新生成这段配置,这一行随之丢失。

Cloudflare 隧道

隧道由 cloudflared 向外建立,不用开入站端口,只有 IPv4 的机器也能得到双栈入口。配置保存为 ~/.cloudflared/config.yml:

tunnel: <tunnel-id>
credentials-file: /root/.cloudflared/<tunnel-id>.json
 
ingress:
  - hostname: hub.example.com
    service: http://127.0.0.1:28080
  - service: http_status:404
yaml
# 建隧道,并把域名解析到它
cloudflared tunnel create monitor
cloudflared tunnel route dns monitor hub.example.com
# 装成系统服务;只想临时试跑用 cloudflared tunnel run monitor
cloudflared service install
bash

在 WAF 或边缘规则里按路径做了放行列表的,要覆盖 hub 的全部路径和主题的前端路由(默认主题的节点详情页是 /node/{id})。被挡的请求 403 来自边缘,hub 没有日志:升级 hub 后新增的接口不通,或详情页「点进去正常,一刷新就被拦」,都是这个原因。路径清单见架构与协议。

四项必需的设置

上面的 caddy 和 nginx 配置已经满足前三项。自己改写配置、或用别的反代时逐项核对。

1. 转发 WebSocket 头,关掉缓冲,放大超时

/api/agent/ws(agent 上报)和 /api/ws(浏览器实时推送)是长连接。没转发 Upgrade / Connection 头,或读写超时还是默认的 60 秒,节点会周期性掉线又自己回来。

2. 请求体上限 8 MiB

导入备份和上传主题按 4 MiB 分片上传,hub 对单个请求的上限是 8 MiB。这个数不随数据库变大:256 MiB 的备份也只是 64 个 4 MiB 的请求,Cloudflare 免费版每个请求 100 MB 的上限同样够用。

nginx 默认的 client_max_body_size 1m 连一片都放不过。这时 413 来自反代,hub 没有日志,面板收到 413 会直接提示调大 client_max_body_size。

3. 透传 X-Forwarded-Proto 和 X-Forwarded-For

  • 没有 X-Forwarded-Proto:会话 cookie 拿不到 Secure
  • 没有 X-Forwarded-For:登录限流按反代的地址计数,所有访客共用一个计数;节点的连接来源也记成反代地址,NAT 后面的节点显示不出出口地址,网卡上没有公网地址的节点也查不到国家(国家的首选地址是节点网卡上的公网地址,只有网卡上没有公网地址时才轮到连接来源,见地址与国家)

hub 只在连接来自本机或内网地址时才采信 X-Forwarded-For,并取最后一个值。上面的写法都是追加而不是覆盖,最后一个值就是反代实际看到的地址。

前面是 Cloudflare(域名开了代理,或走隧道)时,节点的出口地址和国家不用另外配置:hub 在通过 token 校验的 agent 连接上读取 Cloudflare 写入的 CF-Connecting-IP。登录限流不读这个头,因为从 Cloudflare 网络连到源站的不只有它的代理(Worker 也可以),这个头能被伪造;限流因此仍按边缘地址计,同一个边缘后面的访客共用一个计数。

前面是别家 CDN,或隧道先到本机的 nginx 再到 hub 时,最后一个值是边缘或隧道的地址,所有访客共用一个限流计数;别家 CDN 后面,NAT 节点的出口地址和国家也按边缘算。在反代上把边缘地址换回访客地址,只信任列出的网段:

# 写在 server 块里。每个回源网段一行,照 CDN 公布的列表填;隧道在本机时填 127.0.0.1
set_real_ip_from  192.0.2.0/24;
real_ip_header    X-Forwarded-For;
real_ip_recursive on;
nginx

nginx 要带 realip 模块,nginx -V 2>&1 | grep realip 有输出即可。caddy 的写法:

{
    servers {
        trusted_proxies static 192.0.2.0/24
        trusted_proxies_strict
    }
}
 
hub.example.com {
    reverse_proxy 127.0.0.1:28080 {
        header_up X-Forwarded-For {client_ip}
    }
}
caddyfile

不要写 proxy_set_header X-Forwarded-For $http_cf_connecting_ip;,本页早先的版本给过这一行,照抄过的请删掉。有人绕过 Cloudflare 直连源站时,这个头由他自己填:换着值试密码,登录限流就失效;填上你的地址,你会被锁在登录页外 15 分钟。

4. 放行 POST / PUT / DELETE

面板的写操作用到这三个方法。只放行 GET / HEAD 的 WAF 规则会让整个后台变成只读。

验证

# 面板能打开,且走 HTTPS:第一行是 200
curl -sI https://hub.example.com/admin | head -1
 
# WebSocket 能升级:输出 101。升级后连接保持,3 秒后由 --max-time 结束
curl -s --http1.1 --max-time 3 -o /dev/null -w '%{http_code}\n' \
  -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
  -H 'Sec-WebSocket-Version: 13' \
  -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
  https://hub.example.com/api/ws
bash

面板节点页的「添加节点」能点,说明入口被判为可用:要么是从 HTTPS 域名进的,要么是同一内网直连(私网 IP + 明文,且没有经过反代,见内网直连可以用明文)。从公网的 IP 或明文地址进面板时它是灰的。hub 看三样:反代转发过来的 Host(必须是域名)、X-Forwarded-Proto(必须是 https,没给 hub 配 --site 时尤其重要),以及浏览器发来的 Origin(带了就必须和 Host 同源)。所以反代既要在 Host 里保留域名、透传 X-Forwarded-Proto,也不要清掉或改写 Origin。

上面那个「内网直连」的例外不会漏进来:它要求这次请求没有声明 X-Forwarded-Proto,且 hub 看到的客户端地址(本机反代会取 X-Forwarded-For 的最后一段)是私网地址。反代声明了 https、或转发的是一段公网地址,都回到老规矩。

X-Forwarded-Proto 是否生效,看登录后的会话 cookie 有没有 Secure 标志:浏览器开发者工具的应用 → Cookie 里,monitor_session 那一行。

在 GitHub 上修改这一页最后更新 2026-10-02