docker部署vaultwarden+Caddy+FRP(TCP)+CF tunnel(HTTP)的记录

系统环境准备:

安装docker:

sh <(curl -sSL https://linuxmirrors.cn/docker.sh)


安装Caddy:

直接 apt install caddy ,或者 yum install caddy ,或者 dnf install caddy,这取决于你的系统。


Caddy配置:

先在 cloudflareDNS控制台记录上配置 CNAME域名映射(或者A/AAAA记录)到 FRP服务器的地址。

然后是 Caddy需要有 cloudflaredns模块,就能使用 cloudflareAPI_TOKEN自动创建更新非443标准端口的TLS证书。

将此 API_TOKEN替换到 Caddy配置文件内 FRP站点的 <CF_DNS_API_TOKEN>位置。

root@NAS:/etc/caddy# caddy  list-modules | grep cloudflare
dns.providers.cloudflare

然后是 Caddy需要有能解析 FRP(TCP)高级参数 proxy_protocol_version = v2的模块,就能通过 proxy_protocol获取访问者IP。

root@NAS:/etc/caddy# caddy  list-modules | grep proxy_
caddy.listeners.proxy_protocol
layer4.handlers.proxy_protocol
layer4.matchers.proxy_protocol

1、Caddy配置实现了从 FRP隧道来的访问转发到 vaultwarden容器,并获取真实访问者IP给 vaultwarden。但是禁止访问 /admin

2、Caddy配置实现了从 CF Tunnel隧道来的访问转发到到 vaultwarden容器,并获取真实访问者IP给 vaultwarden。可以访问 /admin,但是需要通过邮箱一次性验证码来验证。CF Tunnel自带 TLS,无需配置证书。

3、Caddy配置实现了来自本地的指定访问IP访问,使用自签证书。

Caddy安装模块问一下 AI或者到 Caddy官网 https://caddyserver.com/download下载,这里就不啰嗦了。

Caddy配置文件如下:


cat > /opt/caddy/Caddyfile <<"EOF"
{
        # FRP 站点解析 proxy_protocol_version = v2 参数,仅在1200端口(来自FRP的访问)的站点生效。
        servers :1200 {
                listener_wrappers {
                        proxy_protocol {
                                timeout 2s
                                allow 127.0.0.1/32
                        }
                        tls
                }
        }
}

# ------------------------------------------------------------
# 共享安全 Header
(shared_header) {
        header {
                # 启用 HTTP Strict Transport Security (HSTS)
                Strict-Transport-Security "max-age=31536000;"
                # 禁用 cross-site filter (XSS)
                X-XSS-Protection "0"
                # 禁止在框架内呈现网站 (clickjacking protection)
                # X-Frame-Options "SAMEORIGIN"
                # 阻止搜索引擎建立索引(可选)
                X-Robots-Tag "noindex, nofollow"
                # 禁止嗅探 X-Content-Type-Options
                X-Content-Type-Options "nosniff"
                # 服务器名称移除
                -Server
                # 移除 X-Powered-By,虽然这不应该是一个问题,但最好移除
                -X-Powered-By
                # 移除 Last-Modified,因为 etag 相同并且同样有效
                -Last-Modified
        }
}

# ------------------------------------------------------------
# FRP(TCP + Proxy Protocol v2)站点
vaultwarden.域名地址.xyz:1200 {
        encode gzip

        tls {
		# 根据实际情况更改此处的 <CF_DNS_API_TOKEN>
		dns cloudflare <CF_DNS_API_TOKEN>
                propagation_timeout 120s
                resolvers 1.1.1.1 1.0.0.1
        }

        handle /admin* {
                respond "小黑子你想干什么?!" 403
        }

        handle {
                import shared_header
                reverse_proxy 127.0.0.1:1199 {
                        header_up X-Real-IP {remote_host}
                }
        }
}

# ------------------------------------------------------------
# Cloudflare Tunnel 站点,这里有必要指明http协议,否则大概率会自签内部证书导致无法正常访问。
http://vaultwarden-cf.域名地址.xyz:1201 {
        encode gzip

        handle {
                import shared_header
                reverse_proxy 127.0.0.1:1199 {
                        trusted_proxies 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32
                        header_up X-Real-IP {http.request.header.CF-Connecting-IP}
                }
        }
}

#假设我的客户端设备局域网IP是10.126.126.1,vaultwarden服务器的IP是10.126.126.193,则可以如下配置,然后通过https://10.126.126.193:1202来访问vaultwarden服务。
#如果是通过组网的形式去访问,有些组网软件会默认NAT后去访问,就需要把vaultwarden的IP(10.126.126.193)也加到允许的列表中去
10.126.126.193:1202 {
        encode gzip
        tls internal

        # 1️⃣ 仅允许指定 IP
        @allow_ip {
                remote_ip 10.126.126.1/32 10.126.126.193/32
        }
        handle @allow_ip {
                reverse_proxy 127.0.0.1:1199 {
                        header_up X-Real-IP {remote_host}
                }
        }

        # 2️⃣ 其余来源全部拒绝
        handle {
                respond "Forbidden" 403
        }
}

EOF

然后标准化一下配置文件后载入配置文件到Caddy:

caddy fmt --overwrite /etc/caddy/Caddyfile && caddy reload --config /etc/caddy/Caddyfile

正常来说没错的话应该是这个情况

root@NAS:~# caddy fmt --overwrite /etc/caddy/Caddyfile && caddy reload --config /etc/caddy/Caddyfile
2026/02/04 08:03:57.713 INFO    using config from file  {"file": "/etc/caddy/Caddyfile"}
2026/02/04 08:03:57.744 INFO    adapted config to JSON  {"adapter": "caddyfile"}

root@NAS:~# ss -ntlp  | grep caddy
LISTEN 0      4096                     127.0.0.1:2019       0.0.0.0:*    users:(("caddy",pid=2830955,fd=24))           
LISTEN 0      4096                             *:80               *:*    users:(("caddy",pid=2830955,fd=31))           
LISTEN 0      4096                             *:1202             *:*    users:(("caddy",pid=2830955,fd=29))           
LISTEN 0      4096                             *:1200             *:*    users:(("caddy",pid=2830955,fd=30))           
LISTEN 0      4096                             *:1201             *:*    users:(("caddy",pid=2830955,fd=25))           


安装vaultwarden:

docker run --detach --name vaultwarden \
 -e DOMAIN='https://<用来访问的域名>:<用来访问的端口>' \
 -e TZ='Asia/Shanghai' \
 -p 127.0.0.1:1199:80 \
 -v /opt/vw-data:/data \
 --restart unless-stopped \
 vaultwarden/server:latest

这里主要是 FRP不能使用标准 443端口,否则 -e DOMAIN=参数不需要指定端口。

-e DOMAIN=这个参数用来自定义访问的地址,也可以不设置

-e DOMAIN= 在 vaultwarden 中只能有一个生效值,vaultwarden 本身不支持同时配置多个访问域名。

多域名时,可以配置其他域名 301 重定向 到这个主域名,但不是必要的

因为FRP如果不能访问了,我们要手动使用CF tunnel这个备用路径,所以可以不搞301重定向这一步

这个参数不是访问白名单,而是一个对外公布的主域名,主要用于:

1️⃣ 生成对外 URL

邀请邮件里的链接

Web Vault 中的一些跳转链接

API 返回的绝对 URL

2️⃣ Web Vault 的 CSP / WebSocket

WebSocket 连接地址

某些前端资源引用

-p 127.0.0.1:1199:80是将容器内的 80端口映射到本地 1199端口,仅监听在 127.0.0.1上。

-v /opt/vw-data:/data是将数据库文件挂载出来,方便后面备份或者迁移,即使容器删除,数据依旧存在。


配置FRP服务:

先创建一条TCP隧道,需要有高级参数 proxy_protocol_version = v2才能获取真实访问者IP:

image

然后去下载 frpc客户端程序到 /opt/Frp_linux_arm64/frpc,我使用的是某FRP服务网站提供的,也可自建。

使用下列命令创建systemd服务,以实现开机自启。

cat > /etc/systemd/system/frpc.service <<"EOF"
[Unit]
Description=Frp Client
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=root
Group=root

# 根据实际情况更改 <USER_TOKEN_KEY> 和 <SERVER_ID>
ExecStart=/opt/Frp_linux_arm64/frpc -u <USER_TOKEN_KEY> -p <SERVER_ID>
WorkingDirectory=/opt/Frp_linux_arm64/

Restart=always
RestartSec=5

# 日志优化(可选)
StandardOutput=journal
StandardError=journal

# 安全加固(可选)
ProtectSystem=full
PrivateTmp=true
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

EOF

服务文件创建好之后,刷新一下systemd,然后设置开机自启,并启动服务:

命令:systemctl daemon-reload && systemctl enable frpc && systemctl start frpc

如果手动指定,则 frpc的配置文件大致如下。

命令查看: cat /opt/Frp_linux_arm64/frpc.ini

[common]
server_addr = frp.server.com
server_port = 7000
tcp_mux = true
protocol = tcp
user = <USER_KEY> 
token = <TOKEN_KEY> 
dns_server = 223.6.6.6
tls_enable = false
[vaultwarden]
privilege_mode = true
type = tcp
local_ip = 127.0.0.1
local_port = 1200
remote_port = 32213
use_encryption = false
use_compression = false
proxy_protocol_version = v2

大致意思就是

用TCP协议 type = tcp,将本地 local_ip = 127.0.0.1local_port = 1200端口,映射到FRP服务器的 remote_port = 32213端口

后面就可以通过FRP服务器的 https://<用来访问的域名>:32213进行访问。


配置CF Tunnel服务:

https://one.dash.cloudflare.com/控制台的【网络】-【连接器】中,创建一个【隧道】,在【概述】里有部署方式,推荐使用docker的方式部署,命令参考:
docker run -d --network host --name cfd_tunnel cloudflare/cloudflared:latest tunnel run --token <你的隧道token>

然后到【隧道】的配置里,增加增加一个【已发布应用程序路由】,【子域】部分填 vaultwarden-cf,就是Caddy配置文件里的CF tunnel站点。【域】就是你的域名地址。【路径】为空。服务类型为http,对应Caddy配置文件中的 http://vaultwarden-cf.域名地址.xyz:1201 站点块。【URL】填 127.0.0.1:1201,将来自 CF Tunnel的数据转发到 Caddy1201端口。
image

配置 /admin控制台访问的邮件令牌验证:

https://one.dash.cloudflare.com/控制台的【访问控制】中增加一个【策略】,名称自定义,例如叫【允许列表内邮件验证码认证访问】,【操作】选 Allow,【会话持续时间】自定义,添加一个规则,【选择器】为 Emails,【值】则是自己的邮箱,这个邮箱将用来接受验证码,通过验证就能访问 /admin

image

将策略应用:

https://one.dash.cloudflare.com/控制台的【访问控制】中增加一个【应用程序】,名称自定义,例如叫【管理员控制台】,【会话持续时间】自定义,默认的输入方法,【子域】填 vaultwarden-cf,对应Caddy配置文件中的 http://vaultwarden-cf.域名地址.xyz:1201 站点块。【域】就是你的域名地址。【路径】则是 admin,这里很重要,无需加 /,直接填 admin

image

验证:

当去访问 http://vaultwarden-cf.域名地址.xyz/admin的时候,就会自动跳转到验证。

image

输入策略里存在的邮箱,就会收到类似于以下的验证码,验证后就能访问 /admin控制台了。

image


开启vaultwarden的admin控制台:

接下来关闭注册功能,然后开启vaultwarden的邮件功能,以实现邀请用户。

使用 openssl rand -base64 48命令创建 admin访问的 token它看起来像这样:yi7GmMLGWaTo5OJdmALHL4eajv+rh3G7eVtaXJCxylvcF6+B7vbhh0HWACZdeAQl

此token需要牢记,登录admin后台使用。

然后将此明文的token转成密文,利用vaultwarden容器自带的hash工具,目前vaultwarden容器已经在正常运行了,那么执行 docker exec -it vaultwarden /vaultwarden hash命令,然后隐性输入token两次(建议复制粘贴),若两次输输入token不一致则不会生成密文。看起来会像这样:

Generate an Argon2id PHC string using the 'bitwarden' preset:

Password:            <-----------隐性输入
Confirm Password:            <-----------隐性输入

ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$RtC7dEU7RacyN6v0oUM2nVu9f3rI9OjaFA1S2sWXJLg$4j0HHDGop+71A2dxtco7JuO7UEV2js4wgojPsiETeU8'            <-----------密文生成成功

Generation of the Argon2id PHC string took: 142.1447ms

重建容器,开启admin管理页面(启用变量 -e ADMIN_TOKEN=)带入密文 token,然后关闭注册功能(启用变量 -e SIGNUPS_ALLOWED=false),

docker stop vaultwarden && docker rm vaultwarden && docker run --detach --name vaultwarden \
 -e SIGNUPS_ALLOWED=false  \
 -e DOMAIN='https://<用来访问的域名>:<用来访问的端口>' \
 -e ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$RtC7dEU7RacyN6v0oUM2nVu9f3rI9OjaFA1S2sWXJLg$4j0HHDGop+71A2dxtco7JuO7UEV2js4wgojPsiETeU8' \
 -e TZ='Asia/Shanghai' \
 -v /opt/vw-data:/data \
 -p 127.0.0.1:1199:80 \
 --restart unless-stopped \
 vaultwarden/server:latest

然后去访问 http://vaultwarden-cf.域名地址.xyz/admin就能进admin后台了。


开启SMTP邮件服务(以163为例):

添加一点SMTP参数,参考:

docker stop vaultwarden && docker rm vaultwarden && docker run --detach --name vaultwarden \
 -e SIGNUPS_ALLOWED='false' \
 -e DOMAIN='https://<用来访问的域名>:<用来访问的端口>' \
 -e ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$RtC7dEU7RacyN6v0oUM2nVu9f3rI9OjaFA1S2sWXJLg$4j0HHDGop+71A2dxtco7JuO7UEV2js4wgojPsiETeU8' \
 -e TZ='Asia/Shanghai' \
 -e ADMIN_SESSION_LIFETIME='60' \
 -e SMTP_FROM='邮箱账号@163.com' \
 -e SMTP_HOST='smtp.163.com' \
 -e SMTP_PORT=465 \
 -e SMTP_SECURITY=force_tls \
 -e SMTP_USERNAME='邮箱账号@163.com' \
 -e SMTP_PASSWORD='POP3/SMTP密码' \
 -e HELO_NAME='vaultwarden-selfhosted' \
 -p 127.0.0.1:1199:80 \
 -v /opt/vw-data:/data \
 --restart unless-stopped \
 vaultwarden/server:latest
posted @ 2026-02-04 17:42  Ojox  阅读(4)  评论(0)    收藏  举报