从 Nginx 换到 Caddy

从接触服务器开始,我就一直学习并使用着 Nginx。

Nginx 性能强大也足够好用,但证书要自己解决。

之前也搞过 自动化 SSL 证书,已经可以完全甩手了。

不过我还是觉得更换到 Caddy,因为——

折腾果然是很有意思的嘛!

去年抽了个时间,把主要用的这台服务器上所有配置从 Nginx 迁移到了 Caddy 上,在此做一点记录。

2025.03.11 更新:补充图片优化部分。

安装

安装方法就不赘述,文档 简单且详尽。

但默认安装的 Caddy 是不支持 Cloudflare API 申请泛域名证书的,于是我又去 Download 页面选择 Linux amd64 版本并选中 caddy-dns/cloudflare 插件,将下载下来的二进制文件替换已经安装的 Caddy,重启服务后,现在的 Caddy 就可以支持了。

这样虽然折腾点,但不用配置权限、systemd 等一堆麻烦的东西,我个人觉得是更省事的办法。

使用

Caddyfile

因为 Nginx 已经有自己的使用习惯,在更换到 Caddy 后我想尽可能保持相同的使用习惯,那就一点规划。

那么清空默认的 Caddy 配置文件,写一份新的 Caddyfile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
admin localhost:2019
email <example@mail.com>
}

import /srv/caddy/*.caddy0

*.neko7ina.com {
import /srv/caddy/tls.conf
import /srv/caddy/*.caddy

log {
output file /var/log/caddy/access.log {
roll_size 10mb
roll_keep 5
roll_keep_for 48h
}
format json
}
}

我用 Nginx 习惯把每个服务的配置文件都拆开各写各的,再用 Include 全部引用。

Caddy 有个类似的 import,这里使用了两个引用。

一个引用非泛域名的配置,类似直接 IP 或者裸域;另一个为其他二级域名的配置。

tls.conf 是使用 caddy-dns/cloudflare 的配置段,直接写 Caddyfile 里也行。

caddy0 配置

IP 配置参考

IP 的配置很简单,直接写好 IP 加端口就可以了,比如下面一个工作日接口的配置:

1
2
3
110.40.192.82:80 {
reverse_proxy localhost:3007
}

裸域配置参考

裸域的配置也很简单,比如下面博客本体的配置:

1
2
3
4
5
6
7
8
9
10
11
neko7ina.com {
import /srv/caddy/tls.conf
root * /home/git/hexo
file_server
encode gzip

handle_errors {
rewrite * /404.html
file_server
}
}

非标端口配置参考

有一些服务不想给别人看见,就会使用非标准端口,比如 Frp Dashboard 的服务:

1
2
3
4
5
frp-example.neko7ina.com:79 {
import /srv/caddy/tls.conf
reverse_proxy localhost:79
encode gzip
}

caddy 配置

这里是重头戏了,因为引用 caddy 文件实际上是被包在泛域名的配置段里,所以配置就不能像 caddy0 文件那么写。

我们需要先声明一个变量为哪个二级域名,然后再对这个变量进行配置,比如下面服务器流量查看服务的配置:

1
2
3
4
5
6
@vnstat host vnstat.neko7ina.com
handle @vnstat {
root * /srv/www/vnstat
file_server
encode gzip
}

这里先用 @vnstat 声明了二级域名 vnstat.neko7ina.com,然后再使用 handle @vnstat 来进行配置。

图片优化

2025.03.11 更新:补充图片优化部分。

服务器是小水管,有的地方图片有多,为了保证加载速度想尽了办法。

之前在 Lazyload 上做了点优化,现在对图片本身再动点手脚。

简单来说,就是判断客户端请求头是否支持 webp 与 avif 格式图片,优先返回体积更小的图片来达到加快加载速度的目的。

如果客户端不支持也可以返回原本的图片,保证了兼容性。

比如我的图片都是用一个二级域名来管理,那么配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@image host image.neko7ina.com
handle @image {
root * /home/git/hexo/images
encode zstd gzip
header Cache-Control "public, max-age=31536000"

@procPath {
path_regexp proc (?i).*_proc.*
}
handle @procPath {
@acceptsWebp_proc {
header Accept *image/webp*
path_regexp webp_proc (?i)^(.+)\.(jpg|jpeg|png)$
}
handle @acceptsWebp_proc {
@hasWebp_proc file {re.webp_proc.1}.webp
rewrite @hasWebp_proc {re.webp_proc.1}.webp
}

@acceptsAVIF_proc {
header Accept *image/avif*
path_regexp avif_proc (?i)^(.+)\.(jpg|jpeg|png)$
}
handle @acceptsAVIF_proc {
@hasAVIF_proc file {re.avif_proc.1}.avif
rewrite @hasAVIF_proc {re.avif_proc.1}.avif
}

file_server
}

@acceptsAVIF {
header Accept *image/avif*
path_regexp avif (?i)^(.+)\.(jpg|jpeg|png)$
}
handle @acceptsAVIF {
@hasAVIF file {re.avif.1}.avif
rewrite @hasAVIF {re.avif.1}.avif
}

@acceptsWebp {
header Accept *image/webp*
path_regexp webp (?i)^(.+)\.(jpg|jpeg|png)$
}
handle @acceptsWebp {
@hasWebp file {re.webp.1}.webp
rewrite @hasWebp {re.webp.1}.webp
}

file_server
}

配置参考了这篇 文字 作为参考,逻辑是判断请求头中是否支持 avif,如果是就返回图片同名的 avif 文件,否则再判断请求头中是否支持 webp,如果是就返回图片同名的 webp 文件,都不支持则返回源文件。

Lazyload 会有个占位图,我发现在图片体积很小的情况下(通常是几百 KB),webp 的表现会比 avif 更好,于是在基本规则前又单独加了规则判断路径中带 _proc. 的请求优先处理,按照 webp > avif > 源文件的优先级排列。

便利性配置

上文也说到,尽可能保证和 Nginx 使用时一样的习惯。

在使用 Nginx 时我会频繁使用到 nginx -tnginx -s reload 命令,为了在 Caddy 上也能使用类似的指令,还需要在 Shell 的配置文件里加点配置。

1
vim ~/.zshrc
1
2
3
4
5
6
7
8
9
10
# Caddy to Nginx
function caddy() {
if [[ "$1" == "-t" ]]; then
command caddy validate --config /etc/caddy/Caddyfile "${@:2}"
elif [[ "$1" == "-s" && "$2" == "reload" ]]; then
command caddy reload --config /etc/caddy/Caddyfile "${@:3}"
else
command caddy "$@"
fi
}

这样使用 caddy -tcaddy -s reload 也可以实现类似的效果了。

注意,Caddy 的重载配置是通过 admin API 来实现的,如果你在配置文件中关闭了 admin 那将无法使用。

结语

虽然这篇文字不多,但在迁移时总会遇到各种各样的问题,实际上花的时间并不少。

还好,我对 Caddy 的表现非常满意。

参考