FieryWing 的技术博客

Caddy 入门:自动 HTTPS 的 Web 服务器

2025-09-14

Caddy 是一个用 Go 写的 Web 服务器,最大的特点是配置简单、自动申请和续期 HTTPS 证书。我自己的博客和几个小服务都放在一台小 VPS 上,早先用 Nginx 加 certbot,证书续期、定时任务、重载配置各是一摊事;换成 Caddy 之后,一份十几行的配置文件就把这些都包了。这篇文章记录我的使用过程,从安装到静态站点、反向代理,再到常见的坑,环境是 Debian / Ubuntu。

Caddy 是什么,适合谁

Caddy 是单个可执行文件,默认只要你的域名解析正确、80 和 443 端口可达,它就会自动向证书机构申请证书、自动续期,并默认把 HTTP 跳转到 HTTPS。对个人站点来说,这省掉了最容易出错的一环。如果你对 HTTPS 的原理还不熟,可以先看《HTTPS 是怎么工作的》;域名解析的部分见《DNS 入门》。

它不适合的场景也要说清楚:如果你需要大量第三方模块、非常细的调优,或者已有成熟的 Nginx 配置体系,没必要为了尝鲜而迁移。

安装(Debian / Ubuntu)

推荐使用官方提供的 apt 软件源,这样后续可以通过 apt 升级。以下步骤请对照 Caddy 官方文档确认最新写法,因为密钥和源地址偶尔会调整。

sudo apt update
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-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

安装完成后,Caddy 会以 systemd 服务运行,默认配置文件是 /etc/caddy/Caddyfile。用下面的命令确认状态:

systemctl status caddy
caddy version

开始之前的准备

  1. 域名的 A 记录(有 IPv6 的话再加 AAAA 记录)指向服务器公网 IP,例如 203.0.113.10。
  2. 服务器防火墙和云厂商安全组放行 80 与 443,详见《Linux 防火墙入门》。80 端口也要开,因为证书验证和 HTTP 跳转都会用到。
  3. 确认没有别的程序占用 80/443,可以用 sudo ss -ltnp 查看。如果之前装过 Nginx 或 Apache,要先停掉。
  4. 如果服务器刚买来,请先按《VPS 初始化》和《SSH 安全》做好基本加固。

托管一个静态站点

www.example.com {
	root * /home/user/blog
	encode gzip
	file_server
}

这里 root 指定网站根目录,encode gzip 开启压缩,file_server 启用静态文件服务。注意 Caddy 进程的运行用户(Debian 包里是 caddy)需要对目录有读取权限,而 /home/user 默认可能是 750 权限,会导致 403。我的做法是把站点放到 /var/www/blog 这样的位置:

sudo mkdir -p /var/www/blog
sudo cp -r ~/blog/. /var/www/blog/
sudo chown -R root:root /var/www/blog
sudo find /var/www/blog -type d -exec chmod 755 {} +
sudo find /var/www/blog -type f -exec chmod 644 {} +

然后把 Caddyfile 里的 root 改为 /var/www/blog。如果你想让 example.com 跳转到 www.example.com,可以再加一段:

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

反向代理

app.example.com {
	reverse_proxy 127.0.0.1:8080
}

后端程序只监听本机端口,由 Caddy 对外提供 HTTPS,是常见的做法。如果后端是用 Docker 跑的,端口映射也要绑定到 127.0.0.1,见《Docker 入门》。Caddy 会自动带上 X-Forwarded-For 等请求头,多数应用不需要额外设置。

在同一个站点下混用静态文件和后端

有时我想让同一个域名下 /api/ 走后端,其他走静态文件,可以用 handle:

example.com {
	encode gzip

	handle /api/* {
		reverse_proxy 127.0.0.1:8080
	}

	handle {
		root * /var/www/blog
		file_server
	}
}

同一层级的多个 handle 是互斥的:匹配到第一个就不再往下走,不带匹配器的 handle 相当于兜底。如果想在转发前去掉路径前缀,可以改用 handle_path,它会自动剥离匹配到的前缀。

给管理页面加上基本认证

某些内部页面不想公开,但又懒得做完整的登录系统,可以用 basicauth。密码不能明文写在配置里,要先生成哈希:

caddy hash-password

命令会交互式提示你输入密码,输出一串以 $2a$ 开头的哈希。把哈希填到配置里(下面是占位示例,不是真实值):

admin.example.com {
	basicauth {
		alice $2a$14$REPLACE_WITH_YOUR_OWN_HASH
	}
	reverse_proxy 127.0.0.1:9000
}

基本认证只在 HTTPS 下才算可接受,Caddy 默认就是 HTTPS,所以问题不大;但它不能防暴力破解,密码要足够长。重要的后台建议再叠加 IP 限制或 VPN。

TLS 相关的几个选项

大多数情况下你不需要写 tls 指令。需要时常见的用法有两种。一是指定联系邮箱,方便证书机构发送到期或问题通知:

{
	email admin@example.com
}

二是使用自己已有的证书文件:

example.com {
	tls /etc/ssl/example.com/fullchain.pem /etc/ssl/example.com/privkey.pem
	file_server
}

私钥文件权限要收紧,只让 caddy 用户能读。调试证书申请时,可以在全局选项里临时使用测试环境,避免反复失败触及正式环境的频率限制,具体的指令请查官方文档,不要凭记忆乱写。

常用命令

caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
journalctl -u caddy --no-pager -n 50

改配置前先备份,改完先 validate 再 reload,出错时能快速回退。caddy fmt 可以统一缩进,Caddyfile 对缩进不敏感,但格式统一更容易看出括号是否配对。配置文件也应该纳入备份,见《服务器备份与基础监控》。

常见问题排查

证书一直申请不下来

先看日志:journalctl -u caddy -n 100。最常见的原因有几个:域名没有解析到本机;80/443 被防火墙或云安全组挡住;有别的程序占用了端口;域名走了 CDN 的代理模式,验证请求到不了源站。逐项排除即可。

访问返回 403 或 404

403 通常是目录权限问题,上面提到过;404 多半是 root 路径写错,或者文件名大小写不一致。Linux 文件名区分大小写。

502 Bad Gateway

说明 Caddy 连不上后端。确认后端是否真的在监听:sudo ss -ltnp | grep 8080,以及配置里的地址和端口是否一致。容器里的服务要确认端口映射正确。

reload 没有生效

先 validate;如果语法没问题,再检查是否改的是正在使用的那个文件。用 systemd 启动时读取的是 /etc/caddy/Caddyfile。

和 Nginx 比

Nginx 生态更大、可调参数更多;Caddy 上手更快、自动证书省心。个人站点用 Caddy 完全够了。两者并不冲突,学会一个再看另一个,会更容易理解 Web 服务器在做什么。

小结