Install a Vaultwarden server

I’m pretty sure you’ve ever heard of Bitwarden. Bitwarden is a password manager. The big difference (among many others) compared to Dashlane is that you can deploy your own Bitwarden instance on your server in a Docker container, or do it from scratch but I think it’s pretty tricky…

The software stack is composed as follow:

These two Microsoft technologies convinced me to go elsewhere, but I got caught up with Vaultwarden!

Vaultwarden is a Bitwarden server API implementation written in Rust compatible with upstream Bitwarden clients, perfect for self-hosted deployment where running the official resource-heavy service might not be ideal.

The software stack is composed as follow:

Information and requirements

These elements are to be taken into consideration to follow this article:

Update the system

sudo dnf -y update

Install required utilities

sudo dnf -y install vim openssl-devel httpd mod_ssl epel-release firewalld policycoreutils-python-utils git gcc

Create a system user to run Vaultwarden

sudo adduser --system --shell /bin/false --comment "Vaultwarden server" --user-group -M vaultwarden

Install Rust

Note: for these two downloaded scripts, always check the content or checksums and then execute it. In other words, don’t do pipe curl to bash like many tutorials do.

The nightly version of Rust must be installed.

curl -LOJsSf
bash -E -y --default-host x86_64-unknown-linux-gnu --default-toolchain nightly --profile minimal
source ~/.cargo/env
rm -f
rustc --version
rustc 1.58.0-nightly (bd41e09da 2021-10-18)

Compile and configure the back-end

git clone /tmp/vaultwarden
cargo build --features sqlite --release --manifest-path=/tmp/vaultwarden/Cargo.toml

Create the directory structure

sudo mkdir -p /var/lib/vaultwarden/{data,log}
sudo mkdir /etc/vaultwarden

Create the configuration file

cp /tmp/vaultwarden/.env.template /tmp/vaultwarden/.env

Enable the administration interface

This command uncomments the ADMIN_TOKEN variable in the configuration file and generates a token of 50 characters via /dev/urandom.

sed -i 's/'"# ADMIN_TOKEN=.*"'/'"ADMIN_TOKEN=$(tr -cd '[:alnum:]' < /dev/urandom | fold -w 49 | head -n 1)"'/' /tmp/vaultwarden/.env

Grab the token, we’ll need it later.

grep ^ADMIN_TOKEN /tmp/vaultwarden/.env

Set the web vault folder

sed -i "s/# WEB_VAULT_FOLDER=web-vault/WEB_VAULT_FOLDER=\/var\/www\/web-vault/" /tmp/vaultwarden/.env

Change the Rocket IP address

Vaultwarden uses the web framework Rocket to publish API endpoints. Because we are going to use a reverse proxy, Rocket must serve pages on the server’s loopback address.

sed -i "s/# ROCKET_ADDRESS=0\.0\.0\.0.*/ROCKET_ADDRESS=" /tmp/vaultwarden/.env

Enable WebSocket notifications

sed -i "s/# WEBSOCKET_ENABLED=false/WEBSOCKET_ENABLED=true/" /tmp/vaultwarden/.env

Change the WebSocket IP address

sed -i "s/# WEBSOCKET_ADDRESS=" /tmp/vaultwarden/.env

Set the domain

The domain must match the address from where you access the server.

sed -i "s/# DOMAIN=.*/DOMAIN=https:\/\/vault.local/" /tmp/vaultwarden/.env

Set the log file and path

From release 1.5.0, Vaultwarden supports logging to file. It is necessary to monitor what happens on the server. If you plan to use Fail2ban, you need to do the following thing.

sed -i "s/# LOG_FILE=.*/LOG_FILE=\/var\/lib\/vaultwarden\/log\/vaultwarden.log/" /tmp/vaultwarden/.env
sed -i "s/# LOG_LEVEL=.*/LOG_LEVEL=warn/" /tmp/vaultwarden/.env

LOG_LEVEL options are trace, debug, info, warn, error or off. The ones that still allows Fail2ban properly are warn and error.

Set data and database folders

sed -i "s/# DATA_FOLDER=data/DATA_FOLDER=\/var\/lib\/vaultwarden\/data/" /tmp/vaultwarden/.env
sed -i "s/# DATABASE_URL=data\/db.sqlite3/DATABASE_URL=\/var\/lib\/vaultwarden\/data\/db.sqlite3/" /tmp/vaultwarden/.env

Set trash auto delete

Number of days to wait before auto-deleting a trashed item. This parameter has a global scope, means that it applies to all users.

sed -i "s/# TRASH_AUTO_DELETE_DAYS=.*/TRASH_AUTO_DELETE_DAYS=10/" /tmp/vaultwarden/.env

Install the front-end

Be sure to get the latest version available here.

curl -LOsSf
sudo tar -zxf bw_web_v2.27.0.tar.gz -C /var/www/
rm -f bw_web_v2.27.0.tar.gz 

Move files and set permissions


sudo mv /tmp/vaultwarden/target/release/vaultwarden /usr/local/bin/vaultwarden
sudo mv /tmp/vaultwarden/.env /etc/vaultwarden
rm -rf /tmp/vaultwarden/
sudo chmod 750 /usr/local/bin/vaultwarden
sudo chown root:vaultwarden /usr/local/bin/vaultwarden
sudo chown -R vaultwarden:vaultwarden /var/lib/vaultwarden/
sudo chmod -R 750 /var/lib/vaultwarden/
sudo chown -R root:vaultwarden /etc/vaultwarden/
sudo chmod 750 /etc/vaultwarden/
sudo chmod 640 /etc/vaultwarden/.env


sudo chown -R apache:apache /var/www/web-vault/

Add authorized ports

sudo firewall-cmd --add-port={80/tcp,443/tcp} --permanent
sudo firewall-cmd --reload

Reverse proxy configurations

General TLS configurations

Add these headers and the cache configuration for OCSP stapling just after #SSLCryptoDevice ubsec into /etc/httpd/conf.d/ssl.conf. Also, set the Diffie-Hellman parameters’ path and the temporary curve used for ephemeral ECDH modes.

# Headers
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set X-Frame-Options DENY
Header always set X-Content-Type-Options nosniff
Header always set Referrer-Policy no-referrer

# OCSP stapling
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"

# Key exchange
SSLOpenSSLConfCmd DHParameters "/etc/pki/tls/private/dhparam.pem"
SSLOpenSSLConfCmd Curves secp384r1

Next, you must generate the Diffie-Hellman’s parameters. Here, the size of the generated parameters set will be 4096 bits.

sudo openssl dhparam -out /etc/pki/tls/private/dhparam.pem 4096
sudo chmod 440 /etc/pki/tls/private/dhparam.pem

Disable TLS session tickets and enable the addition of OCSP responses to TLS negociation.

sudo sed -i "214i SSLSessionTickets Off" /etc/httpd/conf.d/ssl.conf
sudo sed -i "215i SSLUseStapling On" /etc/httpd/conf.d/ssl.conf

Accept all protocols except those before TLSv1.2.

sudo sed -i "s/#SSLProtocol.*/SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1/" /etc/httpd/conf.d/ssl.conf

Generate a self-signed certificate (local server only)

Generate a private key for a curve. Here, I use the curve secp384r1 but you can use another one by executing openssl ecparam -list_curves.

sudo openssl ecparam -check -name secp384r1 -genkey -noout -out /etc/pki/tls/private/privkey.pem -rand /dev/urandom
checking elliptic curve parameters: ok

Create a self-signed certificate. Feel free to personalize the answers to the questions asked. This self-signed certificate is used for testing purposes so it is not essential that the information given is correct.

sudo openssl req -utf8 -new -x509 -key /etc/pki/tls/private/privkey.pem -out /etc/pki/tls/certs/cert.pem -days 365 -rand /dev/urandom
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
Country Name (2 letters code) [XX]:FR
State or Province Name (full name) []:Auvergne-Rhone-Alpes
Locality Name (eg, city) [Default City]:Lyon
Organizational Name (eg, company) [Default Company Ltd]:Illuad Technologies
Organizational Unit Name (eg, section) []:IT Security
Common Name (eg, your name or your server's hostname) []:vault.local
Email Address []:contact@vault.local

Set the correct permission.

sudo chmod 440 /etc/pki/tls/private/privkey.pem
sudo chmod 644 /etc/pki/tls/certs/cert.pem

Then, create the reverse proxy configuration into /etc/httpd/conf.d/vhost.conf.

<VirtualHost *:80>
    ServerName vault.local:80
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

<VirtualHost *:443>
    ServerName vault.local:443
    ServerAlias vault.local
    ServerAdmin contact@vault.local

    SSLCertificateFile /etc/pki/tls/certs/cert.pem
    SSLCertificateKeyFile /etc/pki/tls/private/privkey.pem
    SSLCACertificateFile /etc/pki/tls/certs/cert.pem

    Protocols h2 http/1.1

    ErrorLog /var/log/vaultwarden/error_log
    CustomLog /var/log/vaultwarden/access_log combined

    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteRule /notifications/hub(.*) ws:// [P,L]
    ProxyPass /

    ProxyPreserveHost On
    ProxyRequests Off
    RequestHeader set X-Real-IP %{REMOTE_ADDR}s

Request a Let’s Encrypt certificate (public server only)

In case you wish to obtain free certificates (via Let’s Encrypt), two methods are available to you. Luck is on your side, you will find here the method to obtain a certificate from Let’s Encrypt, and here a slightly more advanced method that allows you to obtain a certificate based on elliptic curve cryptography (still via Let’s Encrypt).

The reverse proxy configuration is the exact same. Things you have to modify are the location of the certificate, private key and the chain of trust, but it is documented in the article previously mentionned.

Create the logs directory

sudo mkdir -p /var/log/vaultwarden

Enable and start HTTPD service

sudo systemctl enable --now httpd

Create the service file

Copy the following snippet into /etc/systemd/system/vaultwarden.service.




Restart systemctl daemon

sudo systemctl daemon-reload

SELinux stuff

If SELinux is enabled Vaultwarden will not start. You must change the SELinux content of the binary.

sudo semanage fcontext -a -t bin_t /usr/local/bin/vaultwarden
sudo restorecon -v /usr/local/bin/vaultwarden

You’re still not quite done with SELinux, you must allow HTTPD scripts and modules to connect to the network.

sudo setsebool -P httpd_can_network_connect on

The -P flag allows to make this modification persistent even after a reboot.

Enable and start Vaultwarden service

sudo systemctl enable --now vaultwarden

Final configurations

Go to https://vault.local (or the domain name that you have linked to your Vaulwarden’s public IP address) and follow these steps:

Disable the administration interface

It is necessary to comment this variable and at the same time remove the token associated with it for security reasons.

sudo sed -i "s/^ADMIN_TOKEN=.*/# ADMIN_TOKEN=/" /etc/vaultwarden/.env

Because Vaultwarden has a runtime configuration file, commenting the ADMIN_TOKEN variable is not enough to disable the administration interface. To do this you must also delete the admin_token variable in this file.

sudo sed -i '/"admin_token/d' /var/lib/vaultwarden/data/config.json

Then, restart the server.

sudo systemctl restart vaultwarden


Generate strong password

Using a password manager is good, but storing weak passwords is useless. Generating a strong password can be done like follow. You can easily modify the password length by increasing or decreasing the -w flag value.

tr -cd [:alnum:] < /dev/urandom | fold -w 49 | head -n 1

And now, with special characters.

tr -cd [:graph:] < /dev/urandom | fold -w 49 | head -n 1

Update Vaultwarden

You will find here a script I wrote to update the Vaultwarden binary. The steps of implementation are explained in the file.

YubiKey OTP authentication

Work in progress.

Backing up your Vaultwarden SQLite database

You will find here a script I wrote to save the Vaultwarden SQLite database. The steps of implementation are explained in the file.

Protection against brute-force with Fail2ban

sudo dnf -y install fail2ban-server fail2ban-firewalld

First, we want to ban brute-force user login attempts. Copy the following snippet into /etc/fail2ban/filter.d/vaultwarden.conf.

before = common.conf

failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =

Create the jail by copying the following snippet into /etc/fail2ban/jail.d/vaultwarden.local.

enabled = true
port = 80,443
filter = vaultwarden
action = iptables-allports[name=vaultwarden]
logpath = /var/lib/vaultwarden/log/vaultwarden.log
maxretry = 3
bantime = 86400
findtime = 3600

Although we have disabled the administration page, if you decide to enable it to modify the configuration and forget to disable it again, you’ll protect your server from brute-force attack. Copy the following snippet into /etc/fail2ban/filter.d/vaultwarden-admin.conf.

before = common.conf

failregex = ^.*Invalid admin token\. IP: <ADDR>.*$
ignoreregex =

Same as before, create the jail by copying the following snippet into /etc/fail2ban/jail.d/vaultwarden-admin.local.

enabled = true
port = 80,443
filter = vaultwarden-admin
action = iptables-allports[name=vaultwarden-admin]
logpath = /var/lib/vaultwarden/log/vaultwarden.log
maxretry = 3
bantime = 86400
findtime = 3600

Once again, if SELinux is enabled Fail2ban will not start. You must change the log folder’s SELinux context.

sudo semanage fcontext -a -t var_log_t /var/lib/vaultwarden/log/vaultwarden.log
sudo restorecon -Rv /var/lib/vaultwarden/log/vaultwarden.log

Start the Fail2ban server and enable it at boot.

sudo systemctl enable --now fail2ban

Check the standard login attempts jail.

sudo fail2ban-client status vaultwarden
Status for the jail: vaultwarden
|- Filter
|  |- Currently failed: 0
|  |- Total failed:	0
|  `- File list:	/var/lib/vaultwarden/log/vaultwarden.log
`- Actions
   |- Currently banned:	0
   |- Total banned:	0
   `- Banned IP list:

Check the admin login attempts jail.

sudo fail2ban-client status vaultwarden-admin
Status for the jail: vaultwarden-admin
|- Filter
|  |- Currently failed: 0
|  |- Total failed:	0
|  `- File list:	/var/lib/vaultwarden/log/vaultwarden.log
`- Actions
   |- Currently banned:	0
   |- Total banned:	0
   `- Banned IP list:

You can try to log in with bad credentials and executed a new time the previous command, the Currently failed and Total failed values will increment. Don’t abuse, you’ll be blocked otherwise!

Finally, you can unban an IP with the following command.

sudo fail2ban-client set vaultwarden{-admin} unbanip <IP>

Setup logs rotation

sudo dnf -y install logrotate

Copy the following snippet into /etc/logrotate.d/vaultwarden.

/var/lib/vaultwarden/log/vaultwarden.log {
	rotate 7
	create 0644 vaultwarden vaultwarden

This configuration file contains:

You can execute a dry-run to see what logrotate would do if it was actually executed now.

sudo logrotate -d /etc/logrotate.d/vaultwarden

Finally, logrotate creates an entry in /etc/cron.daily/logrotate to execute the configured tasks.