Caddy:自动 HTTPS 的 Go Web 服务器与反向代理
它是什么
Caddy 是一个开源、以 Go 编写的 HTTP Web 服务器。它以简洁的 Caddyfile 为常用配置入口,并以自动申请和续期 TLS 证书、HTTP/2、静态文件服务、反向代理及模块扩展为主要特点。它既可作为静态站点入口,也可为后端服务提供反向代理、负载均衡、压缩和基础认证等能力。其 Go 运行时基础与 Go GMP 调度模型有关,但本文不展开 Caddy 的运行时性能分析。
在本知识库中,Caddy 最直接的实践关联是 FRP:可由 Caddy 在 HTTPS/WSS 入口终止 TLS 并转发到 frps,但这并不会把 UDP 业务流量转换为 HTTPS。
版本边界:原始笔记使用
caddy:2.5.2与caddy:2.6.2。以下配置用于保留其思路和字段;Caddy 指令、模块和镜像标签会随版本演进,投入生产前必须对目标版本执行caddy fmt和caddy validate。
Docker Compose 快速开始
下面的 Compose 以 Caddyfile 启动 Caddy,并持久化 /data。端口 80/udp 与 443/udp 用于支持 QUIC/HTTP/3;2019 为 Admin API,限定在宿主机 loopback,不能直接暴露到公网。
version: "3.5"
services:
caddy-server:
image: caddy:2.5.2
restart: always
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./data:/data
ports:
- "80:80"
- "80:80/udp"
- "443:443"
- "443:443/udp"
- "127.0.0.1:2019:2019"
command: ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]为避免配置文件和运行时状态因容器重建丢失,实际部署通常还应按镜像文档挂载 /config;证书申请也要求域名解析、入站端口和外部网络条件正确。
Caddyfile:常见路由模式
下列片段保留原始笔记的主要用法:动态响应、静态文件、JSON、反向代理、IP 白名单、按路径分流、WebSocket、自有证书与基础认证。
# 返回访问者 IP
ip.test.work {
templates
header Content-Type text/plain
respond "{{.RemoteIP}}"
encode zstd gzip
}
# 静态文件
file.test.work {
file_server {
root /www/html
}
}
# 返回 JSON 文本
test.test.com {
templates
header Content-Type application/json
respond {"name":"bryan","male":"yes","age":45}
}
# 反向代理
vault.roky.work {
reverse_proxy vault:80
}
# HTTPS 上游与 IP 白名单
test.domain.com {
@ip_whitelist {
remote_ip 6.6.6.6
}
route @ip_whitelist {
reverse_proxy 1.2.3.4:6666 {
transport http {
tls
tls_insecure_skip_verify
}
}
}
}
# 使用 handle,实现类似 Nginx location 的路径分流
http://127.0.0.1:7892 {
@api {
path /api /ping
method GET
}
handle @api {
route {
reverse_proxy host.docker.internal:1234
}
}
handle {
respond "not found" 404
}
}
# WebSocket 代理
ws.roky.work {
@websockets {
header Connection Upgrade
header Upgrade websocket
}
reverse_proxy @websockets 127.0.0.1:7000
}
# 使用自有证书
test.roky.work {
tls test.roky.work.crt test.roky.work.key
reverse_proxy vault:80
}
# 基础认证(指令名和语法应以运行版本为准)
basicauth /secret/* {
Bob $2a$14$UPT8R.QFnkMA6fRYetI.LeqMu.SyKpEcItP8pJdeM7rLQniefCDLG
}这里有三项不能忽略的边界:
tls_insecure_skip_verify会跳过对 HTTPS 上游证书的校验,只应作为临时排障手段;生产环境应改用受信任证书或配置正确的信任链。remote_ip是入口侧的网络限制,不替代应用认证、最小权限和审计;代理后若经过 CDN、负载均衡器或其他反代,还需按部署链路处理真实客户端 IP。- WebSocket 在 Caddy 中通常可由
reverse_proxy自动处理 Upgrade;显式 matcher 可保留为表达意图的写法,但要以实际版本验证。
配置生命周期:格式化、校验与热加载
每次提交 Caddyfile 前先格式化和验证,再执行 reload。下面命令假定容器名为 caddy-server。
# 格式化配置文件
docker exec -ti caddy-server caddy fmt --overwrite /etc/caddy/Caddyfile
# 验证配置文件有效性
docker exec -ti caddy-server caddy validate --config /etc/caddy/Caddyfile
# reload 配置
docker exec -ti caddy-server caddy reload --adapter caddyfile --config /etc/caddy/Caddyfile
# 创建基础认证密码哈希
docker exec -ti caddy-server caddy hash-passwordvalidate 成功只说明配置可被当前二进制解析,不等价于域名、证书、上游连通性、访问控制或实际请求路径均已正确;热加载后应使用预期域名、状态码和日志做回读验证。
Metrics 与 Prometheus
原始笔记通过全局选项启用 metrics:
{
servers {
metrics
}
}并给出一个用 EndpointSlice、Service 与 ServiceMonitor 让 Kubernetes Prometheus 抓取 2019/TCP 的示例:
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: caddy-service-1
namespace: kubesphere-monitoring-system
labels:
kubernetes.io/service-name: caddy-service
addressType: IPv4
ports:
- name: ''
appProtocol: http
protocol: TCP
port: 2019
endpoints:
- addresses:
- "10.1.2.3"
---
apiVersion: v1
kind: Service
metadata:
name: caddy-service
namespace: kubesphere-monitoring-system
labels:
app: caddy-service
spec:
ports:
- protocol: TCP
port: 2019
targetPort: 2019
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: caddy-servicemonitor
namespace: kubesphere-monitoring-system
spec:
selector:
matchLabels:
app: caddy-service
jobLabel: app
endpoints:
- targetPort: 2019
interval: 10s由于 2019 同时承载 Admin API,不能把上述 Service 当作无认证的公网 metrics 服务。只有在 Admin 监听地址、NetworkPolicy、防火墙与 Prometheus 到目标的网络路径都被显式限制时,才可让监控网络访问它;否则应保持 loopback 绑定。该示例中的 EndpointSlice IP、命名空间和端口须替换为实际集群值。
Admin API 与 JSON 配置
Admin API 可加载、停止或读取活动配置,并按路径增删改配置对象。常用接口包括:
| 接口 | 用途 |
|---|---|
POST /load | 替换活动配置。 |
POST /stop | 停止活动配置并退出进程。 |
GET /config/[path] | 导出指定路径的配置。 |
POST / PUT / PATCH / DELETE /config/[path] | 追加、创建、替换或删除配置对象或数组元素。 |
POST /adapt | 将配置适配为 JSON,但不运行。 |
GET /pki/ca/、GET /pki/ca//certificates | 查询 CA 与证书链信息。 |
GET /reverse_proxy/upstreams | 查询上游当前状态。 |
可通过 @id 给 JSON 路由或 handler 标记稳定标识,以便对指定配置节点操作。下面 JSON 会为 localhost 定义一个返回 Hello, world! 的静态响应:
{
"apps": {
"http": {
"servers": {
"srv0": {
"routes": [{
"@id": "route0",
"match": [{"host": ["localhost"]}],
"handle": [{
"handler": "subroute",
"routes": [{"handle": [{
"@id": "handler0",
"handler": "static_response",
"body": "Hello, world!"
}]}]
}]
}]
}
}
}
}
}完整 JSON 结构应以 Caddy JSON 文档 为准。并发自动化修改时应遵循 API 对并发配置变更的约束:先读取当前配置、指定明确路径或 @id,并在变更后回读验证,避免多个控制器互相覆盖。
自定义构建与插件
当官方镜像未包含需要的模块时,可用 xcaddy 在 builder 阶段构建二进制,再复制到运行镜像。原始笔记的示例如下:
FROM caddy:2.6.2-builder AS builder
RUN xcaddy build \
--with github.com/caddyserver/nginx-adapter \
--with github.com/hairyhenderson/caddy-teapot-module@v0.0.3-0
FROM caddy:2.6.2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy自定义构建应同时固定 Caddy 版本、每个模块版本及镜像摘要,并在 CI 中运行配置校验和镜像安全扫描。插件引入会扩展可执行代码与配置面,不能只因 Caddyfile 能通过解析就视作安全可用。
实践清单
- 把 Caddyfile、证书/数据目录和镜像版本纳入版本与备份策略;不要将私钥或密码哈希以可读形式提交到仓库。
- 使用
fmt → validate → reload → 请求与日志回读的闭环发布配置。 - 不对公网开放 Admin API;监控抓取应采用最小网络暴露面。
- 移除
tls_insecure_skip_verify,并为反向代理上游配置可验证的证书与名称。 - 在 FRP 等组合部署中,区分 Caddy 的 HTTPS/WSS 入口与业务协议实际监听的端口。
来源与范围
本文以用户提供的单份配置笔记为基础,保留其主要配置、命令、监控清单、Admin API 和自定义构建示例。文中没有对这些旧镜像版本进行实时兼容性验证,因此涉及指令语法、模块或 API 行为时应以目标版本的官方文档和本地 caddy validate 结果为准。