Caddy 反代:5 分钟给网站加上 HTTPS

2026 年 09 月 28 日 · 约 8 分钟读完

你的网站在服务器上跑起来了,监听的是 localhost:3000。用户要通过 https://你的域名 访问,中间需要一个反向代理。反向代理是啥?打个比方:你的网站是后厨,Caddy 是前台迎宾——客人(浏览器)只跟迎宾打交道,迎宾把点菜单递进后厨、把做好的菜端出来,后厨在哪、长啥样,客人不用知道。好处是:对外只暴露 80/443 两个标准端口,后面跑几个服务、监听什么端口,都藏在里面;HTTPS 证书也由迎宾统一管。选 Caddy 的理由一句话:HTTPS 证书它自动申请、自动续期,你不用管证书这回事。用 Nginx 也行,但证书要自己折腾 certbot,新手建议从 Caddy 开始。

Caddy 反代流向图:浏览器经 Caddy 的 443 端口自动 HTTPS,再反代到本地 3000 端口的服务浏览器用户访问https://你的域名443Caddy监听 80 / 443自动申请 HTTPS 证书反代你的网站localhost:3000不用管证书Caddy 向 Let's Encrypt 免费申请证书,到期自动续期——HTTPS 一行配置都不用你写记住:80 和 443 端口不能被别的程序占着,域名要先解析到这台服务器的 IP
Caddy 反代流向:浏览器走 443 进 Caddy,Caddy 转发给本地 3000 端口的网站

最小配置:两行就够

Caddy 的配置文件叫 Caddyfile,语法是人话。一个域名反代到本地 3000 端口,全部配置就这两行:

example.com {
	reverse_proxy localhost:3000
}

把 example.com 换成你的域名。reverse_proxy 的意思就是:收到的请求原样转给 localhost:3000,再把响应还给浏览器。网站本身不用改任何代码,也不用管证书。

Caddyfile 再讲透一点:多个服务怎么写

一台机器上跑多个网站是常态,Caddyfile 里一个域名一段就行。注意格式:域名后面跟空格加大括号,里面的指令前面用制表符缩进,写错缩进 Caddy 会直接报错,反而好排查——它不让你带着错配置上线。

blog.example.com {
	reverse_proxy localhost:3000
}

api.example.com {
	reverse_proxy localhost:8000
}

# 静态文件直接让 Caddy 托管,不经过后端
static.example.com {
	root * /var/www/static
	file_server
}

几个常用指令记住就行:reverse_proxy 反代给后端,file_server 直接托管静态文件,root 指定静态文件在哪。还有一个 encode gzip,给文本响应开压缩,网站加载能快一截,博客这种文字多的站效果最明显,JS/CSS/HTML 都能压掉一大半体积。改完配置先别急着 reload,跑 caddy validate --config /etc/caddy/Caddyfile 检查语法,通过了再 reload,坏配置进不了线上。

sudo caddy validate --config /etc/caddy/Caddyfile  # 语法没问题会显示 Valid configuration
sudo systemctl reload caddy

Caddy 和 Nginx 到底怎么选

一句话:新手无脑选 Caddy,老手按需选 Nginx。Caddy 的优势是自动 HTTPS 和人话配置,你这辈子都不用碰 openssl 命令;Nginx 的优势是生态大、性能调优资料多,超高并发场景更稳。但个人小站和 side project 的流量,Caddy 闭眼够用。什么时候换 Nginx?当你需要写复杂的 location 匹配、限流、缓存规则,或者团队里大家都熟 Nginx 的时候。在那之前,Caddy 省下来的时间够你多写好几篇文章。

装起来:三步

# 1. 装 Caddy(Ubuntu/Debian)
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy

# 2. 写配置
sudo nano /etc/caddy/Caddyfile   # 粘贴上面那两行,换成你的域名

# 3. 生效
sudo systemctl reload caddy

reload 之后 Caddy 会自动去 Let's Encrypt 申请免费证书,十几秒就好。然后浏览器打开 https://你的域名,小锁出现了就成了。如果十几秒后还没好,别急着改配置,先看日志 journalctl -u caddy -n 30,十有八九是下面四个坑里的一个。

自动 HTTPS 的原理一句话:Caddy 启动时用 ACME 协议向 Let's Encrypt 证明这个域名归你(通过 80 端口放一个验证文件),拿到免费证书,到期前自动续。你全程不用碰证书文件。

反代之后:后端拿到的 IP 为什么是 127.0.0.1

上了反代之后,你的网站看到的所有请求都来自 127.0.0.1——因为真正跟它对话的是 Caddy,不是浏览器。如果你要做访问统计、限流、防刷,得拿真实 IP。好消息是 Caddy 默认就会把真实 IP 放进 X-Forwarded-For 请求头里传给后端,你的程序从这个头里读就行。Node 里就是 req.headers['x-forwarded-for'],Nginx 后的应用同理。别在后端拿连接 IP 当用户 IP,那是 Caddy 的 IP,限流限错了会把所有用户一起封掉。

日志在哪看:Caddy 的访问日志

网站出问题先看两处日志:Caddy 自己的(systemctl status caddy、journalctl -u caddy -n 50)和你后端应用的。默认 Caddy 不记访问日志,想记的话 Caddyfile 里加一行 log,访问记录就进了 journald:

example.com {
	reverse_proxy localhost:3000
	log {
		output stdout
		format json
	}
}

502 报错时先看 Caddy 日志:如果 Caddy 收到请求但后端没响应,日志里会有 reverse_proxy 的报错,这说明问题在后端没起来;如果 Caddy 日志里连请求都没有,问题在更前面——DNS、防火墙、端口被占,按坑 1 到坑 3 查。

www 跳转:要不要做

很多人纠结 example.com 和 www.example.com 留哪个。建议:只留一个,另一个 301 跳过去,不然搜索引擎会当成两个站,权重分散。Caddy 里写两段就行:

example.com {
	reverse_proxy localhost:3000
}

www.example.com {
	redir https://example.com{uri} permanent
}

{uri} 是把用户访问的路径原样带过去,www.example.com/about 会跳到 example.com/about,不会丢路径。两个域名的证书 Caddy 会一起申请,不用你操心。搜索引擎那边不用动,它跟着 301 走,权重会慢慢集中到主域名上。

WebSocket 要额外配吗:不用

很多人从 Nginx 转过来会问:反代 WebSocket 要不要加 Upgrade 头?Caddy 不用,它默认就支持,ws 和 wss 直接透过去,聊天室、实时通知这类应用零配置。这是 Caddy 省心的地方之一,Nginx 那边要手写 proxy_set_header Upgrade $http_upgrade,少一行就连不上。顺带一提,HTTP/3(QUIC)Caddy 也是默认开的,浏览器支持的话会自动用上,不用你调。

坑 1:80 / 443 端口被占了

这是最常见的报错:Caddy 启动失败,日志里写 address already in use。八成是之前装的 Nginx 还占着 80 端口,或者你装了宝塔、1Panel 这类面板,它们自带了 web 服务。先查谁占着,再决定留谁,一台机器上只留一个接 80/443 的程序,别让它俩打架:

sudo ss -tlnp | grep -E ':80|:443'   # 看谁占着
# 如果决定用 Caddy,就停掉 nginx:
sudo systemctl stop nginx
sudo systemctl disable nginx
sudo systemctl restart caddy

一台机器上只留一个接 443 的程序,别让它俩打架。还有一种隐蔽情况:IPv6 占着端口而 ss 默认只显示了 IPv4,查的时候加上 -tlnp 看全,或者直接看 Caddy 的日志 journalctl -u caddy -n 30,报错里会写清楚是哪个地址 bind 失败。

坑 2:域名没解析到这台服务器

Caddy 申请证书前要验证域名归你,验证方式是去公网查这个域名的 DNS。如果域名还没解析到这台机器的 IP,验证一定失败,日志里会看到 acme 相关报错。先查:

dig example.com +short   # 换成你的域名,看是不是这台机器的公网 IP

不是的话,去域名服务商的 DNS 设置里加一条 A 记录指向服务器 IP,等几分钟再生效。心急的话 dig 多查几次,看到 IP 对了再 reload Caddy。注意 DNS 是有缓存的,你本地查到了不代表 Let's Encrypt 那边查到了,刚改完解析就申请证书,可能会遇到验证失败,多等 10 分钟再试一次,别反复 reload——Let's Encrypt 对失败次数有限制,一小时内失败太多次会被临时拉黑,拉黑了就只能干等一小时。

坑 3:防火墙没放行 80 和 443

ACME 验证要走 80 端口,用户访问要走 443,这两个有一个不通就全完。ufw 和云厂商安全组两边都要放行,别只配一边:

sudo ufw allow 80
sudo ufw allow 443

云厂商的安全组(控制台网页上那个)同样放行 80/443。配完从外网 curl -I http://你的域名 试一下,通了再配 https。还有个容易漏的:ufw 改完规则要确认是立即生效的(ufw 是),但云厂商安全组有的要等几十秒,别改完立刻测,喝口水再测。

坑 4:国内服务器要先备案

如果你用的是国内云厂商(阿里云、腾讯云)的服务器,80 和 443 端口默认是被运营商层面封掉的,不备案打不开。这是政策要求,不是技术问题:域名先去工信部备案(服务商后台有引导,一般 1-3 周),备案通过后端口自动放开。用香港、新加坡、美国的服务器没有这个限制,域名解析过去就能用。买服务器之前先想清楚目标用户在哪,免得机器买好了卡在备案上。

验证:三步确认全通

  • curl -I https://你的域名,第一行是 HTTP/2 200
  • 浏览器打开,地址栏有小锁,点开看证书是 Let's Encrypt 签发的
  • curl -I http://你的域名,应该 308 跳转到 https(Caddy 默认帮你跳)

证书 90 天过期,Caddy 会在到期前 30 天自动续,但建议你每两个月瞟一眼:浏览器点小锁看有效期,或者跑 echo | openssl s_client -connect 你的域名:443 2>/dev/null | openssl x509 -noout -dates。自动续期失败的原因,九成是 80 端口后来被占了或者 DNS 被改了,按坑 1 和坑 2 查。

三步都过,HTTPS 就稳了。证书是 90 天有效期的,到期前 Caddy 会自己续,你不用设任何定时任务,也不用半夜爬起来换证书。证书文件存在 /var/lib/caddy 里,你不需要碰它,但要知道它在:如果哪天证书出了问题,删掉这个目录重启 Caddy 会重新申请,这是最后的兜底手段。顺带说一句,换域名、加子域名都不用你管证书,Caddyfile 里加一段 reload 一下,证书自动跟着来。网站进程本身挂了怎么办?看《systemd 守护进程:服务挂了自己重启》那篇。服务器刚到手还没初始化的,先看《VPS 到手先做这 10 件事》。反代相关的命令(curl -I、ss -tlnp、dig)记不清就去站里的 Shell 命令速查页(站内 /shell),都在网络诊断分组里。

Caddy 反代:5 分钟给网站加上 HTTPS · EazyOPC 博客