Last Updated on December 27, 2022 by Thiago Crepaldi
In crazy times as today’s, having strong and unique passwords are a must to handle threats from Internet. However, keeping track of such passwords is very hard, and this is where password managers come in. There are several password managers out there, but I really enjoy Bitwarden. If you don’t know them, I urge you to do your research it; they are great, open source and audited by experts. They have a “personal free forever” plan which is enough for most beginners along with inexpensive plans for all kinds of users, personal or business.
In this post, we are going to deploy on-premise (aka self-host) an alternative Bitwarden implementation called Vaultwarden. All the Bitwarden’s original desktop, mobile and browser clients are compatible with Vaultwarden server, so migration is a breeze. Vaultwarden implements most of the premium features, which is a great bonus.
Regardless of having all these goodies for free using Vaultwarden, I would recommend showing some love to Bitwarden’s project and pay a personal premium license to help them keep up the great work and add more features to the project! They certainly deserve it!
Configuring the Proxmox LXC container
I am assuming you know how to create a container on your Proxmox, and from now on, we are going to refer to it as vault.lan.mydomain.com within your local network and vault.mydomain.com as the public (aka Internet) address. The IP will be assumed to be 10.20.30.40
Requirements
- The minimum requirements are 1.4GHz x64 CPU, 2 GB of RAM and 12 GB of storage, while the recommended configuration is 2 GHz x64 CPU, 4GB of RAM and 25 GB of storage.
- I’ve tested these steps in both Debian 11.6 and Ubuntu 22.10, but will focus on Debian in the next steps.
- Lastly, we will also need to setup SMTP in both Linux server (this is optional) and on Vaultwarden service. Vaultwarden may send users emails with invitations for organizations or for two factor authentication, etc
Configuring SMTP on your Linux using postfix and a Gmail account
I have created a Gmail account to serve as an emailer on all my servers/services. It is always a good idea not to use your personal account for such purpose as it will not expose personal data in case of a server breach. Follow the how to configure postfix on your Linux to send emails and resume from here when you are done.
Installing Vaultwarden
The vaultwarden will be installed at /opt/vaultwarden through the steps below. In fact, we are going to compile the server service and then the web-portal component. The latter is the UI for the former. However, before installing, or compiling them from source, we will install their dependencies and do some prep on the Linux system.
Server component
Note that all commands below are executed as root, so you may need to add “sudo” on them if you prefer that way. You can refer to vaultwarden’s original wiki for full steps, including the variants on how to use MySQL or PostgreSQL as database, but the steps below are enough to get things going using SQLite3 as database.
# apt update && apt install -y curl git wget # basic tools
# curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # latest rust, not from OS package manager
# source ~/.cargo/env # Reload terminal' with Rusts new 's environment
# curl -fsSL https://deb.nodesource.com/setup_19.x | bash - && apt-get install -y nodejs # latest nodejs, not from OS package manager
# apt install -y build-essential pkg-config libssl-dev # basic vaultwarden deps
# apt install -y libsqlite3-dev sqlite3 # database-specific
# mkdir -p /opt/ && cd /opt/ # installing at /opt/vaultwarden
# git clone --recursive https://github.com/dani-garcia/vaultwarden.git # vaultwarden
# cd vaultwarden
# git tag # list all stable versions
# git checkout 1.27.0 # or whatever latest version they have
# cargo build --features sqlite --release # build vaultwarden with sqlite database
# ln -s /opt/vaultwarden/target/release/vaultwarden /opt/vaultwarden/
At this point, vaultwarden will be compiled at /root/vaultwarden/target/release/vaultwarden.
Web-Portal client
In this section we are going to compile the web-portal component that allows you to browse and manage your account by visiting vault.mydomain.com.
# export NODE_OPTIONS="--max-old-space-size=2048" # bw_web_builds build may fail without that
# cd /opt
# git clone https://github.com/dani-garcia/bw_web_builds.git # web-portal code
# cd bw_web_builds
# git tag
# make full # type 'v2022.12.0' or a newer tag
# ln -s /opt/bw_web_builds/web-vault/apps/web/build/ /opt/vaultwarden/web-vault
Putting everything together
At this point, you have both vaultwarden and web portal compiled and ready for deployment. This section will put everything together by adding configuration files and startup scripts to get things up and running
Configuring the Vaultwarden environment
There is a template file at /opt/vaultwarden/.env.template that you could use to configure things your way. Copy it and and modify accordingly:
# cp /opt/vaultwarden/.env.template /opt/vaultwarden/vaultwarden.env
The minimal variables to be able to get the service up is:
DATABASE_URL=data/db.sqlite3
WEBSOCKET_ENABLED=true
DOMAIN=https://vault.mydomain.com
ROCKET_PORT=80
but you must also configure SMTP to enable email notifications. If you are using Gmail, you can use something like the snippet below
SMTP_HOST=smtp.gmail.com
SMTP_FROM=emailer@thiagocrepaldi.com
SMTP_FROM_NAME=Vaultwarden at vault.tfc.pw
SMTP_SECURITY=starttls
SMTP_PORT=587
SMTP_USERNAME=username@gmail.com
SMTP_PASSWORD=my_gmail_app_password_here
SMTP_TIMEOUT=15
SMTP_AUTH_MECHANISM="Plain"
Although the settings above are a great start, be curious and read the whole template file and see what else you may want to enable/disable.
This will work
SMTP_HOST=smtp.gmail.com
This will not
SMTP_HOST=smtp.gmail.com # my SMTP server
Creating startup scripts
With everything we did so far, vaultwarden is ready to fly. However, we are going to create a System D service script to manage (e.g. start/stop/restart or get status of ) the service. Create the /etc/systemd/system/vaultwarden.service as root and paste the following:
Note that we are setting the 1) paths, 2) environment file, 3) database and 4) user/group in this file. Adjust accordingly.
[Unit]
Description=Bitwarden Server (Rust Edition)
Documentation=https://github.com/dani-garcia/vaultwarden
# If you use a database like mariadb,mysql or postgresql,
# you have to add them like the following and uncomment them
# by removing the `# ` before it. This makes sure that your
# database server is started before vaultwarden ("After") and has
# started successfully before starting vaultwarden ("Requires").
# Only sqlite
After=network.target
# MariaDB
# After=network.target mariadb.service
# Requires=mariadb.service
# Mysql
# After=network.target mysqld.service
# Requires=mysqld.service
# PostgreSQL
# After=network.target postgresql.service
# Requires=postgresql.service
[Service]
# The user/group vaultwarden is run under. the working directory (see below) should allow write and read access to this user/group
User=vaultwarden
Group=vaultwarden
# Use an environment file for configuration.
EnvironmentFile=/opt/vaultwarden/vaultwarden.env
# The location of the compiled binary
ExecStart=/opt/vaultwarden/vaultwarden
# Set reasonable connection and process limits
LimitNOFILE=1048576
LimitNPROC=64
# Isolate vaultwarden from the rest of the system
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
# Only allow writes to the following directory and set it to the working directory (user and password data are stored here)
WorkingDirectory=/opt/vaultwarden
ReadWriteDirectories=/opt/vaultwarden
# Allow vaultwarden to bind ports in the range of 0-1024
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
Lastly, run “systemctl daemon-reload” to let the system know about the new kid on the block.
Adding vaultwarden user
So far we have used root for everything, but this is not safe for production. Let’s add a user and group called vaultwarden to run the service:
# /usr/sbin/adduser --disabled-password vaultwarden
# /usr/sbin/adduser vaultwarden vaultwarden
# mkdir /opt/vaultwarden/data
# chmod -R 700 /opt/vaultwarden
# chown -R vaultwarden:vaultwarden /opt/vaultwarden
Starting the service
Finally we can kick off the service and move on. Just run “systemctl start vaultwarden.service” to fire up the service. You can run “systemctl status vaultwarden.service” to see if everything is good. The output should be something like:
* vaultwarden.service - Bitwarden Server (Rust Edition)
Loaded: loaded (/etc/systemd/system/vaultwarden.service; disabled; vendor preset: enabled)
Active: active (running) since Tue 2022-12-27 05:09:15 UTC; 21min ago
Docs: https://github.com/dani-garcia/vaultwarden
Main PID: 2802 (vaultwarden)
Tasks: 16 (limit: 38104)
Memory: 7.3M
CPU: 373ms
CGroup: /system.slice/vaultwarden.service
`-2802 /opt/vaultwarden/vaultwarden
If the service is not “active (running)”, look for logs either at /var/log/syslog or whatever you pointed “LOG_FILE” variable. Otherwise, you should be able to visit http://vault.lan.mydomain.com and see the initial page of Vaultwarden asking for credentials.
Configuring pfSense
Now that the LXC container is running, we need to expose it to Internet using pfSense. In this section, we are going to 1) generate a Let’s Encrypt SSL certificate, 2) Create a NAT rule to expose ports 80/443 to Internet and 3) Expose the Vaultwarden service to Internet using HAProxy reverse proxy.
Issuing a SSL certificate
Using a SSL certificate is a must for a password service, but don’t worry, I’ve posted about how to issue Let’s Encrypt certificates on your pfSense using ACME service already. Follow it and resume from here when you are done.
Adding Vaultwarden to HAProxy
The Vaultwarden server must be accessible on the Internet (or only on your local network) to allow the clients (e.g. Firefox browser extension) to connect to it and sync/fetch your passwords. We are going to use HAProxy on pfSense to expose an local-only vaultwarden.lan.mydomain.com server to the Internet at vault.domain.com.
To make things work, you will need follow How to host multiple domains using HAProxy as reverse proxy on pfSense adding some specific setting that are hilighted below
Backends
First, you will need to create 2 backends while you are following the HAProxy post, one for Vaultwarden itself and another for push notifications to work:
Go to Services >> HAProxy >> Backend and click on Add:
- Edit HAProxy Backend server pool
- Name: vault.mydomain.com (note this is the public FQDN)
- Server list: (click on down arrow to add an entry to the table)
- Mode: Active
- Name: vault.mydomain.com (note this is the public FQDN)
- Forward to: Address+Port
- Address: vault.lan.mydomain.com (or whatever internal IP/FQDN your Vaultwarden has)
- Leave all the SSL settings unchecked/empty/unchanged
- Load balancing options (when multiple servers are defined): empty
- Access control lists and actions: empty
- Timeout / retry settings: empty
- Health checking:
- Health check method: Basic
- Any other method will generate misleading/harmless error logs
- Leave all other options unchecked/empty/unchanged
- Health check method: Basic
- Agent checks: empty
- Cookie persistence: empty
- Stick-table persistence: empty
- Email notifications: empty
- Statistics: empty
- Error files: empty
- HSTS / Cookie protection: empty
- Advanced settings: empty
Click on Save and Apply changes to finish your first backend.
Repeat the step above one more time for the notification backend, but now using Name “vault.lan.mydomain.com-notifications“, Port 3012 and Health Check Method as None; the rest must be identical to the previous backend.
Frontends
When you get to the frontend configuration, we are going to need 6 ACLs and 5 Actions to the HTTPS_443 frontend. Let’s start with the ACLs by:
- Default backend, access control lists and actions
- Access Control lists: click down arrow to add one entry
- Name: vault.mydomain.com-domain
- Expression: Host matches
- CS: unchecked
- Not: unchecked
- Value: vault.mydomain.com
- Access Control lists: click down arrow to add one entry
- Name: vault.mydomain.com-acl1
- Expression: Path starts with
- CS: unchecked
- Not: checked
- Value: /notifications/hub
- Access Control lists: click down arrow to add one entry
- Name: vault.mydomain.com-acl2
- Expression: Path starts with
- CS: unchecked
- Not: unchecked
- Value: /notifications/hub/negotiate
- Access Control lists: click down arrow to add one entry
- Name: vault.mydomain.com-acl3
- Expression: Path starts with
- CS: unchecked
- Not: unchecked
- Value: /notifications/hub
- Access Control lists: click down arrow to add one entry
- Name: vault.mydomain.com-acl4
- Expression: Path starts with
- CS: unchecked
- Not: checked
- Value: /notifications/hub/negotiate
- Access Control lists: click down arrow to add one entry
- Name: vault.mydomain.com-admin
- Expression: Path starts with
- CS: unchecked
- Not: unchecked
- Value: /admin
- Access Control lists: click down arrow to add one entry
Now we have to add five actions:
- Default backend, access control lists and actions
- Actions: click down arrow to add an entry
- Action: Use backend
- Condition acl names: vault.mydomain.com-domain vault.mydomain.com-acl1
- Backend: vault.mydomain.com
- Actions: click down arrow to add an entry
- Default backend, access control lists and actions
- Actions: click down arrow to add an entry
- Action: Use backend
- Condition acl names: vault.mydomain.com-domain vault.mydomain.com-acl2
- Backend: vault.mydomain.com
- Actions: click down arrow to add an entry
- Default backend, access control lists and actions
- Actions: click down arrow to add an entry
- Action: Use backend
- Condition acl names: vault.mydomain.com-domain vault.mydomain.com-acl3
- Backend: vault.mydomain.com-notifications
- Actions: click down arrow to add an entry
- Default backend, access control lists and actions
- Actions: click down arrow to add an entry
- Action: Use backend
- Condition acl names: vault.mydomain.com-domain vault.mydomain.com-acl4
- Backend: vault.mydomain.com-notifications
- Actions: click down arrow to add an entry
- Default backend, access control lists and actions
- Actions: click down arrow to add an entry
- Action: http-request deny
- Condition acl names: vault.mydomain.com-domain vault.mydomain.com-admin
- deny_status:
- Actions: click down arrow to add an entry
Click on Save and Apply changes to complete the HAProxy settings.
Now that HAProxy is configured, you should be able to visit https://vault.mydomain.com and see the same page as when you visited http://vault.lan.mydomain.com, but now with HTTPS and through Internet.
This is it, now all you have to do is to create an account on the Vaultwarden interface and start populating it with your passwords.