Self Hosting Bitwarden Password Manager using Vaultwarden

Bitwarden is an open-source password manager that stores passwords, notes, credit cards and other sensitive information in an encrypted vault. It is a cloud-based password manager that automatically syncs all of your sensitive information across all devices and autofills them into websites and other applications. …

Bitwarden is an open-source password manager that stores passwords, notes, credit cards and other sensitive information in an encrypted vault.

It is a cloud-based password manager that automatically syncs all of your sensitive information across all devices and autofills them into websites and other applications.

Bitwarden comes with whole hosts of features like biometric unlock, strong password generation, file attachments, password history, secure sharing, two-factor authentication support, data breach reports, and a lot more.

You can use the free version of Bitwarden that comes with pretty much everything you’ll need, there’s a Premium version for $10/year which gives access to additional feature, enterprise plans start from $3/month/user.

Where it shines, is its open sourceness, allowing us to self-host all of Bitwarden on our infrastructure.

Note: By self-hosting your Bitwarden password manager, you are assuming responsibility for the security and resiliency of sensitive information stored in the Bitwarden password manager. Ensure that you are confident with assuming the security of the server, also, take the necessary backup measures as explained in later sections.

I don’t recommend self-hosting the password manager for most people as it will contain all the sensitive information if you aren’t comfortable managing the server infrastructure and ensuring its security and reliability.

I recommend letting Bitwarden manage the server infrastructure for most people, the premium version is dirt cheap compared to other password managers, and there’s always the free version that anyone can use.

Alright, with all of that out of the way, let’s get into the good stuff: we’ll be using Vaultwarden (formerly bitwarden_rs)in this tutorial to self-host Bitwarden, it provides a lightweight, single-process, API-compatible Bitwarden server implementation written in Rust.

I’ve used Linode in this tutorial, the steps will be the same for any other cloud provider be it DigitalOcean, AWS, etc.

Step1: Creating a Virtual Machine

Any basic virtual machine running Ubuntu 20.04 will suffice:

  • 1 GB RAM
  • 1 vCPU
  • 20 GB Storage

Pick a server location that’s closest to you for low latency, or you could choose a different geographical location for compliance or privacy reasons.

I’ve used Ubuntu 20.04 in this guide, generally speaking, any Linux distribution that supports running Docker containers should work just fine.

Step 2: Updating the DNS Entry

Note down the IP address of the virtual machine, go to your domain registrar, and add an A record with the IP address of the VM.

Enter @ in the host section, your IP address in the value section, and update TTL to 3600.

We will be securing connections to our Vaultwarden installation via TLS/SSL. Vaultwarden can set up and manage a free, trusted SSL certificate from Let’s Encrypt.

Step 3: Configuring the Virtual Machine for Bitwarden

Now, let’s configure our server:

Step 3.1: Connect to the Server via SSH

Open up the terminal on your device, and run this command:

ssh root@123.45.67.89

If you chose a username while creating the virtual machine, use that instead of root, and replace 123.45.67.89 with the IP address of your VM.

You’ll be prompted with “The authenticity of host…”, just type yes, and then enter the password.

Step 3.2: Configure Automatic Updates

Let’s update packages and configure automatic updates so that our server gets patched automatically.

# Update packages
sudo apt update && apt upgrade

# Install unattended-upgrades
sudo apt install unattended-upgrades

# Configure unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades

# Test unattended-upgrades
sudo unattended-upgrades --dry-run --debug

Step 3.3: Creating a “sudo” user

If your cloud provider didn’t ask you to choose a username while creating it, you are given root access to your server; it is recommended to not use the “root” user, which has unlimited privileges and can execute any command, even ones that could potentially disrupt your server.

Let’s create a new user on a server that can use “sudo” to do day-to-day administration tasks.

# Create a new user
adduser username

# Add user to the "sudo" group
usermod -aG sudo username

# Check user's group
groups username

# Switching users
su - username
su - root

Step 3.4: Configure SSH Keys

Using SSH keys instead of passwords provides you with better security, as SSH keys are far more long and complex than any password could ever be; you can also add an extra password to the SSH keys, requiring both the SSH key and the password to access the server.

Log out of the server or just open up a new terminal on your computer to create SSH keys:

# Create ssh keys
ssh-keygen -b 4096 

# View ssh keys
ls -l ~/.ssh

# Add public key to server
ssh-copy-id -i ~/.ssh/keyname.pub username@123.45.67.89

# Switch ssh keys on client
ssh-add ~/.ssh/keyname

During the ssh-keygen process, you’ll be prompted for file location, use the default one or give a new location by typing in /home/username/.ssh/keyname, and enter a strong password for the SSH key.

In the .ssh folder, there’ll be two files, the one with “.pub” extension is your public key, the other one is your private key, never share the private key with anyone.

You might get a message like Could not open a connection to your authentication agent when switching SSH keys, you’ll need to start ssh-agent first using:

eval `ssh-agent`

Once done, you can log in to the server by just using the ssh username@123.45.67.89 command without entering the user password, although you will need to enter the password of your SSH key.

Step 3.5: Disable root login

Now that we have a new user with limited privileges that can run “sudo” commands and can access the server via SSH keys; let’s lock down our root user, as it is usually the most targeted account by hackers.

To do so, type in sudo nano /etc/ssh/sshd_config, and update PermitRootLogin to no and add AllowUsers username as shown below:

Disable root login via SSH

Optionally, you can also go ahead and disable password-based login via SSH for all users, including the new user account we just created, by updating these values in the same sshd config file:

# Disable password-based login via ssh for all users [optional]
PasswordAuthentication no
ChallengeResponseAuthentication no

Once done, save the file using Ctrl + O & Ctrl + X, and restart the sshd service using this command:

sudo systemctl restart sshd

Now, your server is ready to install Vaultwarden, let’s get into it.

Step 4: Installing Vaultwarden

Vaultwarden is an unofficial Bitwarden server implementation written in Rust. It is compatible with the official Bitwarden clients, and is ideal for self-hosted deployments where running the official resource-heavy service is undesirable.

The official Bitwarden server implementation isn’t really written with small deployment in mind and requires running MS SQL server among other things, which is quite some over-engineering if you intend to use it for small company or family-friendly setup.

Vaultwarden also has a lot of other benefits over using the official Bitwarden, including a few premium features like Attachments, Authenticator, and more; it uses the default SQL backend for the application (sqlite3), the SQL datastore contains the user data of the application, which we’ll need to back up to prevent our sensitive data in a data loss scenario.

We’ll also be configuring a reverse proxy (Nginx) in front of the Docker container, as it provides TLS termination for both the web-based vault interface and the websocket server.

I’m using the latest Vaultwarden Docker image available, version 1.23.0 at the time of writing. Follow the upgrade instructions to ensure that the deployment is up-to-date with current upstream releases and security updates.

Step 4.1: Installing Docker

1. Uninstalling any previously-installed older Docker packages:

sudo apt remove docker docker-engine docker.io containerd runc

Follow the next step if you got an output like this:

Reading package lists... Done
Building dependency tree       
Reading state information... Done
E: Unable to locate package docker-engine

2. Installing Docker using the convenience script at get.docker.com:

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

If all goes well, you’ll see something like this:

# Executing docker install script, commit: 93d2499759296ac1f9c510605fef85052a2c32be
+ sh -c apt-get update -qq >/dev/null
+ sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -qq apt-transport-https ca-certificates curl >/dev/null
+ sh -c curl -fsSL "https://download.docker.com/linux/ubuntu/gpg" | gpg --dearmor --yes -o /usr/share/keyrings/docker-archive-keyring.gpg
+ sh -c echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu focal stable" > /etc/apt/sources.list.d/docker.list
+ sh -c apt-get update -qq >/dev/null
+ sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends  docker-ce-cli docker-scan-plugin docker-ce >/dev/null
+ version_gte 20.10
+ [ -z  ]
+ return 0
+ sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker-ce-rootless-extras >/dev/null
+ sh -c docker version
Client: Docker Engine - Community
 Version:           20.10.9
 API version:       1.41
 Go version:        go1.16.8
 Git commit:        c2ea9bc
 Built:             Mon XXX  X XX:XX:XX 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.9
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.8
  Git commit:       79ea9d3
  Built:            Mon XXX  X XX:XX:XX 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.11
  GitCommit:        5b46e404f6b9f661a205e28d59c982d3634148f8
 runc:
  Version:          1.0.2
  GitCommit:        v1.0.2-0-g52b36a2
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

================================================================================

To run Docker as a non-privileged user, consider setting up the
Docker daemon in rootless mode for your user:

    dockerd-rootless-setuptool.sh install

Visit https://docs.docker.com/go/rootless/ to learn about rootless mode.


To run the Docker daemon as a fully privileged service, but granting non-root
users access, refer to https://docs.docker.com/go/daemon-access/

WARNING: Access to the remote API on a privileged Docker daemon is equivalent
         to root access on the host. Refer to the 'Docker daemon attack surface'
         documentation for details: https://docs.docker.com/go/attack-surface/

================================================================================

3. Installing Docker Compose:

sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

Substitute 1.29.2 with a different version of Compose to install it.

4. Applying executable permissions to the binary:

sudo chmod +x /usr/local/bin/docker-compose

5. Testing that docker is up and running:

sudo docker ps
#Output
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

Step 4.2: Installing Vaultwarden

Now that the docker is up and running, let’s install and configure Vaultwarden.

1. Creating a directory to house all the components of the Bitwarden server:

# Creating the directory
sudo mkdir vaultwarden
cd vaultwarden

# Enforcing strict permissions
sudo chmod go-rwx vaultwarden

2. Creating a docker-compose.yml file:

touch docker-compose.yml

Next up, we’ll need to edit the docker-compose.yml file that the docker-compose will use to provision the Docker instance.

Edit the docker-compose.yml file using sudo nano docker-compose.yml and paste the following:

# docker-compose.yml
version: '3'

services:
  bitwarden:
    image: vaultwarden/server
    restart: always
    ports:
      - 8000:80
    volumes:
      - ./bw-data:/data
    environment:
      WEBSOCKET_ENABLED: 'true' # Required to use websockets
      SIGNUPS_ALLOWED: 'true'   # set to false to disable signups

Now, save this docker-compose.yml file and exit from the vaultwarden folder.

3. Starting up the Vaultwarden server inside Docker:

sudo docker-compose up -d
# Output
Creating network "vaultwarden_default" with the default driver
Pulling bitwarden (bitwardenrs/server:)...
latest: Pulling from bitwardenrs/server
b380bbd43752: Pull complete
464a9f5c31e4: Pull complete
d28f45edcd36: Pull complete
d1cb198dadb5: Pull complete
6cf27201f998: Pull complete
b0bd767f38d8: Pull complete
b4c0e95c83e7: Pull complete
Digest: sha256:c325405ea2cd9f3910f3fb77b20793b30904a2ab1f4eed309911f908c64c0f30
Status: Downloaded newer image for bitwardenrs/server:latest
Creating vaultwarden_bitwarden_1 ... done

Verify that the docker instance is running using sudo docker ps.

Step 4.3: Configure Nginx as a Reverse Proxy

Nginx is a reverse proxy that allows you to point incoming web traffic to our new Bitwarden server.

1. Installing Nginx:

sudo apt install nginx

2. Configuring UFW:

If you have UFW installed, you will have to Allow Nginx through your local firewall, view all options available for nginx:

sudo ufw app list
# Output
Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  OpenSSH

Here, there are 3 application profiles available for Nginx:

  • Nginx Full: Opens both port 80 (normal, unencrypted web traffic) and port 443 (TLS/SSL, encrypted traffic)
  • Nginx HTTP: Opens only port 80 (normal, unencrypted web traffic)
  • Nginx HTTPS: Opens only port 443 (TLS/SSL, encrypted traffic)

Enable Nginx Full using:

sudo ufw allow 'Nginx Full'
# Output
Rules updated
Rules updated (v6)

Next up, we need to double-check that the Nginx server is up and running:

sudo systemctl status nginx

You should see somthing like this:

● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset:>
     Active: active (running) since Sun 2021-10-24 15:00:28 UTC; 10min ago
       Docs: man:nginx(8)
   Main PID: 4239 (nginx)
      Tasks: 2 (limit: 1090)
     Memory: 5.3M
     CGroup: /system.slice/nginx.service
             ├─4239 nginx: master process /usr/sbin/nginx -g daemon on; master_>
             └─4240 nginx: worker process

Oct 24 15:00:28 warden systemd[1]: Starting A high performance web server and a>
Oct 24 15:00:28 warden systemd[1]: Started A high performance web server and a >
lines 1-13/13 (END)

3. Configuring Nginx:

Now, we’ll need to make sure that all the incoming traffic reaches our container instance:

# Navigating to the Nginx directory
cd /etc/nginx/sites-enabled

# Editing the "default" file
sudo nano default

Copy-paste the following code into the default file:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:8000; # bitwarden server address
    }
}

Replace example.com with the domain name you chose during the DNS setup process.

Step 4.4: Setting up Certbot

Certbot is a simple tool by the Electronic Frontier Foundation allows us to generate SSL certificates for free with Let’s Encrypt.

# Installing Certbot
sudo snap install --classic certbot

# Preparing the Certbot command
sudo ln -s /snap/bin/certbot /usr/bin/certbot

Run the following command to set up the SSL Certificate:

sudo certbot --nginx

You’ll need to configure a few things like putting in an email to get the SSL certicate ready, and will be greeted with something like this:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel): mail@example.com      

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
Account registered.

Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: example.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1
Requesting a certificate for example.com

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/example.com/privkey.pem
This certificate expires on 2022-01-22.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

Deploying certificate
Successfully deployed certificate for example.com to /etc/nginx/sites-enabled/deafault
Congratulations! You have successfully enabled HTTPS on https://example.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Step 5: Accessing Bitwarden Vault

And, that is it. Now, our Bitwarden instance is up and running and is accessible from the domain name.

You can now go ahead and create a new account for your Bitwarden server.

Creating a new Bitwarden Account

You can also use your self-hosted Bitwarden on the native apps, just put in the URL of your Vaultwarden instance in the server URL section, that can be found by clicking on the gear icon on the left-hand corner.

Configuring Bitwarden clients for Self-hosted Vaultwarden Instance

Vaultwarden Conclusion

With Vaultwarden running securely over TLS, check out the additional documentation provided by the Vaultwarden project, which can help add more functionality to your installation. Some of these features include:

All in all, running a self-hosted Bitwarden instance using Vaultwarden, is an amazing way to have all the best features of a secure password manager, running on your own infrastructure.

I’ll update this tutorial soon with guides on backing up the database, SMTP configuration and more.