场景很常见:前端同学的 Next.js 服务还跑在自己本机,后端、产品、老板却急着要看效果。今天记录一次真实操作——把本机服务通过「SSH 反向隧道 + Caddy 反向代理 + 自动 HTTPS」串起来,几分钟内就给一个开发中的页面配上了一个可公网访问的 HTTPS 域名。
问题背景
开发者在本地起了一个 Next.js 项目(监听 3000 端口),但评审需要其他人能在公网访问。服务器上没有这个项目的代码,也不方便直接部署。
于是有了这套「隧道 + 代理」的组合拳:
- 开发者用 SSH 反向隧道,把服务器的某个端口转发到自己本机的 3000;
- 服务器上用 Caddy 把某个子域名反代到这个本地端口;
- Caddy 自动申请并续期 Let’s Encrypt 证书,让这个子域名直接就是 HTTPS。
最终效果:任何人在浏览器打开 https://.,就能看到开发者本机正在跑的那个页面。
第一步:确认隧道通不通
在服务器上,先探一下那条 SSH 隧道对应的本地端口(这里假设是 13000):
curl -s -o /dev/null -w "%{http_code}n" http://127.0.0.1:13000/
curl -s http://127.0.0.1:13000/ | head -20
- 如果返回
200,且能拿到 HTML(能看到 Next.js 的页面骨架),说明隧道是通的,可以继续; - 如果返回
000或直接报连接错误,说明隧道没建立起来,就别往下走了,先让开发者重建 SSH 隧道。
这一步很重要:先验证后端再改配置,否则你配好了 Caddy,结果反代到一个打不开的空端口,白忙一场。
第二步:找到 Caddy 真正用的配置文件
很多人(包括我)第一反应是去 /etc/caddy/Caddyfile,但不同安装方式路径可能不一样,别想当然。用这几种方式确认:
systemctl cat caddy 2>/dev/null # 看 systemd 服务定义里的 --config
ps aux | grep -i caddy | grep -v grep # 看实际运行进程的启动参数
ls -la /etc/caddy/Caddyfile /usr/local/caddy/Caddyfile 2>/dev/null
服务文件里那两行最关键:
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile
--config 指向的路径,才是真正生效的配置文件。以实际运行进程的参数为准。
第三步:改之前先备份
改配置前一定先备份,出问题能秒回滚:
cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.bak.$(date +%Y%m%d%H%M%S)
带「时间戳 + .bak」后缀,一眼能看出是哪一次改的。
第四步:新增一个 site block
在 Caddyfile 里补一个站点块(如果已经有同名块就先改它,别重复加):
h5-video.example.com {
reverse_proxy 127.0.0.1:13000
}
几个要点:
- 不要动其它已有的站点块,只追加你自己这一个;
reverse_proxy的目标固定是那个隧道端口(127.0.0.1:13000),别手滑改成别的端口;- 加了新域名后,Caddy 会自动去申请证书。
第五步:重载 Caddy
systemctl reload caddy
如果这个服务不存在,就退回到 caddy reload --config 。reload 是非中断的平滑重载,已经跑着的站点不会断。
第六步:验证与收尾
# 1) 确认隧道仍然通
curl -s -o /dev/null -w "%{http_code}n" http://127.0.0.1:13000/
# 2) 本地强制解析域名到本机,测试 HTTPS(DNS 还没指过来时也能测)
curl -sI --resolve h5-video.example.com:443:127.0.0.1 https://h5-video.example.com/
一个很关键的坑:DNS 和证书申请的顺序
这次操作里最典型的坑,就是证书申请时机。
第一次 reload 之后,我在 Caddy 的日志里看到 ACME 挑战一直失败,报错关键词是:
DNS problem: NXDOMAIN looking up A for h5-video.example.com
原因是:当时 DNS 记录还没指过来,Let’s Encrypt 要校验域名(HTTP-01 或 TLS-ALPN-01 挑战),它去查这个域名的 A 记录,查到的是 NXDOMAIN(域名不存在),校验自然失败。
所以正确的顺序应该是:
- 先把 DNS 的 A 记录指到服务器 IP;
- 等解析生效(可以用公共 DNS 确认,比如阿里云
223.5.5.5、DNSPoddoh.pub,或者直接getent hosts看本地解析); - 再
systemctl reload caddy,让 Caddy 去申请证书。
DNS 一旦生效,reload 之后再看日志,就能看到证书成功签发的字眼:
tls.obtain certificate obtained successfully
authorization finalized authz_status: valid
tls served key authentication certificate challenge: tls-alpn-01
再验证,就能看到 HTTP/2 200,且响应头里带着 server: Caddy 和 x-powered-by: Next.js,整条链路就通了。
小结
这套组合的巧妙之处在于:
- SSH 反向隧道:免部署,把「本机开发服务」变成「服务器上的一个本地端口」;
- Caddy 反向代理:一行配置把子域名接到那个端口;
- 自动 HTTPS:Caddy 内置 ACME,证书申请、续期全自动,零维护。
整个过程唯一容易翻车的地方就是「DNS 没生效就急着让 Caddy 申请证书」。把顺序捋对了,剩下就是几分钟的事。
*本文记录了一次真实的运维小操作,域名和 IP 已做脱敏处理。*
