Atlantis
GitHub Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

3. Caddy使用手册

1. 前言

在过去几年里,caddy 取代了 nginx,成为了我的反向代理心头好。起因是当时给一个内部服务做反向代理时,nginx 跑满 CPU,定位下来发现 nginx 使用 OpenSSL 提供 TLS 加解密,而且 OpenSSL 在 TLS 1.3 中硬编码优先使用 AES-GCM,恰巧 nginx 宿主机中未提供 AES 指令集,软件实现的 AES 资源消耗过大导致 CPU 满载,要继续使用 nginx 的话只能修改 OpenSSL 来设置 ChaCha20-Poly1305 为首选算法。不过当时测试 caddy 时未出现 CPU 满载现象,于是拿它替换了 nginx 解决问题。后面研究 TLS 1.3 时,确定是 golang 内置 TLS 组件会检测 CPU 指令集来修改 TLS 1.3 使用的加密算法,AES 指令集缺失时,优先使用软件实现效率较高的 ChaCha20-Poly1305。

在使用 caddy 的这几年中,积累了一些实践,这里就将一些常用操作记录下来,以下配置以 caddy 2.8.4 版本为准。

2. 基础知识

2.1 部署

我习惯使用容器部署 caddy,开启宿主机网络模式,并添加 NET_ADMIN 权限,如下是一个运行 caddy 的 run.sh 文件:

#!/bin/bash

# 容器镜像
image="wbuntu/caddy:2.8.4"

# 容器名称
container="caddy"

# 检查容器是否存在
if [ "$(docker ps -a -q -f name=$container)" ]; then
    # 如果容器未处于停止状态,则停止容器
    if [ "$(docker ps -q -f name=$container)" ]; then
        docker stop $container
    fi
    # 删除容器
    docker rm $container
fi

# 启动容器: 容器名称-> 是否后台运行 -> 重启策略 -> 网络类型 -> 环境变量 -> 挂载项 -> 镜像名
docker run --name $container -d \
    --restart unless-stopped \
    --network host \
    --cap-add=NET_ADMIN \
    -v $PWD/Caddyfile:/etc/caddy/Caddyfile \
    -v $PWD/root:/usr/share/caddy \
    -v $PWD/log:/var/log \
    -v $PWD/config:/config \
    -v $PWD/data:/data \
    $image

这里有以下几点考虑:

  1. 使用宿主机网络,并启用 NET_ADMIN:避免 NAT 转换,caddy 默认占用 80 TCP、443 TCP、443 UDP 端口,其中 443 UDP 端口用于 HTTP/3,需要 NET_ADMIN 权限来修改系统的 UDP 缓存限制,如果不需要 HTTP/3 的话可以不添加这个权限
  2. 将当前目录下的文件和文件夹映射到容器内,主要有:
    1. Caddyfile:caddy 配置文件,直接映射到容器内的 /etc/caddy/Caddyfile 方便修改
    2. root:web 服务默认根目录,可以用来存储公共文件,执行这个命令拷贝容器内目录下的 index.html 到本地目录 docker run --rm -v $PWD/root:/host_root caddy:2.8.4 sh -c "cp /usr/share/caddy/index.html /host_root/index.html"
    3. log:日志目录
    4. config:JSON 格式的配置文件保存目录,Caddyfile 验证有效后,caddy 才会转换配置再保存到该目录下
    5. data:数据存储,存放实例 ID、证书等文件,建议持久化,避免 caddy 重启时反复申请证书

另一个 reload.sh 脚本可用于格式化并重载配置文件:

#!/bin/bash

# 容器名称
container="caddy"

# 格式化命令
fmt="caddy fmt --overwrite /etc/caddy/Caddyfile"
# 重载命令
reload="caddy reload --config /etc/caddy/Caddyfile --force"

# 执行命令
docker exec $container $fmt
docker exec $container $reload

2.2 配置文件

caddy 使用名为 Caddyfile 的配置文件,它采用简洁的声明式语法,熟悉 nginx 的人应该不会感到陌生。

alt text

我常用的配置文件如下:

{
    servers :443 {
        protocols h1 h2
    }
 }

test.wbuntu.com {
    tls /etc/cert/wbuntu.crt /etc/cert/wbuntu.key
    log {
        output file /var/log/test.log
    }
    root * /usr/share/caddy
    file_server
}

首先在全局配置中定义 443 端口的服务只使用 HTTP/1.1 和 HTTP/2 协议(因为 HTTP/3 使用 UDP 协议,在国内访问不太稳定,而且会占用宿主机 443 UDP 端口),然后定义了一个站点 test.wbuntu.com,使用自己提供的 HTTPS 证书和密钥,站点日志输出到 /var/log/test.log,web 服务目录使用 /usr/share/caddy。我常用这样的配置来创建一个用于中转文件的静态文件服务器。

完整的配置参数较多,参考文档如下:

  1. 全局配置参数:Global options
  2. 站点配置:Caddyfile Directives
  3. 需要匹配请求做特殊处理时:Request Matchers
  4. 常用的 Caddyfile 模板:Common Caddyfile Patterns

2.3 编译

caddy 一直提倡模块化,本体代码和第三方插件都是模块化实现的,我们可以使用容器编译 caddy,添加自己需要的模块,例如下面基于 caddy 2.8.4 版本添加支持 WebDAV 的模块:

ARG TARGETARCH
ARG VERSION

FROM --platform=$BUILDPLATFORM caddy:${VERSION}-builder AS builder
ENV CGO_ENABLED=0
ENV GOOS=linux
ENV GOARCH=${TARGETARCH}
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} xcaddy build ${VERSION} \
--with github.com/mholt/caddy-webdav

FROM --platform=$TARGETPLATFORM caddy:${VERSION}
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

使用 buildx 构建双架构镜像:

docker buildx build --platform linux/amd64,linux/arm64 --build-arg VERSION=2.8.4 -t wbuntu/caddy:2.8.4 .

运行容器可以检查版本与模块:

➜  ~ docker run --rm wbuntu/caddy:2.8.4 caddy --version
v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk=
➜  ~ docker run --rm wbuntu/caddy:2.8.4 caddy list-modules | grep dav
http.handlers.webdav

3. 常用功能

3.1 自动证书管理

记得在 caddy 最初做推广的时候,自动证书管理就是一个亮点功能,假如我们已经将域名解析到服务器 IP,然后使用以下配置就能自动触发证书签发:

test.wbuntu.com {
    log {
        output file /var/log/test.log
    }
    root * /usr/share/caddy
    file_server
}

签发的证书默认单域名,有效期 90 天,容易踩坑的点是:先启动 caddy,然后再去做域名解析(或触发其他导致无法解析域名的问题),当 caddy 多次请求证书机构签发证书失败后,服务器会被证书签发机构限制请求。

建议手动申请通配符证书:CNCF笔记 / 使用K3s部署容器化应用 / 2. 基础配置 / 2.1 通配符证书

3.2 静态文件服务器

参考文档:

  1. file_server
  2. basic_auth

当我们需要一个简单的静态文件服务器时,使用以下配置即可:

test.wbuntu.com {
    log {
        output file /var/log/test.log
    }
    root * /usr/share/caddy
    file_server browse
}

file_server 指令增加 browse,允许列出文件,这样访问网页时会展示一个默认的文件浏览器:

alt text

为了安全起见,可以添加 basic_auth 要求使用账号密码访问:

test.wbuntu.com {
    log {
        output file /var/log/test.log
    }
    root * /usr/share/caddy
    basic_auth {
        username hashed_password
    }
    file_server browse
}

basic_auth 指令中可以设置多个用户,每一行的格式为 用户名 密码的哈希,假设用户名为 user,密码为 password,可以使用 caddy hash-password 生成密码哈希值:

➜  docker run --rm wbuntu/caddy:2.8.4 caddy hash-password -p password
$2a$14$3eAksJeOFGHPRK3JYri.G.v2Dofko0h3I/1b2U2RjPhc5THPBGYQ2

得到 password 字符串的哈希值 $2a$14$3eAksJeOFGHPRK3JYri.G.v2Dofko0h3I/1b2U2RjPhc5THPBGYQ2,完整配置如下:

test.wbuntu.com {
    log {
        output file /var/log/test.log
    }
    root * /usr/share/caddy
    basic_auth {
        username $2a$14$3eAksJeOFGHPRK3JYri.G.v2Dofko0h3I/1b2U2RjPhc5THPBGYQ2
    }
    file_server browse
}

重载配置后再访问网页就需要填写用户名和密码:

alt text

3.3 反向代理

参考文档:

  1. reverse_proxy

caddy 使用 reverse_proxy 指令提供反向代理功能,常用的配置如下:

test.wbuntu.com {
    log {
        output file /var/log/test.log
    }
    root * /usr/share/caddy
    file_server browse
    reverse_proxy /HTTPEndpoint http://localhost:8080
    reverse_proxy /HTTPEndpointWildcard/* http://localhost:8081
    reverse_proxy /HTTPSEndpoint https://localhost:8082
    reverse_proxy /HTTPSEndpointInsecure https://localhost:8083 {
        transport http {
            tls_insecure_skip_verify
        }
    }
    reverse_proxy /H2CEndpoint h2c://localhost:8084
}

reverse_proxy 指令可以按 Request Matchers 匹配请求再转发到指定后端,我常用 URL 中的 PATH 来匹配请求,上面的配置中包含了常用的 HTTP 后端、HTTPS 后端、忽略证书验证的 HTTPS 后端以及 H2C 后端。

我们还可以针对后端设置头部和 PATH 重写、健康检查策略、负载均衡策略等,将 caddy 制作成一个七层负载均衡器,详细内容可以翻阅参考文档。

3.4 WebDAV服务

参考文档:

  1. github.com/mholt/caddy-webdav
  2. github.com/juvenn/caddy-dav

WebDAV(Web Distributed Authoring and Versioning)是一种基于 HTTP 协议的扩展,让我们可以通过 HTTP 协议挂载网络存储来共享文件,我常用的 chatgpt-next-web 也支持通过 webdav 备份配置。

我们可以编译 github.com/mholt/caddy-webdav 模块来为 caddy 添加 WebDAV 支持,下面使用一个提前编译好的容器镜像:wbuntu/caddy:2.8.4,这里会创建一个 Caddyfile,同时启用 file_browser 与 webdav,支持文件管理器和浏览器访问网络存储,并使用 basic_auth 认证用户:

{
	order webdav before file_server
}

test.wbuntu.com {
	encode zstd gzip
	tls /etc/cert/wbuntu.crt /etc/cert/wbuntu.key
	log {
		output file /var/log/test.log
	}
	root * /webdav
	basic_auth {
		# username password
		username $2a$14$3eAksJeOFGHPRK3JYri.G.v2Dofko0h3I/1b2U2RjPhc5THPBGYQ2
	}
	@notget not method GET
	route @notget {
		webdav
	}
	file_server browse
}

在全局设置中,让 webdav 先于 file_server 生效,然后在站点设置中指定 /webdav 目录为根目录,接着添加用户名密码认证,然后使用 route 指令将所有非 GET 请求分流给 webdav 模块,最后通过 file_server 模块启用文件浏览。

配置完成后,在 macOS 上可以同时使用 Finder 和浏览器访问共享存储,效果如下:

alt text