
为什么我把 nginx + certbot 换成了一份 Caddyfile
如果你只是想让一台小服务器对外提供一个网站或一个 API,传统的做法是 nginx 加 certbot:先装 nginx,写一段 server 块,再跑 certbot 申请证书,然后配一条定时任务做续期。这套方案成熟、稳定,但每一步都有出错的可能,而且出错时通常不报错,只是浏览器打不开。
Caddy 2 把这些步骤收进了一个二进制文件。它的默认行为是:拿到域名就自动申请证书、自动续期、自动把 HTTP 跳转到 HTTPS。整个配置可以短到四行。这篇文章记录我在一台 1 核 2G 的云服务器上从零配置的完整过程,包含五个我实际踩过的坑。
1. 先做选择:什么情况下不该用 Caddy
在动手之前先说清楚边界。Caddy 不是 nginx 的全面替代品。
| 场景 | 建议 | 原因 |
|---|---|---|
| 单机、少量站点、需要 HTTPS | Caddy | 配置量约为 nginx 的三分之一 |
| 复杂 rewrite、大量 location 规则 | nginx | Caddy 的 matcher 语法需要重新学 |
| 需要通配符证书 | Caddy(需自定义编译) | 通配符必须走 DNS-01,插件要自己打包 |
| 已有成熟的 nginx 运维体系 | 维持现状 | 迁移收益不足以覆盖风险 |
| 需要极高并发与精细调优 | nginx / Envoy | Caddy 的调优参数相对少 |
我的场景是三个小站点加一个 Node 服务,属于第一行。如果你的服务器上已经跑了十几套 nginx 配置,迁移大概率不划算。
2. 前置条件与工具清单
开工前需要确认四件事,缺一件都会在后面卡住:
- 一台有公网 IP 的服务器(本文以 Debian 12 为例);
- 一个已解析到该 IP 的域名,A 记录生效;
- 安全组与系统防火墙同时放行 80 和 443;
- 服务器上没有其他程序占用 80/443(检查
ss -lntp)。
第三条是最容易被忽略的。很多云厂商的安全组默认只开 22,而系统内部还有一层 ufw 或 firewalld。两层都要开,只开一层等于没开。
3. 安装
官方仓库的方式最省事,也能跟着系统包管理器一起升级:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
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 caddy
装完 Caddy 会自己注册成 systemd 服务并立刻启动。此时访问服务器 IP,应该能看到一个默认页面。如果这一步就打不开,先别往下走,回头查安全组。
4. 一份最小可用的 Caddyfile
配置文件在 /etc/caddy/Caddyfile。最小版本只有三行:
example.com {
root * /var/www/example
file_server
}
保存后执行重载:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
先 validate 再 reload 是个值得养成的习惯。Caddy 在配置有语法错误时会拒绝启动,如果直接 reload 且配置写错,服务可能停在旧配置上,你会以为改动没生效,实际是根本没加载。
证书申请通常在几秒到几十秒内完成。journalctl -u caddy -f 能看到 ACME 的完整过程。
5. 加一个反向代理和压缩
假设本机 3000 端口跑着一个 Node 服务,把它挂到 api.example.com:
api.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:3000
}
example.com {
encode zstd gzip
root * /var/www/example
try_files {path} {path}/ /index.html
file_server
}
这里有两个细节值得说。第一,WebSocket 不需要额外配置,Caddy 会自动处理 Upgrade 头,这一点比 nginx 省事。第二,try_files 那行是给单页应用用的,让所有未命中的路径回落到 index.html;如果你不做这一行,前端路由刷新就会 404。
另外记得把后端服务绑在 127.0.0.1 而不是 0.0.0.0。后端端口直接暴露在公网,是这类部署最常见的失守点。
6. 五个我实际踩过的坑
坑一:80 端口不通,证书申请一直失败
HTTP-01 验证需要 Let’s Encrypt 从外部访问 http://你的域名/.well-known/acme-challenge/。如果 80 被墙或被安全组挡住,验证必然失败。这不是 Caddy 的问题,但报错信息不会直说。排查方法:在外部网络用 curl 访问该路径,看是否返回 404 而不是超时。
坑二:套了 CDN 之后证书申请不上
CDN 回源会占用 80,导致 ACME 验证请求到不了源站。两种解法:一是在 CDN 侧把 /.well-known/ 路径设为不缓存并直连源站;二是改用 DNS-01 验证。后者需要给 Caddy 编译对应的 DNS 插件,官方二进制里没有,得用 xcaddy 自己构建。
坑三:证书签发频率触到上限
Let’s Encrypt 对同一注册域名有每周签发数量上限。反复重装、反复改配置、反复删数据目录,很容易把额度耗光,然后进入一段”怎么试都失败”的状态。调试阶段建议先用 tls internal 生成自签证书,确认路由和反代都通了,再切回自动证书。
坑四:日志目录权限
开启 access log 后如果指定的目录不存在或 Caddy 用户无写权限,服务会启动失败。默认配置已经处理好,但自己改路径时容易忘。先 mkdir -p 再 chown caddy:caddy。
坑五:改完配置忘了 reload
这个坑听起来很蠢,但发生频率最高。Caddyfile 改完不会自动生效,必须 systemctl reload caddy。更麻烦的是”改了一半”:文件保存了,reload 忘了,半小时后你还在排查”为什么新规则不生效”。
7. 怎么确认它真的在工作
配完之后做三项检查,基本可以覆盖 90% 的问题:
curl -I https://你的域名,看是否返回 200 且带strict-transport-security;curl -I http://你的域名,应当返回 308 跳转到 https;journalctl -u caddy --since "10 min ago",确认没有 ACME 报错。
再加一条长期观察:证书续期发生在到期前 30 天左右,建议在续期窗口前后各看一次日志,别等一年后才发现续期一直失败。
写在最后
Caddy 真正省下的不是配置行数,而是一类反复出现的失败模式:证书过期、跳转漏配、续期任务没跑。这些事在 nginx 方案里都需要人记着,而在 Caddy 里是默认行为。
代价是你要接受它的抽象。当配置出问题时,你排查的不是熟悉的 location 匹配顺序,而是一套新的 matcher 规则。对小规模部署,这个交换是值得的;对已经在 nginx 上跑了多年的大系统,未必。
你现在用的是 nginx 还是 Caddy?如果迁过,最麻烦的一步是哪一步?

评论 (0)