Caddy 优缺点

最近了解了一下 Caddy,准备从 Nginx 转到 Caddy。本文指的 Caddy 均为 Caddy 2。

Caddy 官方文档
Caddy 中文文档

Caddy 的优点有:

  • 自动申请 TLS 证书(一大卖点!)
  • 语法简洁

缺点有:

  • 插件较 Nginx 少
  • 文档不多,而且网上的讨论也不多

安装 Caddy

参考链接:下载 | 安装

由于 yum 自带的是 Caddy 1,而我按官网从 yum 下载 Caddy 的方法貌似会报错,因选择了手动安装。

安装好以后,编写一个简单的 Caddyfile 用于测试:

1
2
mkdir /etc/caddy
vim /etc/caddy/Caddyfile

输入如下内容,然后保存:

1
2
3
:2015

Hello world!

启用 Caddy 服务并启动,然后查看其状态:

1
2
3
4
5
6
7
8
9
# 启用
sudo systemctl daemon-reload
sudo systemctl enable caddy

# 启动
sudo systemctl start caddy

# 查看状态
systemctl status caddy

如果是 active (running),则安装成功!如果是 failed,请检查 Caddyfile 的位置是否正确(按官方的配置,应该是 /etc/systemd/system/caddy.service)。

验证一下网站服务,curl 获取网站内容:

1
curl localhost:2015

如果返回 Hello world! 即正确。

以后,如果修改了 Caddyfile,使用 systemctl reload caddy 即可使其重新读取配置文件。

运行 Caddy

Caddy 可以由用户运行,也可以由 caddy 用户以 systemctl 的形式在后台运行。

由于 systemctl 运行出错时的提示很少,推荐学习、测试的时候使用用户身份运行,测试完成以后使用 systemctl

以用户身份运行及停止:

1
2
caddy start
caddy stop

这两条命令会读取当前目录的 Caddyfile,所以记得提前切换到 /etc/caddy

以系统身份运行及停止:

1
2
systemctl start caddy
systemctl stop caddy

查看调试信息:

1
systemctl status caddy

两种方法的重新加载分别为:

1
2
caddy reload
systemctl reload caddy

Caddyfile 常见配置

在不进行额外设置的情况下,Caddy 都是 443 端口自动申请 HTTPS,80 端口重定向到 443 端口的。

常见配置,入门 Caddyfile 时也可以参考一下,对 Caddyfile 有个基本的认识。

Caddyfile 入门

官方教程

Hello World!

如果只打算定义一个网站,Caddyfile 的第一行是网址,后面的就是一个或多个指令 directive

1
2
3
localhost

respond "Hello, world!"

将上述文本保存在 /etc/caddy/Caddyfile,然后使用 systemctl reload caddy 重新读取后,就可以尝试用浏览器或 curl 打开该网站:

1
2
$ curl https://localhost
Hello, world!

定义多个网站

一个文件可以定义多个网站。但是需要将上述语法改为下面的等价语法:

1
2
3
localhost { # 大括号前必须有空格
respond "Hello, world!"
}

就可以在多个语法块中定义每个网站了。

import 其他配置文件

参考

也可以在多个文件中定义配置。如在 a.txt 写入以下内容:

1
2
3
a.com {
respond "Hello, world!"
}

b.txt 写入以下内容:

1
2
3
b.com {
respond "Nice to meet you!"
}

然后在 Caddyfile 中写入:

1
2
3
# 以 Caddyfile 形式导入两个 txt
import a.txt
import b.txt

即可。

和 Nginx 一样,你需要提前将 a.comb.com 的域名解析以 A 形式指向你的服务器 IP。

静态网站 file_server

参考

1
2
3
4
example.com {
root * /var/www
file_server
}

访问 https://example.com 会看到服务器 /var/www/ 的内容。如果存在 index.html,则会打开这个网页。还可指定别的 index:

1
2
3
4
5
6
example.com {
root * /var/www
file_server {
index www.index.html
}
}

还可以使用浏览器浏览文件夹 + 重定向 + 加上反斜线。

1
2
3
4
5
6
7
8
9
example.com {
redir /v /v/
handle /v/* {
uri strip_prefix /v
file_server browse {
root /etc/uestcmsc_webapp/getproxy/
}
}
}

反向代理 reverse_proxy

参考

1
2
3
example.com {
reverse_proxy localhost:5000
}

三行即可。访问 https://example.com 实际上访问的是服务器的 5000 端口。

利用以下配置可将 https://example.com/proxy 反向代理到 localhost:5000

1
2
3
example.com {
reverse_proxy /proxy localhost:5000
}

还可利用以下配置可将 https://example.com 反向代理到 localhost:5000/proxy

1
2
3
4
example.com {
uri replace / /proxy 1
reverse_proxy localhost:5000
}

上述配置表示,在反向代理之前,将 uri 的前 1/ 替换为 /proxy

重定向 redir

参考

1
2
3
www.example.com {
redir https://example.com{uri}
}

访问 www.example.com302 Redirect 重定向到 https://example.com

也可以使用 permanent

1
2
3
www.example.com {
redir https://example.com{uri} permanent
}

访问 www.example.com301 Move permanently 重定向到 https://example.com

按顺序执行 route

参考

上述语法 file_serverreverse_proxyredir 可以混合使用。

1
2
3
4
5
6
example.com {
reverse_proxy /proxy localhost:5000
redir /github github.com
redir /google google.com
file_server * /var/www/html/
}

这会使得 example.com/proxyexample.com/githubexample.com/google 以及 example.com 的其他地址执行对应的功能。

但是,默认情况下,在执行过程中,指令的执行顺序会根据指令名进行调整。比如,file_server 是最后执行的。如果改成以下代码,file_server 则不会被运行,因为在执行到 file_server 之前,/google 已经被重定向了。

1
2
3
4
5
6
example.com {
reverse_proxy /proxy localhost:5000
redir /github github.com
file_server /google /var/www/html/
redir * google.com
}

如果真的需要这么做,可以将所有命令包含在 route 的语句块中。语句块中的内容将被顺序执行

1
2
3
4
5
6
7
8
example.com {
route {
reverse_proxy /proxy localhost:5000
redir /github github.com
file_server /google /var/www/html/
redir * google.com
}
}

处理 handle

handle 类似于 Nginx 中的 location,是一种类似于分支逻辑的 HTTP handler logic(HTTP 处理逻辑)。

1
2
3
4
5
6
7
8
example.com {
handle /foo/* {
file_server
}
handle / {
reverse_proxy 127.0.0.1:8080
}
}

上述语法就会将 /foo 下面的网址以 file_server 运行,对其他则会进行反向代理。实现的功能和 route 类似,不过我猜测 handle 处理这类问题更高效。

定义错误页面 handle_errors

参考

基于静态网站定义 404 页面的代码如下:

1
2
3
4
5
6
7
8
example.com {
root * /var/www/
file_server
handle_errors {
rewrite * /{http.error.status_code}.html
file_server
}
}

访问到不存在的网址则会显示 /404.html 的内容(但网址不会变化,仍然是访问的网址)。

rewrite url try_files

参考

这三条命令都是修改 uri 用。

  • rewrite 将匹配的 uri 修改为指定的 uri;
  • uri 在原 uri 上进行修改;
  • try_filesuri 修改为列出路径中,路径对应的文件(文件夹)存在的第一项。

修改以后,只是访问的路径变化,并不会在地址栏有所体现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
example1.com {
rewrite * /foo.html # 将所有路径修改为 foo.html
}

example2.com {
uri replace / /proxy 1 # 将第一个 / 修改为 /proxy,即在链接前添加一个 /proxy
reverse_proxy localhost:9000 # example2.com/ => localhost:9000/proxy
}

example2.com {
route /api/* {
uri strip_prefix /api
reverse_proxy localhost:9000
}
}

example3.com {
try_files {path} /index.php # 如果 {path} 存在,访问 {path};否则访问 index.php
}

占位符 placeholders

上文的 {uri} 即是一种占位符。更多的占位符可见:https://caddyserver.com/docs/caddyfile/concepts#placeholders

匹配串 matchers

配置文件中一个很重要的部分是匹配串(matchers)如 / /proxy。在 https://caddyserver.com/docs/caddyfile/matchers 做了详细介绍。

泛域名 HTTPS?

当然,以上域名只做到了单域名 HTTPS,对于泛域名解析 HTTPS 则会麻烦得多。这是由于在 Let’s Encrypt,单域名 HTTPS 证书可以使用 HTTP 验证(在网站对应的指定路径放一个指定的 HTML),而泛域名 (如*.example.com HTTPS 证书则需要使用 DNS 验证(在指定的域名下放一个 DNS 解析)。前者交给 Caddy 做非常方便,而后者就不那么方便了。

对于该问题,有以下解决办法:

  1. Caddy 2 的确可以通过插件调用各 DNS 提供商的 API 来实现修改 DNS,但是 Caddy 2 官方的插件只有很少,像国内阿里云、腾讯云都没有开发。貌似可以在阿里云将域名的 DNS 解析服务器改为 CloudFlare 的,然后利用 CloudFlare API 实现,但是这里我没有深入研究。

  2. 相比于 Caddy 2,Caddy 1 就提供了不少插件(但仍然没有阿里云)。如有需求可改为 Caddy 1。

  3. 使用 On-Demand TLS 技术。这是 Caddy 研发的一种技术。

在第一次 TLS 握手时,如果发现本地没有 HTTPS 证书或已过期以后,就立刻向 Let’s Encrypt 申请证书,成功后保存该证书,并完成此次握手;此后如果发现存在 HTTPS 证书且有效,就会使用该证书完成握手。

该方法的优点非常明显,就是可以用对访问到的每个域名申请 HTTPS 证书的方法,代替 DNS 验证。缺点也很明显,由于 HTTPS 证书申请需要时间,再加上国内网络问题,在首次访问某域名时,需要等待 10-40 秒不等供 Caddy 服务器申请证书。

我最终采用了这种方式,因为使用泛域名解析只是为了跳转到主域名上,实际上没有几个人会访问这些域名的。

On-Demand TLS 语法如下:

1
2
3
4
5
6
*.example.com {
tls {
on_demand
}
redir https://example.com{uri} permanent
}
  1. 最后一种无奈之举,就是不使用 HTTPS。如果该域名只是做一个 redir,其实不使用 HTTP 也还可以接受。

Caddy 不使用 HTTPS 的语法是,在域名后指定 80 端口。

1
2
3
*.example.com:80 {
redir https://example.com{uri} permanent
}