How to run Vaultwarden server on linux

I’ve been using Bitwarden’s premium service for two years, and am really satisfied with it.

I’ve also heard of Vaultwarden, but I didn’t bother to run it myself, until yesterday.

This is a footage of building and running Vaultwarden on linux, without docker.

A. Build

Following instructions are from this page.

a. Build vaultwarden binary

I created a working directory for vaultwarden:

$ mkdir vaultwarden

cloned the vaultwarden’s source repository in it:

$ cd vaultwarden
# now in the working directory,
$ git clone https://github.com/dani-garcia/vaultwarden.git ./src

then built the binary with cargo:

$ cd src
# now in the cloned source directory,
$ cargo build --features sqlite --release

b. Download web-vault

The web UI needs to be downloaded or built separately.

I downloaded the latest gzip file from the release page:

$ cd ..
# got back to 'vaultwarden' directory,
$ wget https://github.com/dani-garcia/bw_web_builds/releases/download/v2023.1.1/bw_web_v2023.1.1.tar.gz
$ tar xzvf bw_web_v2023.1.1.tar.gz

c. Run

I symlinked the binary, created data directory, and checked if the binary runs ok:

# now in the working directory,
$ ln -sf src/target/release/vaultwarden ./vaultwarden
$ mkdir data
$ ./vaultwarden

If you can see messages like this:

/--------------------------------------------------------------------\
|                        Starting Vaultwarden                        |
|                      Version 1.27.0-1ba8275d                       |
|--------------------------------------------------------------------|
| This is an *unofficial* Bitwarden implementation, DO NOT use the   |
| official channels to report bugs/features, regardless of client.   |
| Send usage/configuration questions or feature requests to:         |
|   https://vaultwarden.discourse.group/                             |
| Report suspected bugs/issues in the software itself at:            |
|   https://github.com/dani-garcia/vaultwarden/issues/new            |
\--------------------------------------------------------------------/

[2023-02-15 11:13:15.048][start][INFO] Rocket has launched from http://127.0.0.1:8000

it is working!

Then I pressed [ctrl+c] to stop it, and began configuration.

B. Configure a reverse proxy

For using the web UI, we need to configure a reverse proxy with this guide.

I issued a SSL certificate with certbot, and added a reverse proxy site file for nginx:

# /etc/nginx/sites-enabled/MY_SITE_DOMAIN
#
# for Web UI of Vaultwarden

# The `upstream` directives ensure that you have a http/1.1 connection
# This enables the keepalive option and better performance
#
# Define the server IP and ports here.
upstream vaultwarden-default {
  zone vaultwarden-default 64k;
  server 127.0.0.1:8000;
  keepalive 2;
}
upstream vaultwarden-ws {
  zone vaultwarden-ws 64k;
  server 127.0.0.1:3012;
  keepalive 2;
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name MY_SITE_DOMAIN;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name MY_SITE_DOMAIN;

    ssl_certificate /etc/letsencrypt/live/MY_SITE_DOMAIN/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/MY_SITE_DOMAIN/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/MY_SITE_DOMAIN/fullchain.pem;

    client_max_body_size 128M;

    location / {
      proxy_http_version 1.1;
      proxy_set_header "Connection" "";

      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;

      proxy_pass http://vaultwarden-default;
    }

    location /notifications/hub/negotiate {
      proxy_http_version 1.1;
      proxy_set_header "Connection" "";

      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;

      proxy_pass http://vaultwarden-default;
    }

    location /notifications/hub {
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";

      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header Forwarded $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;

      proxy_pass http://vaultwarden-ws;
    }

    # Optionally add extra authentication besides the ADMIN_TOKEN
    # Remove the comments below `#` and create the htpasswd_file to have it active
    #
    #location /admin {
    #  # See: https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/
    #  auth_basic "Private";
    #  auth_basic_user_file /path/to/htpasswd_file;
    #
    #  proxy_http_version 1.1;
    #  proxy_set_header "Connection" "";
    #
    #  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;
    #
    #  proxy_pass http://vaultwarden-default;
    #}
}

I could see my vault page by restarting nginx and running vaultwarden,

but before that, I had to do some more configuration.

C. Configure things

Duplicated the .env.template file and edited it:

# now in the working directory,
$ cp src/.env.template ./.env
$ vi ./.env

a. Enable admin page

There is a guide for enabling admin page.

Created a randomly generated string with:

$ openssl rand -base64 48

and then set ADMIN_TOKEN=xyz0123456789abcdefghijklmnopqrstuvw.

b. Disable registrations

It is advised to disable registration of (unwanted) new users.

I could do it by just setting SIGNUPS_ALLOWED=false.

c. Setup SMTP

A little more efforts were needed on SMTP configuration.

SMTP is essential for sending various emails, eg. welcome messages and validation emails.

There are several options of SMTP server/relay services, so choose wisely.

In my case, I created a SendGrid account for it, setting following values:

SMTP_HOST=smtp.sendgrid.net
SMTP_FROM=MY_VERIFIED_EMAIL_ADDRESS
SMTP_FROM_NAME=Vaultwarden
SMTP_SECURITY=starttls
SMTP_PORT=587
SMTP_USERNAME=apikey
SMTP_PASSWORD=SG.0123456_abcdefghijk.XYZ-abcd-ABCD1234
SMTP_AUTH_MECHANISM="Login"

z. Save configurations

Finally, restarted nginx and ran vaultwarden.

Then I could access the admin page at url: YOUR_SERVER_URL/admin.

Signed in with my ADMIN_TOKEN, checked things up,

and then pressed the blue Save button on the bottom left:

vaultwarden_admin_settings_save

Vaultwarden became ready for service!

D. Make it start automatically

For some more convenience, I created a systemd service with this guide,

so it could be launched automatically on reboots.

[Unit]
Description=Vaultwarden
Documentation=https://github.com/dani-garcia/vaultwarden

# Only sqlite
After=network.target

[Service]
# The user/group vaultwarden is run under. the working directory (see below) should allow write and read access to this user/group
User=ubuntu
Group=ubuntu
# Use an environment file for configuration.
EnvironmentFile=MY_WORKING_DIR/.env
# The location of the compiled binary
ExecStart=MY_WORKING_DIR/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=MY_WORKING_DIR
ReadWriteDirectories=MY_WORKING_DIR

[Install]
WantedBy=multi-user.target

I had to comment out line: ProtectHome=true because my working directory was inside my home directory.

E. Make it more secure

a. Setup for Fail2ban

See the guide here.

I created filter and jail files for fail2ban:

Files for web vault:

/etc/fail2ban/filter.d/vaultwarden.local:

# /etc/fail2ban/filter.d/vaultwarden.local

[INCLUDES]
before = common.conf

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

/etc/fail2ban/jail.d/vaultwarden.local:

# /etc/fail2ban/jail.d/vaultwarden.local

[vaultwarden]
enabled = true
port = 80,443,8081
filter = vaultwarden
banaction = %(banaction_allports)s
#logpath = /path/to/vaultwarden.log
logpath = /var/log/syslog
maxretry = 3
bantime = 14400
findtime = 14400

Files for admin page:

/etc/fail2ban/filter.d/vaultwarden-admin.local:

# /etc/fail2ban/filter.d/vaultwarden-admin.local

[INCLUDES]
before = common.conf

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

/etc/fail2ban/jail.d/vaultwarden-admin.local:

# /etc/fail2ban/jail.d/vaultwarden-admin.local

[vaultwarden-admin]
enabled = true
port = 80,443
filter = vaultwarden-admin
banaction = %(banaction_allports)s
#logpath = /path/to/vaultwarden.log
logpath = /var/log/syslog
maxretry = 3
bantime = 14400
findtime = 14400

then

$ sudo systemctl reload fail2ban

to make the changes work.

These settings will block abusive/malicious attackers from brute-forcing into the vault.

b. Disable registration, invitations, and/or password hints

See following guides: this one and that one.

Z. Connect Bitwarden app with my Vaultwarden server

I wanted to add my Vaultwarden account in my Android app.

By pressing the ‘gear shaped’ button on the top right,

vaultwarden_new_account

I could see the screen for entering my own Vaultwarden server url:

vaultwarden_endpoint_setting

After signing in with my account info, I could use the app just like my premium Bitwarden account!


Wrap-Up

Maintaining a server with critical information is quite stressful.

So if I had a choice between keeping my secrets on my own server, and keeping them in a paid service, I would choose the latter, Bitwarden’s premium service.

Nevertheless, building and running a service manually is fun, so it was worth trying :-)