Contents

caddy

Caddy

Caddy 是一个 Go 编写的 Web 服务器,类似于 Nginx,Caddy 提供了更加强大的功能,随着 v2 版本发布 Caddy 已经可以作为中小型站点 Web 服务器的另一个选择;相较于 Nginx 来说使用 Caddy 的优势如下:

  • 自动的 HTTPS 证书申请(ACME HTTP/DNS 挑战)
  • 自动证书续期以及 OCSP stapling 等
  • 更高的安全性包括但不限于 TLS 配置以及内存安全等
  • 友好且强大的配置文件支持
  • 支持 API 动态调整配置(有木有人可以搞个 Dashboard?)
  • 支持 HTTP3(QUIC)
  • 支持动态后端,例如连接 Consul、作为 k8s ingress 等
  • 后端多种负载策略以及健康检测等
  • 本身 Go 编写,高度模块化的系统方便扩展(CoreDNS 基于 Caddy1 开发)

一、Caddyfile

1、简单使用

在当前目录下新建Caddyfile文件

localhost {
	respond "Hello, world!"
}

localhost:2016 {
	respond "Goodbye, world!"
}

服务管理

# 启动服务
caddy start

# 重启服务,重启服务有两种方式
caddy reload
curl localhost:2019/load \
	-X POST \
	-H "Content-Type: text/caddyfile" \
	--data-binary @Caddyfile


# 停止服务
caddy stop

2、静态文件管理

显示文件列表

localhost

file_server browse

文件夹作为站点根目录:

localhost

root * /home/me/mysite
file_server

3、反向代理

localhost

reverse_proxy 127.0.0.1:9000

4、启用压缩算法

localhost

encode zstd gzip
file_server browse

5、多个站点

:8080 {
	respond "I am 8080"
}

:8081 {
	respond "I am 8081"
}

多端口

:8080, :8081 {
	...
}

6、匹配器

对某一个api使用反向代理

localhost

file_server
reverse_proxy /api/* 127.0.0.1:9005

# 现在反向代理只会处理所有以/api/开始的请求。

使用环境变量

export SITE_ADDRESS=localhost:9055
{$SITE_ADDRESS}

file_server
root *           /var/www  # matcher token: *
root /index.html /var/www  # matcher token: /index.html
root @post       /var/www  # matcher token: @post

占位符

简写 替换
{dir} {http.request.uri.path.dir}
{file} {http.request.uri.path.file}
{header.*} {http.request.header.*}
{host} {http.request.host}
{labels.*} {http.request.host.labels.*}
{hostport} {http.request.hostport}
{port} {http.request.port}
{method} {http.request.method}
{path} {http.request.uri.path}
{path.*} {http.request.uri.path.*}
{query} {http.request.uri.query}
{query.*} {http.request.uri.query.*}
{re.*.*} {http.regexp.*.*}
{remote} {http.request.remote}
{remote_host} {http.request.remote.host}
{remote_port} {http.request.remote.port}
{scheme} {http.request.scheme}
{uri} {http.request.uri}
{tls_cipher} {http.request.tls.cipher_suite}
{tls_version} {http.request.tls.version}
{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_client_certificate_pem} {http.request.tls.client.certificate_pem}
{tls_client_certificate_der_base64} {http.request.tls.client.certificate_der_base64}
{upstream_hostport} {http.reverse_proxy.upstream.hostport}

在Caddyfile中,紧跟在指令后面的匹配器标记可以限制该指令的范围。匹配器标记可以是以下形式之一:

  1. \* 匹配所有请求(通配符;默认)。
  2. /path 以正斜杠开头以匹配请求路径。
  3. @name 指定一个命名匹配器。

匹配器标记通常是可选的。如果省略匹配器标记,则它与通配符匹配器(*)相同。

命名匹配器

要匹配路径以外的任何内容,请定义一个命名匹配器并使用@name引用它:

@postfoo {
	method POST
	path /foo/*
}
reverse_proxy @postfoo localhost:9000
@websockets {
	header Connection *Upgrade*
	header Upgrade    websocket
}
reverse_proxy @websockets localhost:6001

file

file {
	root       <paths>
	try_files  <files...>
	try_policy first_exist|smallest_size|largest_size|most_recent_modified
	split_path <delims...>
}

通过文件进行匹配。

  • root定义在其中查找文件的目录。默认是当前工作目录,或者root变量 ({http.vars.root})对应的位置 (可以通过root指令设置)。

  • try_files检查其列表中与重试策略(try_policy)匹配的文件。如果try_policyfirst_exist,那么列表中的最后一项可能是一个以=(比如=404)开头的数字,作为后备,将触发以这个数字作为错误码的回调; 该错误也可以使用handle_errors捕获和处理错误。

  • try_policy
    

    指定如何选择文件。默认为

    first_exist
    

    .

    • first_exist检查文件是否存在。选择存在的第一个文件。
    • smallest_size选择大小最小的文件。
    • largest_size选择最大的文件。
    • most_recent_modified选择最近修改的文件。
  • split_path将导致路径在每个要尝试的文件路径中找到的列表中的第一个分隔符处拆分。对于每个拆分值,拆分的左侧(包括分隔符本身)将是尝试的文件路径。例如,/remote.php/dav/使用.php作为分隔符,将尝试文件/remote.php。每个分隔符必须出现在 URI 路径组件的末尾,才能用作拆分分隔符。这是一个小众设置,主要用于为 PHP 站点提供服务。

7、地址

有效地址:

  • localhost
  • example.com
  • :443
  • http://example.com
  • localhost:8080
  • 127.0.0.1
  • [::1]:2015
  • example.com/foo/*
  • *.example.com
  • http://

注意:如果你的站点地址包含主机名或 IP 地址,则会启用自动HTTPS。然而,这种行为纯粹是隐含的,因此它永远不会覆盖任何显式配置。例如,如果站点的地址是http://example.com,则不会激活自动HTTPS,因为该方案是明确的http://

如果找不到文件,则回退到发出404错误。

file {path}.html {path} =404
header <field> [<value>]

通过请求头字段进行匹配。

  • <field>
    

    是要检查的 HTTP 标头字段的名称。

    • 如果以!为前缀,则该字段必须不存在才能匹配 (省略value参数).
  • <value>
    

    是字段必须匹配的值。

    • 如果前缀是*,则执行快速后缀匹配。
    • 如果后缀为*,则执行快速前缀匹配。
    • 如果用*括起来,它将执行快速子字符串匹配。
    • 否则,它是快速精确匹配。

统一集合的不同header字段是“和”的关系。每个字段的多个值之间是“或”的关系。

示例:

匹配请求Connection标头字段包含Upgrade的请求:

header Connection *Upgrade*

匹配Foo标头字段包含bar或者baz的请求:

@foo {
	header Foo bar
	header Foo baz
}

匹配根本没有Foo标头字段的请求:

@not_foo {
	header !Foo
}

8、片段

可以定义称为片段的特殊块,方法是给它们一个用括号括起来的名称:

(redirect) {
	@http {
		protocol http
	}
	redir @http https://{host}{uri}
}

然后你可以在任何你需要的地方重复使用它:

import redirect

例如:

(snippet) {
  respond "Yahaha! You found {args.0}!"
}

a.example.com {
	import snippet "Example A"
}

b.example.com {
	import snippet "Example B"
}

9、全局参数

{
	# General Options
	debug
	http_port  <port>
	https_port <port>
	order <dir1> first|last|[before|after <dir2>]
	storage <module_name> {
		<options...>
	}
	storage_clean_interval <duration>
	admin   off|<addr> {
		origins <origins...>
		enforce_origin
	}
	log [name] {
		output  <writer_module> ...
		format  <encoder_module> ...
		level   <level>
		include <namespaces...>
		exclude <namespaces...>
	}
	grace_period <duration>

	# TLS Options
	auto_https off|disable_redirects|ignore_loaded_certs
	email <yours>
	default_sni <name>
	local_certs
	skip_install_trust
	acme_ca <directory_url>
	acme_ca_root <pem_file>
	acme_eab <key_id> <mac_key>
	acme_dns <provider> ...
	on_demand_tls {
		ask      <endpoint>
		interval <duration>
		burst    <n>
	}
	key_type ed25519|p256|p384|rsa2048|rsa4096
	cert_issuer <name> ...
	ocsp_stapling off
	preferred_chains [smallest] {
		root_common_name <common_names...>
		any_common_name  <common_names...>
	}

	# Server Options
	servers [<listener_address>] {
		listener_wrappers {
			<listener_wrappers...>
		}
		timeouts {
			read_body   <duration>
			read_header <duration>
			write       <duration>
			idle        <duration>
		}
		max_header_size <size>
		protocol {
			allow_h2c
			experimental_http3
			strict_sni_host
		}
	}
}

二、使用示例

1、静态文件服务器

example.com {
	root * /var/www
	file_server
}

像往常一样,第一行是站点地址。该root指令指定站点根目录的路径(*匹配所有请求的方法,以便与路径匹配器消除歧义);如果站点不是当前工作目录,则更改站点的路径。最后,我们启用静态文件服务器。

2、反向代理服务器

代理所有请求

example.com {
	reverse_proxy localhost:5000
}

只代理以/api/开头的请求,并为其他所有内容提供静态文件:

example.com {}
    root * /var/www
    reverse_proxy /api/* localhost:5000
    file_server
}

3、php

在运行PHP FastCGI服务的情况下,类似这样的内容适用于大多数现代PHP应用程序

example.com

root * /var/www
php_fastcgi /blog/* localhost:9000
file_server

请对应地调整站点根目录和路径匹配器;此示例假定PHP仅位于/blog/子目录中——所有其他请求将作为静态文件提供。

php_fastcgi指令实际上只是几个配置的快捷方式。

4、重定向到www.子域名

example.com {
	redir https://www.example.com{uri}
}

www.example.com {
}
www.example.com {
	redir https://example.com{uri}
}

example.com {
}

5、通配符证书

*.example.com {
	tls {
		dns <provider_name> [<params...>]
	}

	@foo host foo.example.com
	handle @foo {
		respond "Foo!"
	}

	@bar host bar.example.com
	handle @bar {
		respond "Bar!"
	}

	# Fallback for otherwise unhandled domains
	handle {
		abort
	}
}

6、web

example.com { # 网站的域名信息

    tls example.com.pem example.com.key # 证书和密钥的 PEM 格式的文件路径
    encode zstd gzip # 启用压缩
    root * ./ # 域名映射根路径
    file_server # 启动文件服务
}

example2.com { # 网站的域名信息
    tls example2.com.pem example2.com.key # 证书和密钥的 PEM 格式的文件路径
	reverse_proxy localhost:9000 # 反向代理
}

三、部署

docker-compose.yaml

version: "3.7"

services:
  caddy:
    image: caddy:<version>
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - $PWD/Caddyfile:/etc/caddy/Caddyfile
      - $PWD/site:/srv
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:

caddyfile 示例

 {  
    http_port 9080
    # 设置http类型
     servers {
        protocols h1 h2
    }
    # 关闭admin api
    admin off

    # auto_https off|disable_redirects|ignore_loaded_certs
 }


(TLSC) {
    tls /etc/nginx/cert_files/local.com.crt /etc/nginx/cert_files/local.com.key
}


(LOGC) {

    log {
        format transform `{request>remote_ip} - {user_id} [{ts}] "{request>method} {request>uri} {request>proto}" {status} {size} "{request>headers>Referer>[0]}" "{request>headers>User-Agent>[0]}"` {
                time_format "iso8601"
        }

        # {args.0} 声明引用传入的第一个参数
        output file {args.0} {
            roll_size 100mb
            roll_keep 7
            roll_local_time
            roll_keep_for 30d
        }
    }
}





https://terraform-registry.local.com:9090 {
    encode zstd gzip
    root * /data/terraform-registry
    try_files {path} /index.html
    file_server browse
    import TLSC
    import LOGC /var/log/nginx/tf-registry.log
}

(PROXY_HEADER) {
    header_up Host {host}
    header_up REMOTE-HOST {remote}
    header_up X-Real-IP {remote}
    header_up X-Forwarded-For {remote}    
}

git.local.com:9090, local.com:9090 {
    encode zstd gzip
    import TLSC
    import LOGC /var/log/nginx/local.com.log

    reverse_proxy http://localhost4:5000 {
        import PROXY_HEADER
    }

}