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 features. They also offer Enterprise plans from $3/month/user.

Where it shines, is its open sourceness, allowing anyone 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 the security of the server, also, take the necessary backup measures as explained later.

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 Microsoft Azure as an example, the steps will be the same for any other cloud provider be it DigitalOcean, Linode, AWS, etc.

Creating a Virtual Machine

This is the first step, and it will vary from cloud provider to provider, all you need to do it to create a virtual machine instance.

A basic VM with 1 vCPU and 1 GiB RAM running Ubuntu 20.04 would suffice for most use cases.

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 be equally compatible with the steps explained in this tutorial.

Updating the DNS Entry

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

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.

Configuring the Virtual Machine

Now, you can go ahead and start configuring the virtual machine.

Step 1: SSH into the Virtual Machine

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

ssh root@1.2.3.4

Use the username you selected when creating the virtual machine instead of root (if applicable), and replace the 1.2.3.4 with the IP address of your VM.

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

Step 2: Prerequisites

Before you go ahead and install something or start configuring your VM, here are a few things, I recommend you to perform:

# Update packages
sudo apt update && apt upgrade

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

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

With all of that out of the way, let’s get into the good stuff.

Step 3: Creating a “sudo” user

If your cloud provider didn’t ask you to choose a username for your VM 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 accidentally disrupt your server.

That’s why it’s recommended to use a limited user on a server, and temporarily elevating your limited user’s privileges using sudo for day-to-day administration tasks.

Most of the cloud providers, like Azure, don’t provide root access, instead gives you the option to create sudo user, anyway, here’s how you can create a sudo user and lock down the root user:

# 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

Now that you have created and a limited user and added it to the sudo group, let’s lock down the root user, to prevent ssh access via root:

# Disable root login (password & key) via SSH
sudo nano /etc/ssh/sshd_config
PermitRootLogin no

# Allow user login (password & key) via SSH
sudo nano /etc/ssh/sshd_config
AllowUsers username

# Disable password-based login via SSH for all users [optional]
sudo nano /etc/ssh/sshd_config
PasswordAuthentication no

Open up the sshd_config and scroll down to the PermitRootLogin option, and replace yes with no, additionally, add AllowUsers username, and then save it by Ctrl+O and exit by Ctrl+X as shown below:

Disable Root Login via SSH

Step 4: Configuring SSH Keys

Using SSH keys instead of passwords provides you with better security, as SSH keys are long and complex, far more than any password could be.

It is generally recommended to use SSH keys over passwords, but, it is totally optional, though, I recommend you use SSH keys instead of passwords, here’s how to do just that:

First, you need to create SSH key pair, to do that open a new terminal window and follow these commands:

# Create SSH keys
ssh-keygen

You’ll be asked to enter the file name for your SSH key, additionally, you can also choose a password for your SSH key.

You can check your SSH keys by using this command:

# Check SSH keys
ls -l ~/.ssh

There’ll be two keys, public and private, the one with “.pub” extension is your public key, never share the other one as that’s the private key.

Now, you need to add the public SSH key to your server, here’s how you can do just that:

# Copy Public key to the server
ssh-copy-id -i ~/.ssh/yourkeyname.pub username@1.2.3.4

Once that’s done, you can log in to your server via SSH by just switching to the SSH key on your device using this command:

# Switch SSH keys on your device
ssh-add yourkeyname

# Log in to server using SSH keys
ssh ssh username@1.2.3.4

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

Step 5: 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.

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.

Vaultwarden 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.

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 5.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:

# Output
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
# Output
# 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 Oct  4 16:08:29 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 Oct  4 16:06:37 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 5.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
# Verifying the Docker instance is running
sudo docker ps

Step 5.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.

# Listing application profiles
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)
# Enabling Nginx Full
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
# Output
● 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 5.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.

# Output
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
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

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.

Leave a Comment