“Nginx + FRP + Caddy2” 实现自动化小绿锁内网穿透

前言

博客和一些乱七八糟的网站现在统一搬到我的群晖 220+ 上来,需要一个有公网 IP 的 VPS 把内网中的这些网站映射出去。

之前使用的方案是群晖内部跑一个宝塔面板负责网站的运行和 SSL 的验证,然后使用 FRP 将群晖里的这些网站穿透到 VPS 所在的公网 IP 并负责当作 HTTP 服务器。这么做的缺点有:
1. 在于位于内网 Docker 中的网站无法获取到访客的真实 IP,需要在 VPS 中安装 Nginx 进行反向代理,在请求头部写入访客的真实 IP 以供 Docker 内的 Nginx 获取。而在 VPS 中安装 Nginx 之后,SSL证书管理需要从 Docker 内部的宝塔面板移动到 VPS 中手动管理,无法自动化了。
2. 群晖的宝塔面板内部可以自动管理 SSL 证书,但是穿透到外网的群晖服务无法自动管理 SSL 证书,需要手动申请和更新,自动化脚本暂时也不是很聪明的样子,整体来说比较麻烦。
3. FRP 针对 Https 网站的内网穿透还不支持按照请求路径进行分流,导致群晖和群晖相册在 Https 下无法使用相同的域名,不太优美。
4. 群晖内部的 Nginx 针对系统内的 Https 访问似乎有一些不太聪明…

整体架构

为了解决这个问题,现在公网 IP 的 VPS 使用 Caddy2 作为 Web 服务器,VPS 与 Nas 之间使用 FRP 进行内网穿透,Nas 中使用宝塔面板运行网站和其他应用,整体架构如下图:

整体架构

这样做的好处在于,VPS 的 Caddy2 之前所有环节都是基于 Http 服务的,无须苦恼证书配置与Frp在Https的某些功能缺失;而 VPS 里的 Caddy2 支持自动化申请并更新证书,使用非常比方便。

Caddy2 配置

建立 \etc\caddy 目录用来存放 Caddy2 的程序与配置文件。

1. 下载 xcaddy

通过 xcaddy Github 的说明文档安装 xcaddy 的二进制文件,直接从项目Release中下载二进制文件或者运行如下命令:

go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
2. 编译 caddy 文件

因为 caddy2 的插件需要在程序编译的过程中集成,所以我们要通过 xcaddy 文件编译得到最新的 caddy 服务器程序。
这里因为我的域名在 dnspod.cn 中,后续需要通过 dnspod.cn 的 API Token 来申请泛域名的 SSL 证书,所以这一步我使用如下命令编译:

xcaddy build --with clevergo.tech/caddy-dnspodcn

如果你使用别的域名提供商,需要将插件修改成对应的内容。

3. 建立程序硬链接

为了日后能够方便的使用 xcaddy 更新主程序,这里给将 caddy 程序建立一个硬链接到系统目录中:

ln \etc\caddy\caddy \usr\bin\caddy
4.编写配置文件

运行如下命令新建 Caddy2 配置文件:

vi /etc/caddy/Caddyfile

测试环境,因为 Let’s Encrypt 的证书申请一段时间内有频次限制,所以在写配置文件的时候,最好在测试模式下确认可以申请成功,然后在正式申请证书。
只需要在配置文件中加入,如下内容:

{
  # 解除如下注释,用于测试证书申请(防止重复申请失败导致超过申请频率限制)
  # 在测试通过的生产环境中去除该项
  # acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}

网站通用的定义,例如启用 HSTS 和启用 gzip 压缩。可以根据自己的需要加入其他的内容。
在配置文件中加入,如下内容:

# 通用请求头内容
(secure_headers) {
  header {
    Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    X-Frame-Options SAMEORIGIN
    X-Content-Type-Options nosniff
  }
}
(common_headers) {
  encode gzip
}

TLS配置,进行DNS Challenge时,Caddy2 需要获得域名的控制权,在申请证书时增加一条TXT记录以供验证,并在验证完后删除。这里我是用的是 dnspod.cn,所以需要进行如下配置。其他服务提供商的使用方法请查阅 Caddy-Dns

# 泛域名证书申请,需要进行 DNS-01 challenge 所需内容
(tls) {
  tls {
    # 此处为 DNSPOD.CN 的配置方式,其他域名提供商有所不同,需要查阅文档。
    # TOKEN_ID 与 TOKEN 填写自己的
    dns dnspodcn TOKEN_ID TOKEN
  }
}

之后只需要在使用 Https 的网站配置中写入,import tls 即可应用 Dns Challenge。

Http 服务反向代理,因为Nas中服务较多,所以我是用泛域名反向代理这些服务(当有新服务产生时也不用更新Caddy2配置文件了),重点在于反向代理的时候需要将访客真实IP、访问方式等内容记录并回传。配置文件如下:

# 泛域名用于解析到 Nas 中的各种服务中,直接将 Web 服务转发到 Frps 的 Http 服务所在的 8080 端口上
*.algo.ren {
  import tls
  import common_headers
  import secure_headers
  reverse_proxy localhost:8080 {
    header_up X-Real-IP {http.request.remote}
    header_up X-Forwarded-For {http.request.remote}
    header_up X-Forwarded-Port {http.request.port}
    header_up X-Forwarded-Proto {http.request.scheme}
  }
}

最终配置文件的内容为:

{
  # 解除如下注释,用于测试证书申请(防止重复申请失败导致超过申请频率限制)
  # 在测试通过的生产环境中去除该项
  # acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}

# 通用请求头内容
(secure_headers) {
  header {
    Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    X-Frame-Options SAMEORIGIN
    X-Content-Type-Options nosniff
  }
}
(common_headers) {
  encode gzip
}

# 泛域名证书申请,需要进行 DNS-01 challenge 所需内容
(tls) {
  tls {
    # 此处为 DNSPOD.CN 的配置方式,其他域名提供商有所不同,需要查阅文档。
    # TOKEN_ID 与 TOKEN 填写自己的
    dns dnspodcn TOKEN_ID TOKEN
  }
}

##### Algo.ren #####
# 主域名暂时还没有内容,直接返回一行文本,并把 www 也重定向到主域名上。
algo.ren {
  import tls
  import common_headers
  import secure_headers
  respond "Algo.ren"
}
www.algo.ren {
  import tls
  import common_headers
  import secure_headers
  redir https://algo.ren{uri}
}

# 泛域名用于解析到 Nas 中的各种服务中,直接将 Web 服务转发到 Frps 的 Http 服务所在的 8080 端口上
*.algo.ren {
  import tls
  import common_headers
  import secure_headers
  reverse_proxy localhost:8080 {
    header_up X-Real-IP {http.request.remote}
    header_up X-Forwarded-For {http.request.remote}
    header_up X-Forwarded-Port {http.request.port}
    header_up X-Forwarded-Proto {http.request.scheme}
  }
}
5.写入服务配置文件

运行如下命令,创建系统服务的配置文件:

vi /etc/systemd/system/caddy.service

并写入如下内容(如果你的目录与我不一致则需要进行修改),否则直接写入即可:

[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target

[Service]
User=caddy
Group=caddy
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile
TimeoutStopSec=5s
LimitNOFILE=1048576
LimitNPROC=512
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

使用 systemctl start caddy.service 即可开启服务。
使用 systemctl enable caddy.service 即可让 Caddy2 开机启动。

Frp 配置

详见 群晖配置 FRP 进行内网穿透

其他注意事项

Nginx 获取访客真实 IP

我们只是将访客真实 IP 回传了,在 Nginx 中获取还需要进行一些操作,只需要在网站的Nginx配置文件中加入以下内容:

set_real_ip_from 0.0.0.0/0; 
real_ip_header X-Real-IP;
real_ip_recursive on;

这样设定 Nginx从 X-Real-IP 字段递归的获取真实IP。

WordPress 问题

资源无法 Https 化
这样操作之后,由于内部依旧是使用 Http 进行通信的,Wordpress好像傻傻的无法发现已经 Https On 了。
所以需要在 wp-config.php 中加入以下内容,判断并告诉 WordPress 已经是 Https 访问了。
Admin无法访问
启用了 SSL 的 WordPress 还需要加入如下的两个标记强制登录方式 Https 访问。

if (isset(_SERVER['HTTP_X_FORWARDED_PROTO']) &&_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') $_SERVER['HTTPS']='ON';

define('FORCE_SSL_LOGIN', true);
define('FORCE_SSL_ADMIN', true);

这些内容记得要放在

/** WordPress目录的绝对路径。 */
if ( ! defined( 'ABSPATH' ) ) {
    define( 'ABSPATH', dirname( __FILE__ ) . '/' );
}

之前。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇