
Caddy 简介 Caddy 是一个 Go 编写的 Web 服务器,类似于 Nginx,Caddy 提供了更加强大的功能,随着 v2 版本发布 Caddy 已经可以作为中小型站点 Web 服务器的另一个选择;相较于 Nginx 来说使用 Caddy 的优势如下:
就目前来说,Caddy 对于我个人印象唯一的缺点就是性能没有 Nginx 高,但是这是个仁者见仁智者见智的问题;相较于提供的这些便利性,在性能可接受的情况下完全有理由切换到 Caddy。 编译 Caddy2注意: 在 Caddy1 时代,Caddy 官方发布的预编译二进制文件是不允许进行商业使用的,Caddy2 以后已经全部切换到 Apache 2.0 License。 在默认情况下 Caddy2 官方提供了预编译的二进制文件,以及自定义 build 下载页面,不过对于需要集成一些第三方插件时,我们仍需采用官方提供的 xcaddy 来进行自行编译;以下为具体的编译过程: Golang 环境安装本部分编译环境默认为 Ubuntu 20.04 系统,同时使用 root 用户,其他环境请自行调整相关目录以及配置;编译时自行处理好科学上网相关配置,也可以直接用国外 VPS 服务器编译。 首先下载 go 语言的 SDK 压缩包,其他平台可以从 https://golang.org/dl/ 下载对应的压缩包: wget https://golang.org/dl/go1.15.6.linux-amd64.tar.gz下载完成后解压并配置相关变量: # 解压tar -zxvf go1.15.6.linux-amd64.tar.gz # 移动到任意目录 mkdir -p /opt/devtools mv go /opt/devtools/go" D) ^# ]* ]; I" ?, }" [0 E # 创建 go 相关目录; q5 k5 f% M' O& Q0 @ mkdir -p ${HOME}/gopath/{src,bin,pkg} + A0 A3 z. \* {+ v% z5 y! ]- N # 调整变量配置,将以下变量加入到 shell 初始化配置中& T2 v# A: l' H5 L2 _ # bash 用户请编辑 ~/.bashrc+ y$ S. ?- y9 G: f# [ # zsh 用户请编辑 ~/.zshrc export GOROOT='/opt/devtools/go'3 w+ R# f4 k5 ~ O. Y w export GOPATH="${HOME}/gopath"/ t6 i& w0 D' k4 d, D8 b export GOPROXY='https://goproxy.cn' # 如果已经解决了科学上网问题,GOPROXY 变量可以删除,否则可能会起反作用 x |* D _6 \7 O. R& H export PATH="${GOROOT}/bin ![]() ![]() 7 \- X0 |0 A$ B' q. o* P$ E6 n! g7 Q # 让配置生效* x8 D; q$ W: U0 ]3 } # bash 用户替换成 ~/.basrc # 重新退出登录也可以* q, o/ `8 u, B- ?$ n9 X/ ` source ~/.zshrc! j% p/ p! {' G0 u" q4 Q, n 配置完成后,应该在命令行执行 go version 并有以下成功返回: bleem ➜ ~ go versiongo version go1.15.6 linux/amd641 M& i6 H8 ?* h8 v9 B% y- Y 安装 xcaddy 按照官方文档直接命令行执行 go get -u github.com/caddyserver/xcaddy/cmd/xcaddy 安装即可: bleem ➜ ~ go get -u github.com/caddyserver/xcaddy/cmd/xcaddy2 J. x. H4 l5 o8 e; q* |) Tgo: downloading github.com/caddyserver/xcaddy v0.1.7 go: found github.com/caddyserver/xcaddy/cmd/xcaddy in github.com/caddyserver/xcaddy v0.1.7 go: downloading github.com/Masterminds/semver/v3 v3.1.0 go: github.com/Masterminds/semver/v3 upgrade => v3.1.1 go: downloading github.com/Masterminds/semver/v3 v3.1.1 ..... 安装完成后应当在命令行可以直接执行 xcaddy 命令: # xcaddy 并没有提供完善的命令行支持,所以 `--help` 报错很正常bleem ➜ ~ xcaddy --help go: cannot match "all": working directory is not part of a module5 ^! r) @5 P. o' @$ I" a 2021/01/07 12:15:56 [ERROR] exec [go list -m -f={{if .Replace}}{{.Path}} => {{.Replace}}{{end}} all]: exit status 1: 编译 Caddy2 编译之前系统需要安装 jq、curl、git 命令,没有的请使用 apt install -y curl git jq 命令安装; 自行编译的目的是增加第三方插件方便使用,其中官方列出的插件可以从 Download 页面获取到: ![]() 其他插件可以从 GitHub 上寻找或者自行编写,整理好这些插件列表以后只需要使用 xcaddy 编译即可: # 获取最新版本号,其实直接去 GitHub realse 页复制一下就行( a& X' C/ l8 _# 这里转化为脚本是为了方便自动化 F3 j' K3 x/ h v. _! F export version=$(curl -s "https://api.github.com/repos/caddyserver/caddy/releases/latest" | jq -r .tag_name) # 使用 xcaddy 编译 xcaddy build ${version} --output ./caddy_${version} \ --with github.com/abiosoft/caddy-exec \$ E$ ~6 [. `6 B --with github.com/caddy-dns/cloudflare \6 P4 N( S5 E9 e5 P$ n3 H$ L --with github.com/caddy-dns/dnspod \2 A1 _ v( L$ C' _* G. n( p/ o8 e7 j --with github.com/caddy-dns/duckdns \4 W+ ~6 Z; @( _" b --with github.com/caddy-dns/gandi \ --with github.com/caddy-dns/route53 \0 O) v: m3 ^8 h* L# I: ?2 ? --with github.com/greenpau/caddy-auth-jwt \ --with github.com/greenpau/caddy-auth-portal \3 y# o# |$ A1 L' O --with github.com/greenpau/caddy-trace \ --with github.com/hairyhenderson/caddy-teapot-module \ --with github.com/kirsch33/realip \ --with github.com/porech/caddy-maxmind-geolocation \ --with github.com/caddyserver/format-encoder \ --with github.com/mholt/caddy-webdav$ G9 h! i# j! l% T) s+ ? 编译过程日志如下所示,稍等片刻后将会生成编译好的二进制文件:
![]() 编译成功后可以通过 list-modules 子命令查看被添加的插件是否成功编译到了 caddy 中: bleem ➜ ~ ./caddy_v2.3.0 list-modulesadmin.api.load admin.api.metrics3 G( i/ L2 e# m: w caddy.adapters.caddyfile3 ?2 Y. D; A/ V5 ?( B caddy.listeners.tls caddy.logging.encoders.console" T$ }+ D4 N+ J4 r' N7 @2 o caddy.logging.encoders.filter caddy.logging.encoders.filter.delete caddy.logging.encoders.filter.ip_mask$ v* X4 u; U5 z n! q4 t caddy.logging.encoders.formatted caddy.logging.encoders.json caddy.logging.encoders.logfmt caddy.logging.encoders.single_field caddy.logging.writers.discard% b1 L% t) G1 @8 ]! ~8 a caddy.logging.writers.file/ H5 ~* a% B$ I! ~) h caddy.logging.writers.net7 v3 J. S3 E/ `. e caddy.logging.writers.stderr" X* f( n9 U% a! a caddy.logging.writers.stdout caddy.storage.file_system dns.providers.cloudflare dns.providers.dnspod& z: p* ]( ]# X- I dns.providers.duckdns# i. U- R' ^& H dns.providers.gandi; @0 `; Z& j! ~% \ dns.providers.route53 exec http! T) n% L: J" J http.authentication.hashes.bcrypt http.authentication.hashes.scrypt$ g9 u1 C( Z) c) [! l/ y http.authentication.providers.http_basic http.authentication.providers.jwt ......6 x. |, ?! m7 m" _( L+ Y 安装 Caddy2宿主机安装 宿主机安装 Caddy2 需要使用 systemd 进行守护,幸运的是 Caddy2 官方提供了各种平台的安装包以及 systemd 配置文件仓库;目前推荐的方式是直接采用包管理器安装标准版本的 Caddy2,然后替换自编译的可执行文件: # 安装标准版本 Caddy2sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https; p! l0 Q, L. n5 f0 f6 r curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/cfg/gpg/gpg.155B6D79CA56EA34.key' | sudo apt-key add -8 S) j0 R# v3 P$ l curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/cfg/setup/config.deb.txt?distro=debian&version=any-version' | sudo tee -a /etc/apt/sources.list.d/caddy-stable.list sudo apt update) g* O2 z! r* A% ?6 g sudo apt install caddy6 ]. ^( A2 T7 i9 E) L # 替换二进制文件 systemctl stop caddy rm -f /usr/bin/caddy mv ./caddy_v2.3.0 /usr/bin/caddy, p7 z8 E. w4 ^8 J1 J3 @! ^% d$ E Docker 安装 Docker 用户可以通过 Dockerfile 自行编译 image,目前我编写了一个基于 xcaddy 的 Dockerfile,如果有其他插件需要集成自行修改重新编译即可;当前 Dockerfile 预编译的镜像已经推送到了 Docker Hub 中,镜像名称为 mritd/caddy。 配置 Caddy2Caddy2 的配置文件核心采用 json,但是 json 可读性不强,所以官方维护了一个转换器,抽象出称之为 Caddyfile 的新配置格式;关于 Caddyfile 的完整语法请查看官方文档 https://caddyserver.com/docs/caddyfile,本文仅做一些基本使用的样例。 配置片段Caddyfile 支持类似代码中 function 一样的配置片段,这些配置片段可以在任意位置被 import,同时可以接受参数,以下为配置片断示例: # 括号内为片段名称,可以自行定义0 L8 @$ _$ x$ b$ d7 [/ m(TLS) {4 I. k2 d6 B; U( y% C% l7 n; [7 W protocols tls1.2 tls1.3 ciphers TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 }: i0 ]4 K, {; b ' {4 B: Q* U5 }: x5 I # 在任意位置可以引用此片段从而达到配置复用( B2 S( @) f7 l" @ import TLS 配置模块化 import 指令除了支持引用配置片段以外,还支持引用外部文件,同时支持通配符,有了这个命令以后我们就可以方便的将配置文件进行模块化处理: # 引用外部的 /etc/caddy/*.caddy3 c8 L! n9 K) J4 d1 s# Oimport /etc/caddy/*.caddy; ~! X( h# {, u. z; I3 a/ n4 ` 站点配置 针对于站点域名配置,Caddyfile 比较自由化,其格式如下: 地址 {0 a' [" Y- I8 Q4 D) S站点配置 }+ G. N! n3 Z ]1 e 关于这个 “地址” 接受多种格式,以下都为合法的地址格式: localhost$ ^+ v7 Z. ~5 d: T# a0 {example.com* ]+ s: [3 y+ `+ v5 U :443, t" t% g, } T5 X5 p http://example.com. C$ [8 E( {$ ]$ K3 M `- ^ localhost:8080( m" _& _1 k% S4 f% x 127.0.0.1 [::1]:2015 example.com/foo/* *.example.com& q/ h" c7 K) y$ U9 ^4 m6 \ http://- X: j% O& C+ G& O; p! n4 l, j 环境变量 Caddyfile 支持直接引用系统环境变量,通过此功能可以将一些敏感信息从配置文件中剔除: # 引用环境变量 GANDI_API_TOKENdns gandi {$GANDI_API_TOKEN}+ U( v$ y3 a% k4 Y 配置片段参数支持 针对于配置片段,Caddyfile 还支持类似于函数代码的参数支持,通过参数支持可以让外部引用时动态修改配置信息: (LOG) {% C" r" {! Z- C, ]+ D: G7 o3 Blog {" L5 |; ~! z) J" I4 A& }3 B' y format json {, ?3 `5 K% X4 g5 {2 F, Z. x time_format "iso8601"9 v n& |% ~5 }& Q2 M9 B: l } # "{args.0}" 引用传入的第一个参数,此处用于动态传入日志文件名称 output file "{args.0}" {% ]5 W) z1 x0 t9 N+ z* a roll_size 100mb, q& b* q m3 R, m roll_keep 3* w3 [/ m! Z0 I% B M1 o1 c( K roll_keep_for 7d }! Y: b* |4 F% ~2 Z5 p }; @* j7 B; N/ a- H- }; F } # 引用片段+ a+ k$ H9 G C* x) T import LOG "/data/logs/mritd.com.log"4 N0 Y( {+ E& n8 T+ q' |5 V 自动证书申请 在启动 Caddy2 之前,如果目标域名(例如: www.example.com)已经解析到了本机,那么 Caddy2 启动后会尝试自动通过 ACME HTTP 挑战申请证书;如果期望使用 DNS 的方式申请证书则需要其他 DNS 插件支持,比如上面编译的 --with github.com/caddy-dns/gandi 为 gandi 服务商的 DNS 插件;关于使用 DNS 挑战的配置编写方式需要具体去看其插件文档,目前 gandi 的配置如下: tls {dns gandi {env.GANDI_API_TOKEN}4 J0 S) e/ F, M: E5 C7 O+ U! V( R! b } 配置完成后 Caddy2 会通过 ACME DNS 挑战申请证书,值得注意的是即使通过 DNS 申请证书默认也不会申请泛域名证书,如果想要调整这种细节配置请使用 json 配置或管理 API。 完整模块化配置样例了解了以上基础配置信息,我们就可以实际编写一个站点配置了;以下为本站的 Caddy 配置样例: 目录结构: caddy├── Caddyfile ├── mritd.com.caddy └── mritd.me.caddy& x5 j2 n; O9 g Caddyfile Caddyfile 主要包含一些通用的配置,并将其抽到配置片段中,类似于 nginx 的 nginx.conf 主配置;在最后部分通过 import 关键字引入其他具体站点配置,类似 nginx 的 vhost 配置。 (LOG) {1 M4 @. g# z: m9 j0 `log { # 日志格式参考 https://github.com/caddyserver/format-encoder 插件文档 format formatted "[{ts}] {request>remote_addr} {request>proto} {request>method} <- {status} -> {request>host} {request>uri} {request>headers>User-Agent>[0]}" { time_format "iso8601"3 _% O" E5 e9 F4 X9 V8 o }& w) I& B; i7 p4 C/ [1 B5 ~& j output file "{args.0}" {& G* q1 n4 J Y, R8 j+ L$ m, L roll_size 100mb4 `% L, t4 A8 B% U* \ roll_keep 3) s6 l( J' n7 R" w. Q( ~$ Z# b roll_keep_for 7d* v; Z/ t% G) z6 k6 S7 g* ] }$ I% P1 g0 K" @6 \! p1 ^ } }+ ~# E# A1 u, [. i2 T2 ] / E, D5 ~( c4 j- [ (TLS) {! E& f% j, c1 c # TLS 配置采用 https://mozilla.github.io/server-side-tls/ssl-config-generator/ 生成,SSL Labs 评分 A+0 U: q3 c* K6 c9 {5 Z8 F# B protocols tls1.2 tls1.3 ciphers TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256- Z w$ s+ l9 ]9 P/ ^ } (HSTS) {: p# a9 W* M0 g: F # HSTS (63072000 seconds): @7 a9 t, D9 x/ w9 W n2 p header / Strict-Transport-Security "max-age=63072000"& w* V' a2 o& j7 ^7 V } * n% i. ~: k7 { (ACME_GANDI) { # 从环境变量获取 GANDI_API_TOKEN dns gandi {$GANDI_API_TOKEN}9 q5 J3 M6 W! f/ `9 q } # 聚合上面的配置片段为新的片段5 J/ J8 P1 ^& ~$ ` (COMMON_CONFIG) {+ }0 j( A7 H3 u5 Z8 \# W% q" @ # 压缩支持9 Y, m+ s: l% l6 R encode zstd gzip , ^# o% r3 u; u" w4 H # TLS 配置2 g' T( i' ?' K1 Y w4 g$ ? tls {3 R( R" {6 ^$ E7 _! U9 x import TLS' J2 I7 h' s* n, c1 X import ACME_GANDI }- q" X ]/ O& d" k( M4 ~! ?; g 2 e5 }- g4 O8 x6 }+ f% q$ ?$ o # HSTS- M O( D5 a$ }" w! L1 J9 C import HSTS3 y5 K% Q) a, [2 B } # 开启 HTTP3 实验性支持( J+ v3 M/ X7 R, s' @0 c! a {! k3 s3 G4 R! r$ p Z servers :443 { protocol { experimental_http3/ W% K7 j' ]: H! O8 ~8 n! K7 a } }6 X F! L) i/ r7 y } # 引入其他具体的站点配置 import /etc/caddy/*.caddy7 w8 k8 b1 B7 X2 e, T; M% S0 ? mritd.com.caddy mritd.com.caddy 为主站点配置,主站点配置内主要编写一些路由规则,TLS 等都从配置片段引入,这样可以保持统一。 www.mritd.com {# 重定向到 mritd.com(默认 302)+ c( C. L& o! x2 F+ X redir https://mritd.com{uri} # 日志* s! S& {' `( D8 Q* E7 \1 a import LOG "/data/logs/mritd.com.log" # TLS、HSTS、ACME 等通用配置: Z' `, a$ C: N; i* _+ t import COMMON_CONFIG0 l f8 @ y6 S s6 [1 g$ v } 0 d" V( }2 R+ f mritd.com { # 路由 route /* { reverse_proxy mritd_com:80 }! q6 P' }' m3 }0 {. U$ v5 E # 日志3 O# ~; ~0 B$ _4 w: y( { import LOG "/data/logs/mritd.com.log"( L7 o# i# r0 R0 h; n! i4 i$ E # TLS、HSTS、ACME 等通用配置* Y) Z7 j# H7 a' q import COMMON_CONFIG }1 C1 x8 |& R. c# z mritd.me.caddy mritd.me.caddy 为老站点配置,目前主要将其 301 到新站点即可。 www.mritd.me {# 重定向到 mritd.com # 最后的 "code" 支持三种参数3 H- T0 G* l4 h/ U # temporary => 302; V3 a+ u. f' t3 f6 i& e: c # permanent => 301 # html => HTML document redirect redir https://mritd.com{uri} permanent$ o2 z0 k6 U' ?- e* `+ y # 日志3 R9 P8 q" \2 N3 r7 B import LOG "/data/logs/mritd.com.log" # TLS、HSTS、ACME 等通用配置 import COMMON_CONFIG } mritd.me {* G/ @1 U, x2 S5 a # 重定向 redir https://mritd.com{uri} permanent$ O8 R* s! c4 G+ I' w* {/ j # 日志0 L: A" W+ J: h' T4 y import LOG "/data/logs/mritd.com.log" `+ D! `6 B* u3 }2 }" b# [ # TLS、HSTS、ACME 等通用配置 import COMMON_CONFIG7 K& W% F6 K9 \! |8 A }: F9 b) [& x/ j1 c, J7 u 启动与重载 配置文件编写完成后,通过 systemctl start caddy 可启动 caddy 服务器;每次配置修改后可以通过 systemctl reload caddy 进行配置重载,重载期间 caddy 不会重启(实际上调用 caddy reload 命令),当配置文件书写错误时,重载只会失败,不会影响正在运行的 caddy 服务器。 总结本文只是列举了一些简单的 Caddy 使用样例,在强大的插件配合下,Caddy 可以实现各种 “神奇” 的功能,这些功能依赖于复杂的 Caddy 配置,Caddy 配置需要仔细阅读官方文档,关于 Caddyfile 的每个配置段在文档中都有详细的描述。 值得一提的是 Caddy 本身内置了丰富的插件,例如内置 “file_server”、内置各种负载均衡策略等,这些插件组合在一起可以实现一些复杂的功能;Caddy 是采用 go 编写的,官方也给出了详细的开发文档,相较于 Nginx 来说通过 Lua 或者 C 来开发编写插件来说,Caddy 的插件开发上手要容易得多;Caddy 本身针对数据存储、动态后端、配置文件转换等都内置了扩展接口,这为有特定需求的扩展开发打下了良好基础。 最终总结,综合来看目前 Caddy2 的性能损失可接受的情况下,相较于 Nginx 绝对是个绝佳选择,各种新功能都能够满足现代化 Web 站点的需求,真香警告。 |