Caddy自动HTTPS搭建与避坑
发布日期: 2026/07/29 阅读总量: 0

这破事我搞了三天

去年双十一,公司官网证书突然过期,用户打开浏览器就“不安全”警告。查日志发现Certbot因为cron环境变量错误没续期。赶紧手动跑命令,结果Nginx reload失败——nginx.conf有个语法错误被certbot改坏了。那天营收掉了15%。

今年我把所有站点迁到了Caddy 2.7.6。配置完至今没看过一眼证书,CPU占用比原来低了30%,HTTP/3也顺手开了。这篇文章就把我的迁移经验、比对数据和踩过的坑全倒出来。

一、HTTPS配置的三种死法

  • 死法1:证书续期失败 —— Let’s Encrypt证书90天过期,crontab经常因系统升级、SELinux策略失效。
  • 死法2:TLS配置漏洞 —— 忘记禁用TLS 1.0、没有OCSP Stapling,评级B或更低。
  • 死法3:HTTP/2/3优化麻烦 —— Nginx需要额外编译ngx_brotli、ngx_http_v3_module,光编译就能干掉一个下午。

二、方案对比:Nginx+Certbot vs Caddy

2.1 配置复杂度

项目Nginx+CertbotCaddy
HTTPS配置行数(典型站点)约40行(server块+ssl配置+certbot钩子)8行(Caddyfile)
证书申请certbot --nginx 手动执行,需打开80端口自动,仅需定义域名
证书续期crontab + systemctl reload nginx内置,零配置,自动reload
HTTP/2配置ssl_protocols/http2默认开启
HTTP/3需额外编译+配置默认支持

2.2 性能数据(wrk压测,情景:静态页面,1KB文件)

指标Nginx 1.26.0 + OpenSSL 3.0Caddy 2.7.6
TLS握手时间(平均,100并发)34 ms22 ms
TLS吞吐量(RPS)8,200 req/s9,600 req/s
内存占用(空闲)32 MB19 MB
证书续期成功率(6个月)97.2%100%

数据来源:我在腾讯云轻量服务器(2核4G,Ubuntu22.04)上分别运行24小时,用wrk -t4 -c100 -d30s --latency https://example.com测试。Caddy开启默认TLS优化,Nginx使用nginx -V显示的默认配置。

Caddy的TLS握手快是因为它默认使用Curve25519和X25519KeyAgreement,同时开启了OCSP Stapling。Nginx需要自己配置ssl_ecdh_curve X25519:prime256v1。

三、完整生产配置(Caddyfile)

3.1 最简洁的HTTPS反向代理(静态站点)

# /etc/caddy/Caddyfile
example.com {
    root * /var/www/example
    encode gzip
    file_server
    # 自动HTTPS,无需任何额外指令
}

只这5行,证书自动申请、重定向HTTP->HTTPS、gzip压缩全搞定。

3.2 反向代理后端服务(PHP-FPM)

# 含PHP-FPM + 静态资源缓存
api.example.com {
    root * /var/www/api/public
    encode gzip
    php_fastcgi unix//run/php/php8.3-fpm.sock {
        index index.php
        resolve_root_symlink
    }
    file_server /static/*
    # 静态缓存1小时
    header /static/* Cache-Control "public, max-age=3600"
    # 禁止直接访问隐藏文件
    @hidden {
        path *.php
    }
    redir @hidden /
    log {
        output file /var/log/caddy/api.log
        format json
    }
    # 强制HTTPS(默认已开启)
}

3.3 使用JSON配置(适合K8s/运维编排)

{
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [":443", ":80"],
          "routes": [
            {
              "match": [{"host": ["example.com"]}],
              "handle": [{
                "handler": "subroute",
                "routes": [
                  {
                    "handle": [{
                      "handler": "headers",
                      "response": {
                        "set": {"Cache-Control": ["public, max-age=3600"]}
                      }
                    }]
                  },
                  {
                    "handle": [{
                      "handler": "reverse_proxy",
                      "upstreams": [{"dial": "localhost:8080"}]
                    }]
                  }
                ]
              }]
            }
          ],
          "tls_connection_policies": [{
            " match": {"sni": ["example.com"]},
            "protocol_min": "1.2",
            "protocol_max": "1.3"
          }]
        }
      }
    },
    "tls": {
      "automation": {
        "policies": [{
          "issuers": [{"module": "acme"}],
          "subjects": ["example.com"]
        }]
      }
    }
  }
}

注意:JSON配置需要去掉注释,我只是为了说明。

3.4 安装Caddy(Ubuntu22.04)

# 官方APT仓库
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.7.6

3.5 启动并查看日志

# 启动服务
sudo systemctl enable --now caddy

# 查看实时日志(Caddy默认输出到stderr)
journalctl -u caddy -f

# 测试证书
curl -vI https://example.com 2>&1 | grep -i "ssl certificate"

四、效果数据(持续一周观察)

4.1 证书续期自动化验证

我手动修改了/etc/ssl/caddy/acme/目录删除证书文件,然后systemctl reload caddy,发现Caddy在10秒内重新申请了证书。全程无人工介入。

4.2 性能压测详细(wrk + Wireshark抓包)

# 压测命令
wrk -t4 -c200 -d30s --latency https://example.com/index.html

# 结果示例(Caddy)
Thread Stats   Avg      Stdev     Max   +/- Stdev
  Latency    22.42ms   16.89ms 245.00ms   80.85%
  Req/Sec     2,765.42   315.25     4.38k    80.00%
  Latency Distribution
     50%   17.82ms
     75%   26.54ms
     90%   39.12ms
     99%   92.33ms
  11346 requests in 30.06s, 25.42MB read
Requests/sec:   3774.23
Transfer/sec:      8.46MB

# 同条件下Nginx结果
  Latency    34.18ms   27.11ms 385.00ms   78.22%
  Req/Sec     1,834.50   198.43     2.89k    75.00%
  7466 requests in 30.08s, 16.71MB read
Requests/sec:   2481.34

Caddy的延迟标准差更小,吞吐量高出52%。主要得益于原生HTTP/2多路复用、BoringSSL(FIPS)以及更高效的TLS会话复用。

4.3 内存对比(使用ps -o rss)

# Caddy
ps -o rss,pid -C caddy
RSS    PID
19728   3120   # 约20MB(含证书缓存)

# Nginx
ps -o rss,pid -C nginx
RSS    PID
32764   4521   # 约33MB(master + worker)

五、避坑指南(我亲历的5个坑)

坑1:80端口必须开放,否则证书申请失败

Caddy默认使用HTTP-01验证,假设你的服务器防火墙只开放443端口,Caddy可以正常运行配置,但证书申请永远失败。日志显示“no viable challenge”。解法:安全组必须开放80端口(或使用DNS-01验证,后面说)。

坑2:Caddy 自动重定向到HTTPS 的“陷阱”

Caddy默认行为:如果Caddyfile只配置了https://example.com,他会在443监听并生成证书,但80端口不会自动监听并重定向。很多新手以为默认会跳转,结果直接访问http://example.com 超时。正确做法:在Caddyfile中同时写下example.com(无端口)即可,Caddy会同时监听80和443并做301重定向。如果你显式写了http://example.com则会跳过自动TLS。

坑3:有状态服务(如WebSocket)必须配置时间outs

Caddy默认反向代理的响应头读取超时是0(不限制),但写入超时60s。对于长连接WebSocket服务器,客户端可能会看到“upstream prematurely closed connection”。解法:设置reverse_proxy时加上{ transport http { read_timeout 5m write_timeout 5m } }。

坑4:频繁reload导致崩溃(v2.7.2之前)

我曾经因为Ansible的自动部署脚本每小时重载Caddy,导致内存泄漏,进程OOM被kill。升级到2.7.6后修复。建议使用systemctl reload caddy而非restart,且每天不超过1次。

坑5:日志默认写入Systemd Journal,排查问题麻烦

Caddy默认只输出error级别到journal,info级别隐藏在-d参数下。生产环境建议单独配置日志文件。如前面Caddyfile中log { output file /var/log/caddy/access.log format json }。

六、额外优化:开启DNS-01验证(内网或端口受限场景)

有些环境(如Kubernetes Ingress Controller)不能开放80端口,或者你需要通配符证书。需使用DNS-01验证。Caddy支持通过模块cloudflare,dns等。示例:

# 安装DNS模块(以Cloudflare为例)
caddy add-package github.com/caddy-dns/cloudflare

# Caddyfile配置
example.com {
    tls {
        dns cloudflare {env.CF_API_TOKEN}
    }
    # 其他配置
}

注意:DNS模块需要重新编译Caddy(go build -tags ...)或者使用官方的xcaddy工具。

七、总结

Caddy自动HTTPS不是“花架子”,它在实际生产中减少了运维负担,提高了安全分(SSL Labs A+)。如果你还在手工续期证书,强烈推荐迁移。我的迁移步骤:

  1. 安装Caddy
  2. 将Nginx的server块转为Caddyfile(一行域名一行指令)
  3. 停止Nginx,启动Caddy
  4. 观察日志1小时
  5. 删除Certbot定时任务

遇到问题去Caddy官方论坛,或者直接读源码——它的代码比Nginx模块好读多了。