前言
最近遇到一个挺典型的线上问题:一台云服务器上跑着一个自托管服务(openclaw,一个 AI agent 框架),之前一直好好的,某天突然「登录不了了」,而且服务还绑定了自己的域名(openclaw.example.com)来访问。
这篇文章完整记录了这次排查修复的全过程,中间踩了好几个坑——SSH 证书登录方式忘了、域名反代整个丢失、包仓库装不上、GitHub 下载被墙,最后靠一条 SSH 反向隧道借本机 VPN 才把东西下下来。每个环节都值得单独拿出来说说。
第一幕:先能上服务器——找回 SSH 登录方式
服务器是台云主机,当初配过 SSH 登录,但时间一久忘了是「密码」还是「证书」。要排查服务问题,第一步得先能连上去。
排查思路就四个位置,从「最可能直接给答案」到「最兜底」:
# 1. SSH 配置:看有没有配过别名
cat ~/.ssh/config
# 2. 已连过的主机指纹
grep "你的服务器IP" ~/.ssh/known_hosts
# 3. 本地有哪些密钥
ls -la ~/.ssh/
# 4. 最关键:翻 shell 历史,以前敲过的登录命令
grep "你的服务器IP" ~/.zsh_history
果然,历史记录里躺着之前用过的完整命令:
ssh -i aliyun_key.pem root@你的服务器IP
原来当初用的是阿里云 ECS 密钥对登录,证书文件就放在家目录 ~/aliyun_key.pem。三个问题(用户是谁 / 用什么证书 / 证书在哪)一次全解了。私钥权限记得是 600 或 400,不对就 chmod 400 收紧。
第二幕:定位「登录不了」的真因——域名反代整个没了
连上服务器后,先看服务本身在不在跑:
ps aux | grep -iE "claw|gateway" | grep -v grep
systemctl list-units --all --no-pager | grep -iE "claw"
ss -tlnp | grep -E "18789|80|443"
结果很有意思:服务本身完全正常,进程在跑,默认端口 18789 在正常监听。但——
nginx -T 2>/dev/null | grep -iE "openclaw.example.com"
grep -riE "openclaw.example.com" /etc/caddy/ /etc/nginx/ 2>/dev/null
ss -tlnp | grep -E ":(80|443)b"
这三条全部是空的。真相浮出水面:
域名 DNS 是正确指向这台服务器的,但服务器上没有任何进程监听 80/443,也没有任何反向代理(nginx、caddy、宝塔、docker 一个都没有)。域名走 443 进来,却没有程序接住它再转发给服务,所以访问不通。
结论:之前应该是靠某个反代把 443 → 18789,现在这个反代没了。于是决定从头配一个反向代理 + HTTPS。
第三幕:装 Caddy 的坎坷之路
选型:反代选了 Caddy。理由很充分——它是唯一能自动申请并续期 Let’s Encrypt 证书、自动 HTTPS、默认支持 WebSocket 的反代。而 openclaw 的 dashboard 恰好依赖 WebSocket,用 Caddy 配置就两行。
坑 1:包仓库装不上
先试官方 copr 源:
dnf install -y 'dnf-command(copr)' && dnf copr enable -y @caddy/caddy && dnf install -y caddy
报错:
Error: It wasn't possible to enable this project.
Repository 'epel-4-x86_64' does not exist in project '@caddy/caddy'.
原因:这台服务器是阿里云龙蜥/Anolis(alnx4),Caddy 的 copr 仓库没有对应的 epel-4 版本。
坑 2:GitHub 下载到的是 404 页面
换官方静态二进制。第一次手动拼 GitHub 下载链接,把版本号写死了:
curl -L -o /tmp/caddy.tgz
'https://github.com/caddyserver/caddy/releases/latest/download/caddy_2.9.1_linux_amd64.tar.gz'
进度条跑满 100%,但解压时:
gzip: stdin: not in gzip format
下下来的不是安装包,是 GitHub 的 404 HTML 页面——因为版本号 2.9.1 早过时了,这个资产名不存在。
正确做法:用 API 动态拿最新版本号,再拼下载链接:
VER=$(curl -sL 'https://api.github.com/repos/caddyserver/caddy/releases/latest'
| grep -oE '"tag_name": *"v[0-9.]+"' | grep -oE 'v[0-9.]+')
curl -L --progress-bar -o /tmp/caddy.tgz
"https://github.com/caddyserver/caddy/releases/download/${VER}/caddy_${VER#v}_linux_amd64.tar.gz"
tar -xzf /tmp/caddy.tgz -C /usr/local/bin caddy
坑 3:服务器访问 GitHub 被墙
还没下载完,就发现服务器在国内,访问 caddyserver.com / api.github.com 又慢又卡。这就引出本文最值得收藏的一个技巧。
第四幕:借本地 VPN 网络——SSH 反向隧道
需求:服务器访问不了 GitHub,但本地电脑有 ClashX(VPN),能正常访问。怎么让服务器「借」本地电脑的网络?
答案:SSH 反向隧道,把本地代理端口反向映射到服务器上。
先确认本地代理端口(Clash 系默认 7890):
lsof -iTCP -sTCP:LISTEN -n -P | grep -iE "clash|7890|7891"
然后在本地电脑开一条反向隧道(-R 是关键,方向是「服务器端口 → 本地代理端口」,和常见的 -D/-L 相反):
ssh -N -R 7890:127.0.0.1:7890 -o ServerAliveInterval=60 -i ~/aliyun_key.pem root@你的服务器IP
语法:-R 服务器端口:127.0.0.1:本地代理端口,把服务器的 7890 转发到你本地 127.0.0.1:7890 的 ClashX 代理。-N 表示只转发不执行命令。这条命令会一直挂着,保持隧道。
然后在服务器上,让 curl 走这个端口:
export http_proxy=http://127.0.0.1:7890
export https_proxy=http://127.0.0.1:7890
curl -sL 'https://api.github.com/repos/caddyserver/caddy/releases/latest' | grep tag_name
# 返回 "tag_name": "v2.11.4",说明隧道通了
原理一句话:服务器上的 127.0.0.1:7890 是「假的代理」,实际上它通过 SSH 隧道一路连回你本地电脑的 ClashX 代理,再由 ClashX 走 VPN 访问 GitHub,数据再原路返回。服务器以为自己访问的是本地代理,实际借的是你电脑的出口。
第五幕:反代配置 + 自动 HTTPS
Caddy 装好后,配置极简。反代到本机服务:
openclaw.example.com {
reverse_proxy 127.0.0.1:18789
}
reverse_proxy 默认自动处理 WebSocket 升级和 HTTPS,不需要额外 header。
配 systemd 服务时,把代理环境变量也写进去,让 Caddy 申请证书(去 Let’s Encrypt)时同样走隧道:
[Service]
Type=notify
ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile --force
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,::1"
NO_PROXY 排除 127.0.0.1 很关键:反代到本机服务(127.0.0.1:18789)时不能走代理,只有去 Let’s Encrypt 才走。
启动后,日志里能看到证书申请全过程:
new ACME account registered ... status: valid
trying to solve challenge ... challenge_type: tls-alpn-01
authorization finalized ... authz_status: valid
validations succeeded; finalizing order
certificate obtained successfully
验证:
curl -I https://openclaw.example.com
# HTTP/2 200
# via: 1.1 Caddy
via: 1.1 Caddy + 响应头里带着 connect-src ... ws: wss:,说明域名 HTTPS 反代彻底打通,返回的就是服务的 dashboard 页面。
复盘总结
这次排查串起了好几个独立的知识点,单独拎出来都值得记:
| 问题 | 解法 | 关键点 |
|---|---|---|
| SSH 登录方式忘了 | 翻 ~/.zsh_history | 历史命令往往一锤定音 |
| 域名访问不了 | 查 80/443 监听 + 反代配置 | 服务活着 ≠ 域名能通,中间还隔着反代 |
| 包仓库装不上(Anolis) | 改静态二进制 | 小众系统别死磕包管理器 |
| GitHub 资产 404 | API 动态拿版本号 | 别手写死版本号 |
| 服务器被墙下载 | SSH 反向隧道借本地 VPN | -R 服务器端口:127.0.0.1:本地代理端口 |
最大的收获是那条反向隧道:以后再遇到「服务器访问不了某个国外资源、但本地电脑能访问」的场景,一个 ssh -R 就能解决,不用折腾服务器上的代理客户端。
