Caddy 是一个用 Go 语言编写的、现代化的、高性能 Web 服务器。它被设计为“开箱即用”的,尤其是对于 HTTPS。
① 自动 HTTPS (Automatic HTTPS)
这是 Caddy 最著名的特性。您只需在配置中提供一个域名,Caddy 就会自动通过 Let's Encrypt 或 ZeroSSL 申请、配置、安装并自动续期 TLS 证书。它还会自动将 HTTP 重定向到 HTTPS。
② 简洁的 Caddyfile
它的配置文件(Caddyfile)被设计为人类可读的,非常简洁。几行配置就能实现 Nginx 需要几十行才能完成的复杂功能(例如反向代理 + 自动 HTTPS)。
③ API 驱动
Caddy 2 的核心是一个 JSON Admin API。Caddyfile 只是一个“适配器”,它会被翻译成 JSON 配置。这意味着 Caddy 天生就支持动态配置,非常适合云原生和自动化环境。
④ 零依赖
Caddy 是一个单一的 Go 二进制文件,不依赖任何外部库(如 OpenSSL)。这使得安装和部署(尤其是 Docker)极其简单。
⑤ 内存安全
Go 语言的特性使其免受像“心脏出血”(Heartbleed) 这类 C 语言常见的缓冲区溢出漏洞的影响。
这将自动把 Caddy 安装为一个 systemd 服务,这是在服务器上运行的最佳方式。
Debian / Ubuntu / Raspberry Pi OS
xxxxxxxxxx51sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https2curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg3curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list4sudo apt update5sudo apt install caddyCentOS / RHEL / Fedora
xxxxxxxxxx31sudo dnf install 'dnf-command(copr)'2sudo dnf copr enable @caddy/caddy3sudo dnf install caddy101docker pull caddy2# 运行最新版的 Caddy3# -p 80:80 -p 443:443 映射端口4# -v `pwd`/Caddyfile:/etc/caddy/Caddyfile 挂载配置文件5# -v caddy_data:/data 持久化证书和 Caddy 状态6docker run -d -p 80:80 -p 443:443 \7 -v `pwd`/Caddyfile:/etc/caddy/Caddyfile \8 -v caddy_data:/data \9 --name my-caddy \10 caddy(更多 Docker 细节请参见 6.5 节)
您可以从 Caddy 的 GitHub Releases 页面 下载适用于您系统的二进制文件,解压后即可使用。
xxxxxxxxxx21caddy version2# v2.x.x ...Caddy 的 CLI 非常强大,您甚至不需要 Caddyfile 就能完成很多事:
启动一个静态文件服务器(当前目录)
21# 在 8080 端口启动,并开启文件浏览2caddy file-server --listen :8080 --browse访问 http://localhost:8080 即可看到效果。
启动一个快速反向代理
21# 将所有到 8080 端口的请求,转发到本地的 9000 端口2caddy reverse-proxy --from :8080 --to localhost:9000
Caddyfile 是配置 Caddy 的最常用方式。
/etc/caddy/Caddyfile/etc/caddy/CaddyfileCaddyfile191# Caddyfile2# 这是一个注释34# 1. 站点地址 (Site Address)5example.com {6 # 2. 指令 (Directive)7 reverse_proxy localhost:80808}910# 另一个站点11sub.example.com {12 root * /var/www/subdomain13 file_server14}1516# 绑定到特定端口17localhost:8080 {18 respond "Hello, Caddy!"19}example.com 是一个站点块。reverse_proxy。localhost:8080。这是 Caddy 2 与 Caddy 1 最大的区别,也是其强大的根源。Matcher (匹配器) 是一个过滤器,它决定了哪些请求应该被特定的指令处理。
Matcher 有三种定义方式:
匿名 Matcher (最常用): 直接跟在指令后面。
51# 只有 /api/* 路径的请求才会被反向代理2reverse_proxy /api/* localhost:808034# 只有 /images/* 路径的请求才会被 file_server 处理5file_server /images/*命名 Matcher ( @name ) (处理复杂逻辑):
使用 @name 定义一个匹配器,然后在指令中引用它。
191# @api 匹配所有 /api/ 路径下的 POST 或 PUT 请求2 {3 path /api/*4 method POST PUT5}6reverse_proxy @api localhost:800078# @static 匹配静态文件9 {10 path *.css *.js *.jpg *.png11}12file_server @static1314# @forbidden 匹配特定路径15 {16 path /admin/* /config/*17}18# 对匹配到的请求返回 40319error @forbidden 403所有请求 (*) (默认):
如果您不提供 Matcher,Caddy 默认使用 *,即“匹配所有请求”。
51# 匹配所有请求2reverse_proxy localhost:808034# 等同于5reverse_proxy * localhost:8080常用的 Matcher 类型:
path /path/to/*: 匹配 URL 路径(支持通配符)。host example.com *.example.com: 匹配 Host 头。method GET POST: 匹配 HTTP 方法。header X-Api-Key "secret": 匹配请求头。query key=value: 匹配 URL 查询参数。
file_server)托管静态 HTML/CSS/JS 网站。
root: 必须与 file_server 配合使用,用于指定网站文件的根目录。browse: 当找不到 index.html 时,以列表形式显示目录内容(可选)。
xxxxxxxxxx121example.com {2 # 1. 设置网站根目录3 root * /var/www/my-site4 5 # 2. 启用静态文件服务6 file_server7 8 # 3. (可选) 启用目录浏览9 file_server /documents/* {10 browse11 }12}reverse_proxy)将请求转发给后端的 Web 应用(Node, Python, Go, Java, PHP 等)。
基础用法:
41api.example.com {2 # 将所有请求转发到 8000 端口3 reverse_proxy localhost:80004}负载均衡:
Caddy 原生支持负载均衡和健康检查。
151api.example.com {2 reverse_proxy localhost:8001 localhost:8002 localhost:8003 {3 # 负载均衡策略4 # round_robin (轮询, 默认)5 # least_conn (最少连接)6 # first (第一个可用)7 # random (随机)8 lb_policy least_conn9 10 # 主动健康检查11 health_uri /healthz12 health_interval 10s13 health_timeout 5s14 }15}转发请求头 (重要):
Caddy 默认会设置 Host, X-Forwarded-For, X-Forwarded-Proto 和 X-Forwarded-Host 头,这比 Nginx 智能。
header_up: 向后端发送自定义的请求头。header_down: 从后端响应中修改/删除头,再发给客户端。xxxxxxxxxx91api.example.com {2 reverse_proxy localhost:8000 {3 # 1. 向后端发送一个 "My-Header"4 header_up My-Header "my-value"5 6 # 2. 从后端响应中删除 "X-Powered-By" 头7 header_down -X-Powered-By8 }9}tls)Caddy 默认对所有公网域名启用自动 HTTPS。
⚠️ 自动 HTTPS 的前提:
- Caddy 必须能通过公网 IP 访问。
- 域名的 A/AAAA 记录必须指向该公网 IP。
- 服务器的 80 和 443 端口必须对外开放(防火墙允许)。Caddy 会在 80 端口响应 ACME HTTP-01 质询,并在 443 端口提供 TLS 服务。
41# 只要这样写,HTTPS 就会自动启用2example.com {3 reverse_proxy localhost:80004}自定义 TLS 选项:
tls email: 指定用于 Let's Encrypt 账户的邮箱。tls internal: (开发环境常用) 使用 Caddy 自己的内部 CA 签发证书(浏览器会不信任,需手动导入 Caddy 的根证书)。tls /path/to/cert.pem /path/to/key.pem: 手动指定证书文件。tls { ... }: 高级 TLS 配置块。xxxxxxxxxx271# 示例 1: 本地开发环境 (http://localhost)2localhost {3 reverse_proxy localhost:80004}56# 示例 2: 本地开发环境 (https://local.dev)7local.dev {8 # Caddy 会自动生成一个自签名证书9 # 第一次运行时,Caddy 会提示您输入 sudo 密码来安装其根 CA10 tls internal11 reverse_proxy localhost:800012}1314# 示例 3: 指定邮箱15example.com {16 tls your-email@example.com17 reverse_proxy localhost:800018}1920# 示例 4: DNS 质询 (用于通配符证书)21# 这需要一个带 DNS 插件的 Caddy 版本 (见 6.4 节)22*.example.com {23 tls {24 dns cloudflare {env.CLOUDFLARE_API_TOKEN}25 }26 reverse_proxy localhost:800027}如 3.2 节所述,Matcher 是路由的基石。
示例:try_files (Nginx 常用)
try_files 尝试按顺序查找文件,如果都找不到,则执行一个后备操作(通常是 index.php)。
在 Caddy 中,try_files 是 file_server 的一个选项。
x
1# 典型 PHP-FPM / SPA (React/Vue) 配置2example.com {3 root * /var/www/public4 5 # 尝试查找文件,如果找不到,就重写到 /index.php (或 /index.html for SPA)6 try_files {path} {path}/ /index.php?{query}7 8 # 将 .php 文件发送到 PHP-FPM9 php_fastcgi localhost:900010 11 file_server12}示例:handle 与 route (高级路由)
handle: 处理匹配的请求,但不会停止后续处理(除非显式停止)。route: 互斥路由。一旦匹配,后续 route 块将被跳过。xxxxxxxxxx131example.com {2 # 匹配 /admin/*, 终止后续处理3 /admin/*4 handle @admin {5 # 在这里进行 IP 限制等6 respond "Admin area"7 }89 # 不匹配 /admin/* 的其他请求10 handle {11 reverse_proxy localhost:800012 }13}redir)xxxxxxxxxx231# 1. 自动 www 重定向 (Caddy 自动处理)2# 这样写,Caddy 会自动将 www.example.com 308 重定向到 example.com3example.com, www.example.com {4 ...5}67# 2. HTTP 重定向到 HTTPS (Caddy 自动处理)8# 只要 Caddy 启用了 HTTPS,它就会自动将 HTTP (80) 重定向到 HTTPS (443)910# 3. 手动重定向11example.com {12 # 永久重定向 (301)13 redir /old-page /new-page14 15 # 临时重定向 (302)16 redir /temp-page /new-temp-page 30217 18 # 使用正则表达式19 {20 path_regexp ^/blog/(\d+)/([\w-]+)$21 }22 redir @blog /article/{re.2} 30123}log)Caddy 默认开启结构化 (JSON) 日志,输出到 stderr (当使用 systemd 时,由 journald 捕获)。
xxxxxxxxxx191example.com {2 ...3 4 log {5 # 输出到文件6 output file /var/log/caddy/example.com.log7 8 # (可选) 设置日志格式。json (默认) 或 console (更易读)9 format console 10 11 # (可选) 设置日志级别12 level INFO13 }14}1516# 全局禁用日志17{18 log19}header)用于添加、修改或删除响应头。
xxxxxxxxxx171example.com {2 # ...3 4 header {5 # 1. 添加响应头 (例如 CORS)6 Access-Control-Allow-Origin "*"7 8 # 2. 添加安全头9 Strict-Transport-Security "max-age=31536000;"10 X-Frame-Options "DENY"11 X-Content-Type-Options "nosniff"12 13 # 3. 删除响应头 (注意开头的 -)14 -Server15 -X-Powered-By16 }17}encode)启用 gzip 或 zstd 压缩。
xxxxxxxxxx61example.com {2 # 启用 zstd (优先) 和 gzip 压缩3 encode zstd gzip4 5 # ...6}handle_errors)为 4xx 或 5xx 错误提供自定义页面。
xxxxxxxxxx171example.com {2 # ...3 4 handle_errors {5 # 捕获所有错误,重写到 /error.html6 # Caddy 会保留原始的状态码7 rewrite * /error.html8 9 # 渲染错误页面10 file_server {11 root /var/www/errors12 }13 14 # (可选) 也可以直接响应15 # respond "{http.error.status_code} - {http.error.status_text}"16 }17}snippet, import)这是 Caddyfile 保持简洁的秘诀。
xxxxxxxxxx221# 1. 定义一个代码片段 (snippet)2(security_headers) {3 header {4 Strict-Transport-Security "max-age=31536000;"5 X-Frame-Options "DENY"6 }7}89# 2. 在站点中导入 (import)10example.com {11 import security_headers12 reverse_proxy localhost:800013}1415sub.example.com {16 import security_headers17 root * /var/www/sub18 file_server19}2021# 也可以导入文件22# import /etc/caddy/snippets/common.conf
如果您通过 apt 或 dnf 安装,Caddy 已被配置为 systemd 服务 (caddy.service)。
配置文件: /etc/caddy/Caddyfile
启动服务: sudo systemctl start caddy
停止服务: sudo systemctl stop caddy
开机自启: sudo systemctl enable caddy
查看状态: sudo systemctl status caddy
查看日志 (重要):
xxxxxxxxxx51# 实时查看日志2sudo journalctl -u caddy -f34# 查看最后 100 行5sudo journalctl -u caddy -n 100 --no-pager重新加载配置 (优雅重载):
xxxxxxxxxx51# (推荐) Caddy 官方提供的 reload 命令2sudo caddy reload34# (等效) 手动触发 systemd 重载5# sudo systemctl reload caddy
如果您是手动下载二进制文件:
caddy run: (前台运行) 在当前目录查找 Caddyfile 并运行。日志输出到控制台。caddy start: (后台运行) 在后台启动 Caddy 服务。caddy stop: 停止后台运行的 Caddy 实例。caddy reload: (核心) 优雅地加载新的 Caddyfile 配置(在 caddy start 模式下使用)。caddy validate: 检查 Caddyfile 语法是否正确。caddy adapt: 将 Caddyfile 翻译成 JSON 配置(用于调试)。这是生产环境中的标准操作流程:
编辑 Caddyfile
11sudo nano /etc/caddy/Caddyfile验证 Caddyfile (防止配置错误导致服务中断)
31caddy validate --config /etc/caddy/Caddyfile2# 如果没问题,它会安静地退出3# 如果有问题,它会打印错误应用配置
31sudo caddy reload --config /etc/caddy/Caddyfile2# (如果使用 systemd 且 Caddyfile 在默认路径, 可简化为)3# sudo caddy reload
Caddyfile 只是一个“适配器”。Caddy 进程真正运行的是 JSON 配置。
caddy adapt 命令可以向您展示 Caddyfile 被翻译成了什么:
11caddy adapt --config /etc/caddy/Caddyfile --prettyCaddyfile:
31example.com {2 reverse_proxy localhost:80003}会被翻译成 (简化版):
xxxxxxxxxx291{2 "apps": {3 "http": {4 "servers": {5 "srv0": {6 "listen": [":443"],7 "routes": [8 {9 "match": [{"host": ["example.com"]}],10 "handle": [11 {12 "handler": "reverse_proxy",13 "upstreams": [{"dial": "localhost:8000"}]14 }15 ]16 }17 ]18 }19 }20 },21 "tls": {22 "automation": {23 "policies": [24 {"hosts": ["example.com"]}25 ]26 }27 }28 }29}Caddy 默认在 localhost:2019 运行一个管理 API。这允许您在不重启/重载 Caddy 的情况下动态更改配置。
安全警告: Admin API 默认只监听本地回环地址 (
localhost)。请勿将其暴露在公网,除非您配置了严格的防火墙或认证。
常用 API 操作 (curl):
获取当前配置:
11curl http://localhost:2019/config/加载新配置 (覆盖):
31# 假设 config.json 是您完整的 JSON 配置文件2curl -X POST -H "Content-Type: application/json" \3 -d @config.json http://localhost:2019/load在特定路径添加/修改配置 (例如添加一个站点):
31# 假设 new-site.json 只包含一个新站点的 JSON 块2curl -X POST -H "Content-Type: application/json" \3 -d @new-site.json http://localhost:2019/config/apps/http/servers/my_new_site删除配置 (例如删除站点):
11curl -X DELETE http://localhost:2019/config/apps/http/servers/my_new_site
这是 Caddy 2 最常见的陷阱:
如果您使用 curl 通过 API 更新了配置,/etc/caddy/Caddyfile 文件不会自动更新。
此时,如果您(或其他人)运行了 caddy reload (或 systemctl reload caddy),Caddy 会重新读取 Caddyfile,导致您通过 API 所做的所有更改全部丢失。
解决方案:选择一种工作流并坚持下去!
工作流 A (推荐): Caddyfile-First (GitOps)
/etc/caddy/Caddyfile 文件。caddy reload (或 systemctl reload caddy) 来应用更改。工作流 B (高级): API-First (动态自动化)
curl ... /load API 将其 POST 给 Caddy。xcaddy 构建自定义插件Caddy 的许多功能(例如 DNS 质询)都是插件。官方的 Caddy 二进制文件只包含标准插件。
如果您需要 DNS 插件(例如用于 Cloudflare 以获取通配符证书),您必须使用 xcaddy 重新编译 Caddy。
安装 go (Go 语言环境) 和 xcaddy:
xxxxxxxxxx21# (以 Ubuntu 为例)2sudo apt install golang-goxxxxxxxxxx11go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest(确保 $HOME/go/bin 在您的 $PATH 中)
构建 Caddy (包含 Cloudflare 插件):
xxxxxxxxxx21xcaddy build \2 --with github.com/caddy-dns/cloudflare运行 xcaddy build
这会在当前目录生成一个 caddy 二进制文件。用它替换您系统中的 Caddy (例如 /usr/bin/caddy),然后重启 Caddy 服务。
在 Caddyfile 中使用 DNS 质询:
xxxxxxxxxx71*.example.com {2 tls {3 # Caddy 会从环境变量 CLOUDFLARE_API_TOKEN 中读取 API 令牌4 dns cloudflare {env.CLOUDFLARE_API_TOKEN}5 }6 reverse_proxy ...7}在 Docker 中运行 Caddy 是非常推荐的方式。
推荐的 docker-compose.yml 结构:
321version"3.7"23services4 caddy5 imagecaddylatest6 container_namecaddy7 restartunless-stopped8 ports9"80:80" # HTTP10"443:443" # HTTPS11"443:443/udp" # (可选) HTTP/312 volumes13 # 1. 挂载 Caddyfile14./Caddyfile:/etc/caddy/Caddyfile15 16 # 2. 挂载网站数据 (如果提供静态文件)17./my-site:/var/www/my-site18 19 # 3. (重要!) 挂载数据卷以持久化证书和 Caddy 状态20caddy_data:/data2122 # (示例) 后端服务23 my-app24 imagemy-node-app25 container_namemy-app26 # Caddy 可以通过服务名 "my-app" 访问它27 # ports:28 # - "8000:8000" # (可选) 暴露给宿主机调试2930volumes31 # (重要!) 定义持久化卷32 caddy_data对应的 Caddyfile (./Caddyfile):
xxxxxxxxxx101# Caddyfile2example.com {3 # 在 Docker Compose 网络中,直接使用服务名4 reverse_proxy my-app:80005}67static.example.com {8 root * /var/www/my-site9 file_server10}
Q: 我的更改没有生效!
A:
caddy reload (或 systemctl reload caddy) 了吗?caddy validate 检查语法错误。journalctl -u caddy -f。日志会告诉您 Caddyfile 加载失败的原因。Q: 自动 HTTPS 失败了!
A: 检查日志!journalctl -u caddy -f。99% 的原因是:
Q: 我在本地开发,Caddy 一直重定向到 HTTPS 导致失败。
A:
如果您想在 http://localhost 上开发,请在 Caddyfile 中明确使用 http://:
xxxxxxxxxx31http://localhost:8080 {2...3}
如果您想在 https://local.dev 上开发,请使用 tls internal:
xxxxxxxxxx41local.dev {2 tls internal3 ...4}(您可能需要在浏览器中手动信任 Caddy 的根 CA 证书)。