Caddyfile 概念
本文将帮助你详细了解 HTTP Caddyfile。
结构
Caddyfile 的结构可以用视觉方式描述:
要点:
Caddyfile 至少包含一个或多个站点块,每个块总是以一个或多个站点地址开始。任何出现在地址前的指令都会让解析器困惑。
块
用大括号打开和关闭块:
... {
...
}
- 左大括号
{
必须在其行末且前有空格。 - 右大括号
}
必须单独成行。
当只有一个站点块时,大括号(和缩进)可选。这是为了方便快速定义单站点,例如:
localhost
reverse_proxy /api/* localhost:9001
file_server
等价于:
localhost {
reverse_proxy /api/* localhost:9001
file_server
}
当你只有一个站点块时,两者都可以,取决于个人偏好。
要用同一个 Caddyfile 配置多个站点,必须用大括号包裹每个站点以分隔配置:
example1.com {
root * /www/example.com
file_server
}
example2.com {
reverse_proxy localhost:9000
}
如果一个请求匹配多个站点块,将选择地址最具体的站点块。请求不会级联到其他站点块。
指令
指令是功能性关键字,用于自定义站点的服务方式。它们必须出现在站点块内。例如,一个完整的文件服务器配置可能如下:
localhost {
file_server
}
或反向代理:
localhost {
reverse_proxy localhost:9000
}
在这些例子中,file_server
和 reverse_proxy
是指令。指令是站点块中每行的第一个单词。
在第二个例子中,localhost:9000
是参数,因为它出现在指令后同一行。
有时指令可以打开自己的块。子指令出现在指令块内每行的开头:
localhost {
reverse_proxy localhost:9000 localhost:9001 {
lb_policy first
}
}
这里,lb_policy
是 reverse_proxy
的子指令(设置后端间的负载均衡策略)。
**除非另有说明,指令不能嵌套在其他指令块内。**例如,basic_auth
不能用在 file_server
内,因为文件服务器不支持认证;但你可以在 route
、handle
和 handle_path
块内使用指令,因为它们专为分组指令设计。
注意,当 HTTP Caddyfile 被适配时,HTTP 处理器指令会按特定的默认顺序排序,除非在 route
块内,所以除 route
块外,指令出现的顺序无关紧要。
Token 与引号
Caddyfile 在解析前会被分词。Caddyfile 中空白符很重要,因为 token 由空白分隔。
通常,指令期望一定数量的参数;如果某个参数的值中有空格,会被分成两个 token:
directive abc def
这可能导致错误或意外行为。
如果 abc def
应作为单个参数值,需要加引号:
directive "abc def"
如果参数中需要引号,可以转义:
directive "\"abc def\""
为避免转义引号,也可以用反引号
包裹 token,例如:
directive `{"foo": "bar"}`
在带引号的 token 内,所有字符都按字面处理,包括空格、制表符和换行符。因此支持多行 token:
directive "first line
second line"
也支持 Heredoc :
example.com {
respond <<HTML
<html>
<head><title>Foo</title></head>
<body>Foo</body>
</html>
HTML 200
}
Heredoc 起始标记必须以 <<
开头,后跟任意文本(推荐大写字母)。结束标记必须与起始标记一致(如上例的 HTML
)。如需避免 heredoc 解析,可用 \<<
转义。
结束标记可缩进,这会导致每行文本去除相同缩进(受 PHP 启发),便于在块内书写且可控 token 文本的空白。结尾换行也会被去除,如需保留可在结束标记前加空行。
结束标记后可跟其他 token 作为指令参数(如上例中的状态码 200
)。
全局选项
Caddyfile 可选地以无键的特殊块开头,称为全局选项块:
{
...
}
如有,必须是配置的第一个块。
用于设置全局生效或不针对某一站点的选项。块内只能设置全局选项,不能用常规站点指令。
例如,启用常用于排查的 debug
全局选项:
{
debug
}
阅读全局选项页面了解更多。
地址
地址总是出现在站点块顶部,通常是 Caddyfile 的第一项。
以下是有效地址示例:
地址 | 效果 |
---|---|
example.com |
使用受信任公有证书的 HTTPS |
*.example.com |
使用受信任通配符证书的 HTTPS |
localhost |
使用本地受信任证书的 HTTPS |
http:// |
HTTP 全部,受 http_port 影响 |
https:// |
HTTPS 全部,受 https_port 影响 |
http://example.com |
明确 HTTP,带 Host 匹配 |
example.com:443 |
由于端口匹配 https_port 默认值,使用 HTTPS |
:443 |
由于端口匹配 https_port 默认值,HTTPS 全部 |
:8080 |
非标准端口 HTTP,无 Host 匹配 |
localhost:8080 |
非标准端口 HTTPS,因有有效域名 |
https://example.com:443 |
HTTPS,但 https:// 和 :443 都是多余的 |
127.0.0.1 |
HTTPS,使用本地受信任 IP 证书 |
http://127.0.0.1 |
HTTP,IP 地址 Host 匹配(拒绝 localhost ) |
Caddy 可从地址推断站点的 scheme、主机和端口。如果地址无端口,Caddyfile 会选择与 scheme 匹配的端口,或默认 443。
如果你指定了主机名,只有 Host 头匹配的请求才会被处理。换句话说,如果站点地址是 localhost,则不会匹配 127.0.0.1 的请求。
通配符()可用,但只能代表主机名的一个标签。例如,.example.com 匹配 foo.example.com,但不匹配 foo.bar.example.com;* 匹配 localhost,但不匹配 example.com。实际用法见通配符证书模式。
要匹配所有主机,省略主机部分,如 https://。这在用按需 TLS 时很有用,适合提前不知道域名的场景。
如果多个站点块定义相同,可用空格或逗号分隔所有地址(至少一个空格)。以下三种写法等价:
# 逗号分隔
localhost:8080, example.com, www.example.com {
...
}
或
# 空格分隔
localhost:8080 example.com www.example.com {
...
}
或
# 换行+逗号分隔
localhost:8080,
example.com,
www.example.com {
...
}
地址必须唯一,不能重复。
占位符 不能用于地址,但可用 Caddyfile 风格的环境变量:
{$DOMAIN:localhost} {
...
}
默认情况下,站点监听所有网卡。如需自定义,用 bind
指令 或 default_bind
全局选项。
匹配器
HTTP 处理器指令默认应用于所有请求(除非另有说明)。
请求匹配器可用于根据给定条件对请求进行分类。使用匹配器,你可以准确指定某个指令应用于哪些请求。
对于支持匹配器的指令,指令后的第一个参数是匹配器标记。以下是一些示例:
root * /var/www # 匹配器标记: *
root /index.html /var/www # 匹配器标记: /index.html
root @post /var/www # 匹配器标记: @post
如果要匹配所有请求,可以完全省略匹配器标记;例如,如果下一个参数看起来不像路径匹配器,就不需要给出 *
。
阅读请求匹配器页面了解更多。
占位符
占位符是一种在静态配置中注入动态值的简单方法。它们可以用作指令和子指令的参数。
占位符两侧由大括号 { }
包围,内部包含标识符,例如:{foo.bar}
。可以通过转义开头的大括号 \{like.this}
来防止替换。占位符标识符通常使用点号进行命名空间分隔,以避免模块间的冲突。
可用的占位符取决于上下文。不是所有占位符都在配置的所有部分都可用。例如,HTTP 应用设置的占位符只在处理 HTTP 请求相关的配置区域可用(即在 HTTP 处理器指令和匹配器中,但在 tls
配置中不可用)。某些指令或匹配器也可能设置它们自己的占位符,这些占位符可以被后续的配置使用。某些占位符在全局范围内可用。
你可以在 Caddyfile 中使用任何占位符,但为了方便,你也可以使用以下这些等效的简写形式,它们会在解析 Caddyfile 时被展开:
Shorthand | Replaces |
---|---|
{cookie.*} |
{http.request.cookie.*} |
{client_ip} |
{http.vars.client_ip} |
{dir} |
{http.request.uri.path.dir} |
{err.*} |
{http.error.*} |
{file_match.*} |
{http.matchers.file.*} |
{file.base} |
{http.request.uri.path.file.base} |
{file.ext} |
{http.request.uri.path.file.ext} |
{file} |
{http.request.uri.path.file} |
{header.*} |
{http.request.header.*} |
{host} |
{http.request.host} |
{hostport} |
{http.request.hostport} |
{labels.*} |
{http.request.host.labels.*} |
{method} |
{http.request.method} |
{path.*} |
{http.request.uri.path.*} |
{path} |
{http.request.uri.path} |
{port} |
{http.request.port} |
{query.*} |
{http.request.uri.query.*} |
{query} |
{http.request.uri.query} |
{re.*} |
{http.regexp.*} |
{remote_host} |
{http.request.remote.host} |
{remote_port} |
{http.request.remote.port} |
{remote} |
{http.request.remote} |
{rp.*} |
{http.reverse_proxy.*} |
{resp.*} |
{http.intercept.*} |
{scheme} |
{http.request.scheme} |
{tls_cipher} |
{http.request.tls.cipher_suite} |
{tls_client_certificate_der_base64} |
{http.request.tls.client.certificate_der_base64} |
{tls_client_certificate_pem} |
{http.request.tls.client.certificate_pem} |
{tls_client_fingerprint} |
{http.request.tls.client.fingerprint} |
{tls_client_issuer} |
{http.request.tls.client.issuer} |
{tls_client_serial} |
{http.request.tls.client.serial} |
{tls_client_subject} |
{http.request.tls.client.subject} |
{tls_version} |
{http.request.tls.version} |
{upstream_hostport} |
{http.reverse_proxy.upstream.hostport} |
{uri} |
{http.request.uri} |
{vars.*} |
{http.vars.*} |
并非所有配置字段都支持占位符,但在你期望的地方大多数都支持。对占位符的支持需要明确添加到这些字段中。插件作者可以阅读本文来了解如何在自己的模块中添加对占位符的支持。
片段
你可以通过给特殊块一个用括号括起来的名称来定义片段:
(logging) {
log {
output file /var/log/caddy.log
format json
}
}
然后你可以在任何需要的地方使用特殊的 import
指令重用它:
example.com {
import logging
}
www.example.com {
import logging
}
import
指令也可以用来在其位置包含其他文件。如果参数与已定义的片段不匹配,它将被尝试作为文件处理。它还支持使用通配符来导入多个文件。作为特殊情况,它可以出现在 Caddyfile 的任何位置(除了作为另一个指令的参数),包括站点块之外:
{
email admin@example.com
}
import sites/*
你可以向导入的配置(片段或文件)传递参数,并像这样使用它们:
(snippet) {
respond "Yahaha! 你找到了 {args[0]}!"
}
a.example.com {
import snippet "示例 A"
}
b.example.com {
import snippet "示例 B"
}
⚠️ Experimental | v2.9.x+
You can also pass an optional block to an imported snippet, and use them as follows.
(snippet) {
{block}
respond "OK"
}
a.example.com {
import snippet {
header +foo bar
}
}
b.example.com {
import snippet {
header +bar foo
}
}
Read the import
directive page to learn more.
命名路由
⚠️ 实验性功能
命名路由使用类似于片段的语法;它们是在站点块之外定义的特殊块,以 &(
开头,以 )
结尾,名称在其中间。
&(app-proxy) {
reverse_proxy app-01:8080 app-02:8080 app-03:8080
}
然后你可以在任何站点中重用这个命名路由:
example.com {
invoke app-proxy
}
www.example.com {
invoke app-proxy
}
如果在许多不同站点中需要相同的路由,或者如果需要多个不同的匹配器条件来调用相同的路由,这特别有用,可以减少内存使用。
阅读 invoke
指令页面了解更多。
注释
注释以 #
开头,一直延伸到行尾:
# 注释可以在行首
directive # 或者在行尾
注释的井号 #
不能出现在标记的中间(即必须前面有空格或出现在行首)。这允许在 URI 或其他值中使用井号而无需引号。
环境变量
如果你的配置依赖于环境变量,你可以在 Caddyfile 中使用它们:
{$ENV}
这种形式的环境变量会在 Caddyfile 解析开始之前被替换,所以它们可以展开为空值(即 ""
)、部分标记、完整标记,甚至多个标记和行。
例如,环境变量 UPSTREAMS="app1:8080 app2:8080 app3:8080"
将展开为多个标记:
example.com {
reverse_proxy {$UPSTREAMS}
}
当找不到环境变量时,可以使用 :
作为变量名和默认值之间的分隔符来指定默认值:
{$DOMAIN:localhost} {
}
如果你想在运行时延迟替换环境变量,你可以使用标准的 {env.*}
占位符。请注意,并非所有配置参数都支持这些占位符,因为模块开发者需要添加代码来执行替换。如果它似乎不起作用,请提交问题来请求支持。
例如,如果你安装了 caddy-dns/cloudflare
插件 并希望配置 DNS 质询,你可以像这样将你的
CLOUDFLARE_API_TOKEN
环境变量传递给插件:
{
acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}
如果你正在以 systemd 服务运行 Caddy,请参阅这些说明来设置服务覆盖以定义你的环境变量。