趁现在自己还记忆犹新,赶紧记录下搭建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证书
选择自签名IP的ca.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
[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
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的网页管理端进行使用。