趁现在自己还记忆犹新,赶紧记录下搭建Vaultwarden的过程。

安全加固措施

  • MySQL替换默认SqLite数据库,并配置异地主从数据库备份
  • 在Nginx配置location / {return 403;}屏蔽WEB访问,只保留必要配置
  • 启用OpenVpn+SSL自签证书

服务器部署 Vaultwarden

搭建开源Vaultwarden

注意:如果在同一个Docker中运行MySQL数据库,并运行vaultwarden容器,则需要将MySQL容器加入到docker-custom-network(示例名称:docker-custom-network))网络中。

docker network create docker-custom-network

通过Docker容器安装MySQL

--network docker-custom-network 这样Mysql容器就在Docker构建的网络中。

# 当前命令基于 MySQL 8.0.0 
docker run -d -p 3306:3306 --network docker-custom-network -v /home/docker/mysql/mysql_8.0/data:/var/lib/mysql -v /home/docker/mysql/mysql_8.0/conf/my.cnf:/etc/mysql/conf.d/my.cnf -e MYSQL_ROOT_PASSWORD=123456 --restart=always --privileged=true --name mysql_8.0 mysql:8.0.0 --lower_case_table_names=1

通过Docker容器安装vaultwarden

当前容器配置数据库为示例,如果需要使用Docker中的数据库,则将--network docker-custom-network添加其中,并且修改-e DATABASE_URL=mysql://user:password@127.0.0.1:8888/database-e DATABASE_URL=mysql://user:password@mysql_8.0:8888/database,修改ip地址为MySQL的容器名。

docker run -d \
--rm \
--name vaultwarden \
-p 8787:80 \
-p 3012:3012 \
-v /home/docker/vaultwarden:/data \
-e DATABASE_URL=mysql://user:password@127.0.0.1:8888/database \
-e SIGNUPS_ALLOWED=false \
-e WEBSOCKET_ENABLED=true \
-e INVITATIONS_ALLOWED=false \
-e ADMIN_TOKEN=your_admin_token \
-e DOMAIN=https://your_url.com \
vaultwarden/server:latest

80 web访问端口

3012 webscoket端口

/home/docker/vaultwarden建立目录用于容器外映射配置文件

  • -d 后台运行
  • --rm 当容器停止时自动删除容器
  • -e DATABASE_URL 配置数据源,默认SqLite(可选)
  • -e SIGNUPS_ALLOWED 允许用户注册(首次用户注册后,删除容器,指定SIGNUPS_ALLOWED=false重新配置容器)
  • -e WEBSOCKET_ENABLED 开启webscoket
  • -e INVITATIONS_ALLOWED 允许邀请用户注册
  • -e ADMIN_TOKEN 管理员令牌
  • -e DOMAIN 配置访问域名(需要是https://xxx.com域名,不能配置ip地址),

切换MySQL数据库

在docker配置容器命令添加:

示例配置:DATABASE_URL=mysql://[[user]:[password]@]host[:port][/database]

# 上述docker命令已包括
-e DATABASE_URL=mysql://vaultwarden_user:strong_password@11.1.0.9:8888/vaultwarden

配置私有IP自签名证书

注意:低版本的openssl执行会失败

11.1.0.1是我自签名SSL证书的的IP地址,执行命令得到:ca.crt证书、 ca.key密钥

openssl req -x509 -newkey rsa:2048 -sha256 -days 3650 -nodes \
-keyout ca.key -out ca.crt -subj "/CN=11.1.0.1" \
-addext "subjectAltName=IP:11.1.0.1"

由于是使用的自签名证书,需要导入浏览器、安卓手机才能正常访问


基于Ubuntu下,配置自签名SSL证书并解决浏览器访问提示不安全告警

详情点击下文内容,查看完整信息

配置openVPN

openvpn加固:

  • ca.crt 根证书
  • x.crt 签发证书
  • x.key 签发证书密钥
  • 用户名+密码

openvpn导入连接

在移动端导入openvpn配置,需要将.ovpn文件和依赖的根证书ca.crt、自签证书.crt.key一并选择进行导入


附录

安卓APP相关软件

安卓手机app软件(谷歌商店)

  • OpenVpn Connect (3.7.1) :开启OpenVPN连接
  • Bitwarden (2025.06.1):密码管理软件
  • Bitwarden Authenticator (2025.6.0) 密码管理软件-两步验证工具

手机导入自签名证书

小米14的证书安装路径如下:

设置->隐私与安全->安全->更多安全设置->更多安全设置->加密与凭据->安装证书->CA证书

选择自签名IPca.crt证书安装到手机即可。

Nginx配置

server {
listen 8443 ssl;
# 监听ovpn地址
server_name 11.1.0.1;
# 自签名证书
ssl_certificate /usr/local/nginx/conf/cert/vpn/11.1.0.1.crt;
ssl_certificate_key /usr/local/nginx/conf/cert/vpn/11.1.0.1.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

# 仅允许openvpn下IP地址访问
allow 11.1.0.0/24;
deny all;


# 屏蔽web端访问
location /{
return 403;
}

# 允许 API 访问(确保客户端仍能同步)
location /api/ {
proxy_pass http://127.0.0.1:8787;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

# 登录接口(web、pc、浏览器插件、app依赖)
location /identity/ {
proxy_pass http://127.0.0.1:8787;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

# 允许 WebSocket(用于实时同步)
location /notifications/hub {
proxy_pass http://127.0.0.1:3012/notifications/hub;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

# 其他
location /notifications/hub/negotiate {
proxy_pass http://127.0.0.1:8787;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

启用MTLS双向认证

配置双向认证后,在公网环境也可以

基于CentOS 7.8环境

特别注意:当前基于基于CentOS 7.8环境的内容可能已过时!

环境信息:CentOS Linux release 7.8.2003 (Core) 、OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017

创建CA根证书(证书颁发机构)

# 创建CA私钥
openssl genrsa -out ca.key 2048

# 创建CA根证书(有效期10年)
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
# 需要填写国家、组织等信息

创建客户端证书

# 创建客户端私钥
openssl genrsa -out client.key 2048

# 创建证书签名请求(CSR)
openssl req -new -key client.key -out client.csr
# 需要填写与CA证书类似的信息

# 使用CA签署客户端证书(有效期1年)
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt

# 将客户端证书转换为PKCS12格式(方便浏览器导入)
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
# 需要设置导出密码

基于Ubuntu 24.0.0 环境

环境信息:Ubuntu 24.0.0 、OpenSSH_9.6p1 Ubuntu-3ubuntu13.15, OpenSSL 3.0.13 30 Jan 2024

注意事项:请参考下文自签名证书不受信任的解决方案,此处仅适用于Mtls证书。 此处的根CA配置不适用于签发自签名SSL证书。

# 1. 创建证书目录
mkdir -p /home/mtls
cd /home/mtls

# 2. 生成 CA 根证书(有效期10年)
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/CN=MyMTLS_CA/C=CN/ST=BJ/L=Beijing/O=MyCompany/OU=IT"

# 3. 生成服务器端证书(如果你需要)
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -subj "/CN=your-domain.com/C=CN/ST=BJ/L=Beijing/O=MyCompany/OU=IT"
openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt

# 4. 生成客户端证书(有效期10年)
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/CN=wo0ow/C=CN/ST=NJ/L=NJ/O=wo0ow/OU=wo0ow"
openssl x509 -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt

# 5. 创建浏览器用的 P12 证书(密码设为:123456)
openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt -certfile ca.crt -passout pass:123456

# 6. 设置正确的文件权限
chmod 644 *.crt
chmod 600 *.key

# 7. 查看生成的文件
ls -lh /home/mtls/


自签名证书不受信任的解决方案

参考博客:自签名证书不受信任的解决方案

自签名证书

生成根证书CA.crt和根证书私钥CA.key

# 生成根证书CA.crt和根证书私钥CA.key,证书主体描述在-subj参数中修改
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
-subj "/CN=MUMU_CA/C=CN/ST=JS/L=NanJing/O=Wo0oW" \
-keyout CA.key -out CA.crt \
-addext "basicConstraints=critical,CA:TRUE"

创建一个应用证书

# 创建证书的私钥
openssl genrsa -out private.key 2048

# 根据私钥创建一个证书请求文件csr,注意证书主体的描述使用-subj参数描述,CN必须和应用中请求的地址一致,可以是IP地址或域名
openssl req -new -key private.key \
-subj "/C=CN/ST=JS/L=NanJing/O=Wo0oW/CN=19.7.0.1" \
-sha256 -out private.csr

创建证书的扩展描述文件private.ext

不使用扩展描述文件,那么谷歌浏览器中无法授信,会提示证书无效

[ req ]
default_bits = 1024
distinguished_name = req_distinguished_name
req_extensions = san
extensions = san
[ req_distinguished_name ]
countryName = CN
stateOrProvinceName = Definesys
localityName = Definesys
organizationName = Definesys
[SAN]
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
# csr文件中CN配置的是域名,这里也写域名;反之IP。
# 配置域名
#subjectAltName = DNS:www.test.com
# 配置IP
subjectAltName = IP:19.7.0.1

使用csr文件和private.ext、以及根证书CA.crt创建一个证书private.crt

openssl x509 -req -days 364 -in private.csr \
-CA CA.crt \
-CAkey CA.key \
-CAcreateserial -sha256 -out private.crt \
-extfile private.ext \
-extensions SAN

安装根CA的证书后,当通过访问自签名ip:443端口时,浏览器不会抛出不安全警告信息提示。

PC端

双击CA.crt证书文件,选择受信任根证书颁发机构存储区

安卓手机

点击CA证书,选择签发CA.crt证书文件进行安装。

自签名Mtls证书

生成客户端私钥

openssl genrsa -out client.key 2048

生成客户端证书请求文件 (CSR)

mTLS 客户端证书的 CN 通常用于标识客户端身份(如 client、user1 或具体的服务名),不必是 IP 或域名。

openssl req -new -key client.key \
-subj "/C=CN/ST=JS/L=NanJing/O=Wo0oW/CN=client" \
-sha256 -out client.csr

创建客户端证书扩展文件 client.ext

cat > client.ext <<EOF
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = client_cert
extensions = client_cert

[ req_distinguished_name ]
countryName = CN
stateOrProvinceName = JS
localityName = NanJing
organizationName = Wo0oW

[ client_cert ]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
# 标识这是客户端证书,用于 mTLS 客户端验证
extendedKeyUsage = clientAuth
authorityKeyIdentifier = keyid,issuer
# 如果需要用 IP 或域名区分多个客户端,同上改 IP:xx.xx.xx.xx
subjectAltName = DNS:client
EOF

使用根证书签发客户端证书

openssl x509 -req -days 3650 -in client.csr \
-CA CA.crt \
-CAkey CA.key \
-CAcreateserial -sha256 -out client.crt \
-extfile client.ext \
-extensions client_cert

验证客户端证书 (可选)

# 查看证书内容
openssl x509 -in client.crt -text -noout

# 验证证书链
openssl verify -CAfile CA.crt client.crt

生成p12证书文件(包含签发的客户端公钥、私钥)

openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt -certfile CA.crt -passout pass:123456
PC端

双击p12证书文件,输入密码123456,选择个人存储区

安卓手机

如果使用Bitwarden的移动端APP,直接在APP中底部配置环境信息,导入当前P12证书即可。


nginx配置


server {
listen 9003 ssl;
server_name 11.1.0.9;

# 标准SSL配置
ssl_certificate /usr/local/nginx/conf/cert/11.1.0.9/ca.crt;
ssl_certificate_key /usr/local/nginx/conf/cert/11.1.0.9/ca.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
#ssl_ciphers HIGH:!aNULL:!MD5;

# 启用客户端证书验证
ssl_client_certificate /usr/local/nginx/conf/cert/CA/ca.crt;
ssl_verify_client on;
ssl_verify_depth 2;
# 调整加密套件
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305';

location /{
root /usr/local/nginx/html/snowyweb/dist;
try_files $uri $uri/ /index.html;
index index.html index.htm;
}
}

排查mtls证书错误信息

因为服务器从Centos迁移到Ubuntu,原博客中关于mtls的生成命令就不可用了,生成的证书验证失败(这里我卡了半天)。

# 临时配置 - 仅用于测试!
ssl_verify_client optional_no_ca; # 不验证 CA,只检查是否有证书

location / {
return 200 "Has Certificate: $ssl_client_verify\nSubject: $ssl_client_s_dn\nCert: $ssl_client_cert";
}

验证mtls证书通过截图

服务器迁移 Vaultwarden

MySQL 数据库备份迁移

如果数据库不需要迁移,则请忽略当前 MySQL 数据库备份迁移 步骤

导出类似vaultwarden.sql的文件,需要包 表结构+数据

我在服务器上通过docker构建的mysql数据库,例如当前我需要导出vaultwarden数据库结构和数据:

查看docker启动中的镜像,并找到对应mysql的镜像ID

docker ps ;
[root@manman ~]# docker ps;
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
420e8c5f5fc4 mysql "docker-entrypoint.s…" 2 years ago Up 7 weeks 33060/tcp, 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp mysql_8_0

进入docker镜像对应的命令

docker exec -it 420e8c5f5fc4 bash

导出需要备份的数据库sql文件到当前目录下

点击 ↩ 后,会要求输入密码︎

#mysqldump -u [用户名] -p [数据库名] > 导出的文件名.sql
mysqldump -u root -p vaultwarden > backup_vaultwarden.sql

复制文件到宿主机目录下

#docker cp [容器名或ID]:容器内路径 宿主机路径
docker cp 420e8c5f5fc4:/backup_vaultwarden.sql ./

在另一台Docker的宿主机上执行,复制sql到MySQL容器内
docker cp [宿主机文件路径] [容器名或ID]:容器内路径
docker cp backup_vaultwarden.sql 420e8c5f5fc4:/

下载到本地

# ubuntu下需要安装 `sudo apt install lrzsz -y`
sz backup_vaultwarden.sql

Data 数据文件目录迁移

docker run -d \
--name vaultwarden \
...
-v /home/docker/vaultwarden:/data \ ## 需要迁移的文件目录在 /home/docker/ 下
...
vaultwarden/server:latest
cd /home/docker
tar -czvf vaultwarden.tar.gz vaultwarden/

需要先安装docker环境,并拉取镜像文件

docker pull vaultwarden/server:latest

本地启动镜像服务

docker run -d \
--name vaultwarden \
-p [WEB_PORT]:80 \
-p [SCOKET_PORT]:3012 \
-v [FILEPATH]:/data \
-e DATABASE_URL=mysql://[USERNAME]:[PASSWORD]@[IP:PORT]/[DBNAME] \
-e SIGNUPS_ALLOWED=false \
-e WEBSOCKET_ENABLED=true \
-e INVITATIONS_ALLOWED=false \
-e ADMIN_TOKEN=[ADMIN_TOKEN] \
-e DOMAIN=[DOMAIN] \
vaultwarden/server:latest
  • [WEB_PORT] WEB服务端口
  • [SCOKET_PORT] SCOKET端口
  • [FILEPATH] 宿主机文件目录,我的是 /home/docker/vaultwarden
  • [USERNAME],[PASSWORD],[IP:PORT],[DBNAME] 分别对应的数据库用户名,密码,连接ip地址及端口,数据库名
  • [ADMIN_TOKEN]管理员令牌
  • [DOMAIN] 部署域名,我的是 https://bitwarden.wo0ow.com

配置Nginx并设置SSL证书

修改host文件,win的修改路径 C:\Windows\System32\drivers\etc\hosts

127.0.0.1 bitwarden.wo0ow.com

nginx-ssl的配置参考

    server {
listen 443 ssl;
server_name bitwarden.wo0ow.com;
ssl_certificate D:\\Env\\Nginx\\nginx-1.26.2\\conf\\cert\\cert.crt;
ssl_certificate_key D:\\Env\\Nginx\\nginx-1.26.2\\conf\\cert\\cert.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
#ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

# 启用客户端证书验证
# ssl_client_certificate /usr/local/nginx/conf/cert/CA/ca.crt;
# ssl_verify_client on;
# ssl_verify_depth 2;
# 强制浏览器发送证书
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-CHACHA20-POLY1305';

#偶尔需要的时候在放开deny all,目前就通过openVPN进行访问安全性第一
#allow 222.94.93.108; #jmt-work 动态ip,可能下次又变其他ip
#allow 11.1.0.0/24;
#deny all;

# 静态资源缓存配置
location ~* \.(js|css|png|jpg|jpeg|gif|ico|woff2|svg)$ {
proxy_pass http://127.0.0.1:8787; # 指向 Vaultwarden 容器端口
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 缓存设置
expires 365d; # 缓存有效期 1 年
add_header Cache-Control "public, immutable";
proxy_hide_header Cache-Control; # 覆盖后端返回的缓存头
}

# 屏蔽web端访问
#location /{
# return 403;
#}


# 允许 API 访问(确保客户端仍能同步)
#location /api/ { # 只需要访问api,就使用这个配置行
location / { # 需要访问web端,打开这个注释

# 在nginx.conf配置允许国家
# if ($allowed_country = no) {
# return 403;
# }

# 自签mtls证书校验
# if ($ssl_client_verify != SUCCESS) {
# return 403;
# }

proxy_pass http://127.0.0.1:8787;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

# 登录接口(web、pc、浏览器插件、app依赖)
location /identity/ {
proxy_pass http://127.0.0.1:8787;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

# 允许 WebSocket(用于实时同步)
location /notifications/hub {
proxy_pass http://127.0.0.1:3012/notifications/hub;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

# 其他
location /notifications/hub/negotiate {
proxy_pass http://127.0.0.1:8787;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

# 495 是 Nginx 定义的错误代码,表示 "SSL Certificate Error"
# 496 表示 "SSL Certificate Required"
#error_page 495 496 =403 @empty;
location @empty {
return 403;
}

}

可以通过浏览器安装 bitwarden 插件进行连接,或者是通过bitwarden的网页管理端进行使用。