用 Caddy 取代 Nginx 做反向代理:三行 Caddyfile 自动 HTTPS 与续期,附 Compose 写法
- 官网:
- 仓库:
- 文档:
Nginx 作为反向代理的默认答案已经超过十年,但它从来不是最省事的选择。每加一个服务,你要写 server block、跑 Certbot、确认 renewal cron 没失效、测出配置没有 syntax error 才能 reload。这套流程本身没错,只是在 Let's Encrypt 普及、单机跑多个服务已成常态的今天,显得过于繁琐。
Caddy 的设计目标就是把这些摩擦去掉:一个域名三行配置,HTTPS 自动申请、自动续期,HTTP redirect 也不用手写。这里不是说 Nginx 不好——高流量、需要细粒度调校的场景,Nginx 仍是首选。但在 VPS 上跑几个服务的开发者和中小企业,Caddy 值得认真评估。
Caddy 的自动 HTTPS 到底做了什么
Caddy 启动时,会对所有配置了公开域名的站点自动完成:
- 向 Let's Encrypt(或 ZeroSSL)申请 TLS 证书
- 完成 ACME HTTP-01 挑战(需要确保 80 端口能从外部连入)
- 把证书存放在
~/.local/share/caddy(或 Docker volume) - 设置自动 renewal,证书到期前 30 天自动更新
- 强制 HTTP 跳转 HTTPS,不需要任何额外配置
整个过程在 Caddy 第一次启动时就完成,后续不需要管理。背后用的就是 Let's Encrypt,只是 Caddy 把这层操作完全内化了。
Caddyfile 语法
反向代理到本机 3000 端口,全站 HTTPS:
example.com {
reverse_proxy localhost:3000
}
就这样。对比 Nginx 加 Certbot 的完整配置,这三行覆盖了至少 40 行的工作量:HTTP → HTTPS redirect、证书申请、TLS 设置、proxy header 转发全部默认处理好。
多个域名代理到不同服务:
api.example.com {
reverse_proxy localhost:8080
}
app.example.com {
reverse_proxy localhost:3000
}
admin.example.com {
reverse_proxy localhost:4000
basicauth {
admin JDJhJDE0JHBhc3N3b3JkX2hhc2g
}
}
每个域名各自申请独立证书,互不影响。basicauth 指令直接写在 Caddyfile 里,不需要另外建 htpasswd 文件。
需要加 header(例如让后端应用知道原始 IP):
api.example.com {
reverse_proxy localhost:8080 {
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
}
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
}
}
AI 相关服务或其他需要长时间连接的应用,可以调整 timeout:
ai-api.example.com {
reverse_proxy localhost:11434 {
transport http {
read_timeout 5m
write_timeout 5m
}
}
}
在 VPS 上安装 Caddy(不用 Docker)
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
配置文件路径是 /etc/caddy/Caddyfile,修改完后:
sudo caddy fmt --overwrite /etc/caddy/Caddyfile # 格式化
sudo caddy validate --config /etc/caddy/Caddyfile # 语法检查
sudo systemctl reload caddy # 热重载,不中断连接
不像 Nginx 需要 nginx -t 确认语法再 reload,Caddy 的 validate 会在套用前做完整验证,reload 是真正的 zero-downtime reload。
与 Docker Compose 集成
最常见的场景:把 Caddy 放进 Docker Compose stack 里,反代其他服务。
services:
caddy:
image: caddy:2.11-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3 (QUIC)
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data # 存放凭证,必须 persist
- caddy_config:/config
networks: [proxy]
cap_drop: [ALL]
cap_add: [NET_BIND_SERVICE]
api:
image: myapp:v1.2.3
networks: [proxy]
# 注意:不需要 ports 设置,Caddy 通过 Docker network 直接连
volumes:
caddy_data:
caddy_config:
networks:
proxy:
对应的 Caddyfile(容器名称直接作为 upstream hostname):
api.example.com {
reverse_proxy api:8080
}
caddy_data volume 一定要 persist。Caddy 把证书存在这里,如果用 docker compose down -v 删掉它,下次启动会重新申请证书。Let's Encrypt 有速率限制(每个 registered domain 每周 50 张),开发时频繁重建 stack 很容易撞到。
443:443/udp 开启 HTTP/3 支持,Caddy 2.7+ 默认启用,让支持 QUIC 的浏览器走更快的连接。另外注意 ./Caddyfile 以只读方式挂载,容器内改不了配置,修改一律在宿主机上做,否则 reload 会失败。
本机开发用 HTTPS
Caddy 可以替本机服务签发由本机 CA 信任的证书:
caddy trust # 安装 Caddy 的本机 CA 到系统信任清单
localhost {
reverse_proxy localhost:3000
}
这样 https://localhost 就是有效的 HTTPS,不会出现证书警告,测试 HSTS、Secure cookie 等只能在 HTTPS 环境跑的功能时特别有用。注意这张 CA 只装在你执行 caddy trust 的那台机器上,局域网内其他设备访问仍然会报证书不受信任。
从 Nginx 换到 Caddy 需要考虑的事
几个 Nginx 能做但 Caddy 目前比较麻烦或做法不同的地方:
- 复杂的条件式路由:Nginx 可以用
if、map、geo模块做复杂条件判断。Caddy 有@matcher语法,但逻辑深的场景配置会比较冗长。 - fine-grained 缓存设置:Nginx 有成熟的 proxy cache 模块,Caddy 的缓存功能是社区外挂,需要自行编译。如果现有 Nginx 有复杂的缓存规则,迁移成本较高。
- 已有的 Certbot 证书无法直接移转:Caddy 和 Certbot 各自管理证书,切换时 Caddy 会重新申请。要注意 Let's Encrypt 速率限制,特别是子域名多的情况。
- Lua/OpenResty:如果用到 OpenResty 的能力,Caddy 没有对等方案。
以上这些对大多数 VPS 上的常规部署不构成问题。如果你的 Nginx 配置只是几个 server block 加 SSL,迁移到 Caddy 大概花半小时。