一直以来我自己都是用 nginx
作为自己的web服务后端。这两天帮朋友搭建服务,觉得 nginx
太过麻烦,不适合快速实现,所以调研了一下 Caddy2
觉得用起还算比较方便,所以就用 Caddy2
帮朋友把服务搭建好了。帮朋友搭建完成后,配置一了下,觉得自己的服务也可以用 Caddy2
来实现,所以就趁机把服务器切换成 Caddy2
了。
需求背景
对于 web
服务有以下几个需求:
-
支持
https
-
支持代理转发
-
支持
webdav
-
支持
letsencrypt
证书自动获取
实现这几个功能,那么web服务的功能基本就齐全了。还有一点也是本次改造重点关注的地方,那就是可以实现简单的快速部署。
Nginx存在的问题
对于 nginx
来说,主要有以下几个问题:
-
首先
nginx
不能自动申请letsencrypt
证书,我的解决方案是通过lego
来定时获取的。 -
其次,默认的
nginx
对webdav
命令支持不全,如果有需要,还要额外编译ngx-dav-ext-module
。 -
第三,还有就是
nginx
的webdav
模块处理路径最后’/‘
时,还有点小bug。所以使用起来不是很方便。 -
最后,nginx配置文件写起来真的很麻烦。
申请证书
因为 nginx
本身不能申请证书,所以申请证书的工作是通过 lego
来实现的。而 lego
的使用也有很多麻烦的地方。
lego
命令本身使用起来非常复杂,为了使用方便需要封装一个脚本方便调用。
#!/bin/bash
lego_bin=/path/to/lego
data=/path/to/letsencrypt
domain="*.leenzhu.com"
log=/path/to/lego.log
account_email="a@b.com"
dns_provider="alidns"
export ALICLOUD_ACCESS_KEY=<ali-acc>
export ALICLOUD_SECRET_KEY=<ali-sec>
case $1 in
run)
$lego_bin --email="$account_email" --domains="$domain" --accept-tos --path=$data --dns="$dns_provider" run --run-hook="`readlink -f $0` reload" >> $log
;;
renew)
for domain in `$lego_bin --path=$data list -n` ; do
$lego_bin --email="$account_email" --domains="$domain" --accept-tos --path=$data --dns="$dns_provider" renew --reuse-key --renew-hook="`readlink -f $0` reload" >> $log
done
;;
reload)
#cp -f $data/certificates/*.crt /usr/local/openresty/nginx/conf/vhosts/keys/
#cp -f $data/certificates/*.key /usr/local/openresty/nginx/conf/vhosts/keys/
#rm -rf /usr/local/openresty/nginx/conf/vhosts/keys/*.issuer.crt
#/usr/local/openresty/bin/openresty -s reload
sudo /usr/sbin/nginx -s reload
;;
,*)
echo "黙认签发泛域名证书,只写根域名即可"
echo "证书保存在 $data"
echo "用法: 1. 签发证书 $0 run nixops.me"
echo "用法: 2. 续签证书 $0 renew "
echo "用法: 3. 复制证书,reload服务 $0 reload"
;;
esac
因为证书需要定时签发,所以需要把 lego
做成一个定时服务,通过 systemd
来实现定时任务,就需要编写两个文件,一个是 lego.service
:
[Unit]
Description=Lego for let's encrypt auto issue cert
[Service]
Type=oneshot
ExecStart=/path/to/lego.sh renew
一个是 lego.timer
:
[Unit]
Description=Daily check for lego.service
[Timer]
OnCalendar=daily
AccuracySec=15m
Persistent=true
[Install]
WantedBy=timers.target
dav配置
以下配置记不得是从哪抄来的,虽然知道每配置的意思,但是不知道为什么要配置这么多。
location / {
root /path/to/site/root;
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, HEAD, POST, PUT, OPTIONS, MOVE, DELETE, COPY, LOCK, UNLOCK' always;
add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,X-Accept-Charset,X-Accept,origin,accept,if-match,destination,overwrite' always;
add_header 'Access-Control-Expose-Headers' 'ETag' always;
add_header 'Access-Control-Max-Age' 1728000 always;
if ($request_method = 'OPTIONS') {
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, HEAD, POST, PUT, OPTIONS, MOVE, DELETE, COPY, LOCK, UNLOCK';
add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,X-Accept-Charset,X-Accept,origin,accept,if-match,destination,overwrite';
add_header 'Access-Control-Expose-Headers' 'ETag';
add_header 'Access-Control-Max-Age' 1728000;
return 204;
}
autoindex on;
dav_methods PUT DELETE MKCOL COPY MOVE;
dav_ext_methods PROPFIND OPTIONS;
create_full_put_path on;
dav_access user:rw group:rw all:rw;
auth_basic "Authorized Users Only";
auth_basic_user_file /path/to/htpasswd;
# MKCOL不以/结尾
if ($request_method = MKCOL) { rewrite ^(.*[^/])$ $1/ break; }
# Windows下MOVE文件夹不以/结尾
if (-d $request_filename) {
rewrite ^(.*[^/])$ $1/;
set $md /;
}
# 重命名文件夹Destination不以/结尾,需要headers-more-nginx-module
set $x $http_destination$request_method;
if ($x ~ [^/]MOVE) {
more_set_input_headers -r "Destination: ${http_destination}${md}";
}
#没有PROPPATCH指令,用PROPFIND处理。
proxy_method PROPFIND;
include proxy_params;
proxy_set_header Host "webdav.leenzhu.com";
if ($request_method = PROPPATCH) {
proxy_pass https://127.0.0.1;
}
}
dav bug处理
nginx
的 webdav
主要有3处bug:
-
MKCOL不以
'/'
结尾 -
Windows下MOVE文件夹不以
'/'
结尾 -
重命名文件夹
Destination
不以'/'
结尾,需要headers-more-nginx-module
-
没有PROPPATCH指令,用PROPFIND处理。
# MKCOL不以/结尾
if ($request_method = MKCOL) { rewrite ^(.*[^/])$ $1/ break; }
# Windows下MOVE文件夹不以/结尾
if (-d $request_filename) {
rewrite ^(.*[^/])$ $1/;
set $md /;
}
# 重命名文件夹Destination不以/结尾,需要headers-more-nginx-module
set $x $http_destination$request_method;
if ($x ~ [^/]MOVE) {
more_set_input_headers -r "Destination: ${http_destination}${md}";
}
#没有PROPPATCH指令,用PROPFIND处理。
proxy_method PROPFIND;
include proxy_params;
proxy_set_header Host "webdav.leenzhu.com";
if ($request_method = PROPPATCH) {
proxy_pass https://127.0.0.1;
}
nginx 配置示例
对于一个代理转发,nginx至少要配置以下的内容,如果有10个服务要配置,那么下面的代码就要写10遍。
server {
listen 443 ssl;
server_name rss.leenzhu.com;
location / {
proxy_set_header Host $http_host;
proxy_pass http://localhost:8280;
}
}
遇到 websocket
代理,上述配置并不能正常工作,需要参考如下配置:
server {
listen 443 ssl;
server_name sy.leenzhu.com;
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_pass http://localhost:6806;
}
}
Caddy2 实现
Caddy2 安装
因为 caddy2
提供了编译工具 xcaddy
,所以定制编译 caddy2
比较简单:
export GOPROXY=https://goproxy.cn
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build v2.6.2 \
--with github.com/mholt/caddy-webdav \
--with github.com/caddy-dns/alidns \
--with github.com/caddy-dns/cloudflare
Caddy2 配置
对 Caddy2
配置需要进行以下处理:
-
全局配置
-
泛域名证书获取
-
子域名共享泛域名证书
-
本机目录静态资源访问
-
本机反向代理配置
-
跨机器反向代理配置
-
本机静态资源混合反向代理实现
-
webdav配置
-
webdav 用户认证配置
-
webdav 多用户配置
-
不同用户不同目录配置
-
webdav 目录浏览支持
-
{
http_port 80
https_port 443
email i@leenzhu.com
}
(local) {
@{args.0} host {args.0}.{labels.1}.{labels.0}
handle @{args.0} {
root * /path/to/caddy/www/{args.0}.{labels.1}.{labels.0}
}
}
(host) {
@{args.0} host {args.0}.{labels.1}.{labels.0}
}
(fwd) {
@{args.0} host {args.0}.{labels.1}.{labels.0}
handle @{args.0} {
reverse_proxy 127.0.0.1:{args.1}
}
}
(fwdex) {
@{args.0} host {args.0}.{labels.1}.{labels.0}
handle @{args.0} {
reverse_proxy @{args.0} {args.1}
}
}
https://*.leenzhu.com {
tls {
dns alidns {
access_key_id {env.ALIYUN_ACCESS_KEY_ID}
access_key_secret {env.ALIYUN_ACCESS_KEY_SECRET}
}
}
import fwd git 3000
import fwdex say 192.168.50.100:8086
import local keeweb
import host sync
handle @sync {
reverse_proxy 127.0.0.1:8384 {
header_up Host {upstream_hostport}
header_up X-Forwarded-Host {host}
}
}
import host down
handle @down {
reverse_proxy /jsonrpc 127.0.0.1:6800
file_server {
root /path/to/caddy/www/down.leenzhu.com
}
}
import host webdav
handle @webdav {
basicauth {
user1 xxxxxxxxxxxxxx
user2 xxxxxxxxxxxxxx
}
route {
@get method GET
root * /path/to/caddy/www/webdav.leenzhu.com/{http.auth.user.id}
file_server @get browse
webdav
}
}
handle {
abort
}
}
总结
-
caddy2
安装部署过程还算顺利,没有遇到卡了很久的问题 。 -
caddy2
的import
功能可以简化不少配置代码的编写。 -
webdav
不能控制读写权限,需要通过系统文件权限来控制 -
caddy2
默认反向代理会默认传递原始请求Host
值给后端,这点与nginx
相反,在部署syntching
服务时,需要通过header_up
指令修正。