Caddy自动HTTPS搭建实战:从零到生产
发布日期: 2026/07/18 阅读总量: 0

一、真实场景:被证书折腾到凌晨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.8Nginx 1.24 + Certbot 2.9
配置行数(单站点)5行30+行
证书管理自动申请+续签需cron+手动排错
HTTP/3支持默认开启需编译quiche
内存占用(空闲)12MB28MB
首次配置时间10分钟45分钟(含排错)
QPS(静态文件)18,00022,000
QPS(PHP动态)1,2001,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-FPM
  • file_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.2MBNginx 32.1MB(-56%)
CPU使用率(空闲)0.3%Nginx 0.5%
HTTPS证书续签自动,0人工干预Nginx需手动排错2次
HTTP/3请求占比12.7%Nginx不支持
首次加载时间(LCP)1.2sNginx 1.4s(-14%)
QPS(PHP动态)1,230Nginx 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。