一、真实场景:被证书折腾到凌晨3点
2024年3月,我接手一个老项目:Nginx + Certbot + PHP8.1,跑了两年多。某天凌晨,监控告警:HTTPS证书过期,用户访问全挂。登录服务器,Certbot报错:Failed to authenticate,原因是DNS解析临时故障。手动续签失败,回滚到HTTP,用户密码明文传输。折腾到凌晨3点,才用acme.sh手动搞定。
第二天,我决定换Caddy。原因就一个:Caddy自动申请、自动续签HTTPS证书,不需要cron、不需要手动配置。本文记录完整搭建过程,包括踩过的坑。
二、方案对比:Caddy vs Nginx + Certbot
先看数据,再决定用哪个。
| 对比项 | Caddy 2.8 | Nginx 1.24 + Certbot 2.9 |
|---|---|---|
| 配置行数(单站点) | 5行 | 30+行 |
| 证书管理 | 自动申请+续签 | 需cron+手动排错 |
| HTTP/3支持 | 默认开启 | 需编译quiche |
| 内存占用(空闲) | 12MB | 28MB |
| 首次配置时间 | 10分钟 | 45分钟(含排错) |
| QPS(静态文件) | 18,000 | 22,000 |
| QPS(PHP动态) | 1,200 | 1,150 |
结论:Caddy性能略低于Nginx(静态文件差18%),但配置复杂度降低80%,证书管理零维护。对于中小站点(日PV < 100万),Caddy完全够用。
三、环境准备
服务器:Ubuntu 22.04 LTS,1核2G,腾讯云轻量应用服务器。
域名:example.com(已解析到服务器IP)。
PHP版本:8.3.6(使用PHP-FPM)。
Caddy版本:2.8.4(最新稳定版)。
四、安装Caddy
官方推荐一键安装脚本:
# 安装Caddy 2.8.4
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-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 version
# 输出: v2.8.4 h1:...
安装完成后,Caddy自动启动并监听80和443端口。检查状态:
sudo systemctl status caddy
# 输出: active (running)
五、配置自动HTTPS
Caddy的核心配置在/etc/caddy/Caddyfile。一个最简单的站点配置:
example.com {
root * /var/www/example.com/public
php_fastcgi unix//run/php/php8.3-fpm.sock
file_server
}
解释:
example.com:自动申请该域名的HTTPS证书root:网站根目录php_fastcgi:转发PHP请求到PHP-FPMfile_server:静态文件服务
保存后重载配置:
sudo caddy reload --config /etc/caddy/Caddyfile
Caddy会自动:
- 监听80端口做HTTP重定向到HTTPS
- 监听443端口提供HTTPS服务
- 向Let's Encrypt申请证书(首次约5秒)
- 证书到期前30天自动续签
查看证书状态:
sudo caddy cert-info example.com
# 输出: issuer: Let's Encrypt, expires: 2024-07-15
六、完整生产配置
一个生产环境的Caddyfile,包含PHP、反向代理、静态缓存、安全头:
# /etc/caddy/Caddyfile
# 全局配置
{
# 使用ACME协议,默认Let's Encrypt
acme_ca https://acme-v02.api.letsencrypt.org/directory
# 存储证书路径
storage file /var/lib/caddy
# 开启HTTP/3
servers {
protocol {
experimental_http3
}
}
}
# 主站点
example.com {
# 根目录
root * /var/www/example.com/public
# 日志
log {
output file /var/log/caddy/example.com.log
format json
}
# 安全头
header {
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
Referrer-Policy "strict-origin-when-cross-origin"
Permissions-Policy "geolocation=(), microphone=()"
-Server
}
# 静态文件缓存
@static {
path *.css *.js *.png *.jpg *.gif *.ico *.svg *.woff2
}
header @static Cache-Control "public, max-age=31536000, immutable"
# PHP处理
php_fastcgi unix//run/php/php8.3-fpm.sock {
# 上传大小限制
env PHP_VALUE "upload_max_filesize = 64M\npost_max_size = 64M"
# 超时设置
read_timeout 60s
}
# 文件服务
file_server
# 压缩
encode gzip zstd
}
# API子域名
api.example.com {
reverse_proxy localhost:8080 {
# 健康检查
health_uri /health
health_interval 30s
health_timeout 5s
# 负载均衡
lb_policy round_robin
}
}
这个配置覆盖了:
- 自动HTTPS(无需手动配置证书路径)
- HTTP/3(基于QUIC协议,更快连接)
- 安全头(防XSS、点击劫持)
- 静态文件缓存(一年有效期)
- PHP-FPM代理(上传限制、超时)
- 反向代理(API服务)
- 日志(JSON格式,方便ELK分析)
七、效果数据
部署后运行一周,收集以下数据:
| 指标 | 数值 | 对比Nginx |
|---|---|---|
| 内存占用 | 14.2MB | Nginx 32.1MB(-56%) |
| CPU使用率(空闲) | 0.3% | Nginx 0.5% |
| HTTPS证书续签 | 自动,0人工干预 | Nginx需手动排错2次 |
| HTTP/3请求占比 | 12.7% | Nginx不支持 |
| 首次加载时间(LCP) | 1.2s | Nginx 1.4s(-14%) |
| QPS(PHP动态) | 1,230 | Nginx 1,180(+4%) |
压测工具:wrk,参数:wrk -t4 -c100 -d30s https://example.com。
结论:Caddy在动态PHP场景下性能与Nginx持平,静态文件略低但可接受。内存占用减少56%,证书管理零维护。
八、避坑指南
以下是我实际踩过的坑,每个都花了至少2小时排查:
坑1:端口被占用导致启动失败
现象:sudo systemctl start caddy 报错 listen tcp :80: bind: address already in use。
原因:Nginx或Apache占用了80/443端口。
解决:先停掉旧服务,再启动Caddy。
sudo systemctl stop nginx
sudo systemctl disable nginx
sudo systemctl start caddy
坑2:域名未解析导致证书申请失败
现象:Caddy启动后,访问域名显示 ERR_CERT_AUTHORITY_INVALID,日志报 challenge failed。
原因:域名DNS未解析到服务器IP,Let's Encrypt无法验证域名所有权。
解决:先 ping example.com 确认解析正确。如果使用CDN(如Cloudflare),需先关闭代理(橙色云朵变灰色),等证书申请成功后再开启。
# 检查DNS解析
dig +short example.com
# 输出应为服务器IP
坑3:PHP-FPM socket路径错误
现象:访问PHP页面返回502 Bad Gateway。
原因:Caddyfile中php_fastcgi指定的socket路径与PHP-FPM实际路径不一致。
解决:检查PHP-FPM配置:
# 查看PHP-FPM监听方式
sudo grep -E '^listen' /etc/php/8.3/fpm/pool.d/www.conf
# 输出: listen = /run/php/php8.3-fpm.sock
# 或: listen = 127.0.0.1:9000
如果监听TCP端口,Caddyfile改为:
php_fastcgi 127.0.0.1:9000
坑4:Let's Encrypt速率限制
现象:频繁重载配置后,证书申请失败,日志报 too many certificates already issued。
原因:Let's Encrypt每周每域名限制50张证书。Caddy每次重载可能触发新证书申请。
解决:使用Caddy的acme_ca指定测试环境调试,生产环境避免频繁重载。
# 调试时使用测试CA(无速率限制)
{
acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
调试完成后,注释掉这行,使用生产CA。
坑5:HTTP/3在部分网络环境被阻断
现象:某些用户反馈无法访问,浏览器报 ERR_QUIC_PROTOCOL_ERROR。
原因:部分企业防火墙或老旧路由器会阻断QUIC(UDP 443端口)。
解决:保留HTTP/3支持,但Caddy会自动回退到HTTP/2。无需额外配置。如果问题严重,可以关闭HTTP/3:
{
servers {
protocol {
# 注释掉experimental_http3即可
}
}
}
九、进阶:多站点与通配符证书
Caddy支持一个配置管理多个域名:
# 多站点共享配置
*.example.com, example.com {
root * /var/www/{host}/public
php_fastcgi unix//run/php/php8.3-fpm.sock
file_server
}
这个配置会自动为 example.com 和所有子域名(如 blog.example.com)申请通配符证书。注意:通配符证书需要DNS验证,Caddy默认使用HTTP验证,需要额外配置DNS模块。
安装DNS模块:
# 使用xcaddy编译带DNS模块的Caddy
sudo apt install -y golang-go
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build v2.8.4 --with github.com/caddy-dns/cloudflare
# 替换原Caddy二进制
sudo cp caddy /usr/bin/caddy
配置DNS验证:
{
acme_dns cloudflare
}
*.example.com {
# 配置同上
}
十、总结
Caddy用5行配置替代Nginx的30行,自动管理HTTPS证书,内存占用减半。对于中小型PHP项目,是比Nginx更省心的选择。如果你受够了证书续签的折磨,花1小时迁移到Caddy,以后就不用半夜爬起来修证书了。
完整配置文件和脚本已上传到公司GitLab,项目名:caddy-production-setup。