Caddy 多站点配置实战:静态站、路由与路径重写
上一篇讲了 Caddy 怎么自动接 HTTPS,这篇聚焦一个 Caddyfile 里同时托管多个站点时的配置技巧:静态站、自定义错误页、按路径分流、子路径重写、多域名复用同一个后端。下面所有片段都来自本服务器的真实配置。
一、站点块:一个域名一段配置
Caddyfile 的基本单元是站点块(site block),域名写在开头,花括号里是这个站点的指令。多域名 + 多站点共享一个实例、一份配置:
wuee.cn {
root * /var/www/wuee.cn
file_server
}
napcat.wuee.cn {
reverse_proxy 127.0.0.1:6099
}
astrbot.wuee.cn {
reverse_proxy 127.0.0.1:6185
}
root * 里的 * 表示这个站点根路径下的所有请求都从这里找文件;file_server 提供静态文件服务。Caddy 启动时会自动为每个域名申请并续期证书,站点点名即可。
二、静态站 + 自定义错误页
静态站总会遇到不存在的路径,默认的 404 页面很丑。用 handle_errors 统一重写:
sntfct.cn {
root * /var/www/sntfct.cn
file_server
handle_errors {
rewrite * /404.html
file_server
}
}
效果:任何 4xx/5xx 错误都返回 /var/www/sntfct.cn/404.html,状态码保持原始错误码,页面内容统一。注意 handle_errors 块里的 file_server 是单独写的,因为它运行在错误处理阶段。
三、handle 块:静态站和 API 分流
同一个域名下,静态页面和一个后端 API 并存时,用 handle 按路径前缀分流。Caddy 的 handle 是互斥的——只执行第一个匹配的块:
sntfct.cn {
root * /var/www/sntfct.cn
file_server
handle /api/* {
reverse_proxy 127.0.0.1:9999
}
handle /jm/* {
reverse_proxy 127.0.0.1:8088
}
handle_errors {
rewrite * /404.html
file_server
}
}
请求 /api/stats 走 9999 端口的后端,/jm/xxx 走 8088,其他路径落回 file_server 静态服务。三段互不干扰,顺序无关紧要——handle 内部按最长匹配优先,不是按书写顺序。
四、路径重写:子路径应用的根路径访问
不少 Web 应用自带子路径(如 Genshin 面板的 WebUI 在 /app/),用户却希望直接访问域名根路径就打开界面。直接用 rewrite 把根路径改写:
gs.wuee.cn {
rewrite / /app/
reverse_proxy 127.0.0.1:8765
}
为什么是 rewrite 而不是简单反代:应用内部渲染的链接、跳转都基于 /app/ 前缀。如果根路径直接反代到应用,浏览器访问 /,应用 302 跳转到 /app/,跳转时 location 头携带的认证信息可能丢失,导致登录态异常。rewrite / /app/ 在转发前就把路径改好,后端收到的就是 /app/ 开头的完整路径,一切正常。
rewrite / /app/ 只匹配根路径。像 /app/xxx 这类路径本身已经在正确前缀下,不需要再处理。五、多域名复用同一个后端
两个域名指向同一个服务时,不用复制配置,在一个站点块里写多个域名即可(证书会为每个域名单独申请):
gs.wuee.cn,
gs.sntfct.cn {
rewrite / /app/
reverse_proxy 127.0.0.1:8765
}
或者用通配符统一管理一组子域名(证书申请需要 DNS 支持通配符验证):
*.wuee.cn {
# 按子域名分发到不同后端
@gs host gs.wuee.cn
handle @gs {
rewrite / /app/
reverse_proxy 127.0.0.1:8765
}
# ...
}
六、改配置后的热加载
Caddy 的杀手锏之一:修改 Caddyfile 后不用重启进程,reload 即可零中断生效(证书、路由全部热更新):
# 系统级 Caddy
sudo systemctl reload caddy
# 容器化 Caddy
docker exec maibot-caddy caddy reload --config /etc/caddy/Caddyfile
七、常见坑
- 证书申请失败:Let's Encrypt 需要 80 端口可达。防火墙只开了 443 没开 80,会报
acme: error presenting token。Caddy 靠 80 端口的 HTTP challenge 验证域名所有权。 - 反代目标地址写错:Caddy 在宿主机上就用
127.0.0.1:端口;在 Docker 网络里就写容器名(maim-bot-core:8001)。写错网络是通不了的。 - root * 忘了星号:
root /var/www/site不会限制到站点根,可能被路径穿越到上级目录,Caddy 也会报警告。*必须带。 - handle 块内不要重复 file_server:静态服务写在块外,handle 只写要分流的路径,避免同一请求被处理两次。