Self hosted Bitwarden password manager with Docker
Introduction
If you need a self hosted password manager then Bitwarden is a good choice. In this post I will show how to install Bitwarden with Let's Encrypt certificate using DNS challenge with Cloudflare (by default it is HTTP challenge).
OS used: Debian 12
Software used: Docker 27.4.0, Bitwarden 2024.12.1
Source
- Bitwarden's official Linux installation documentation
- Bitwarden's official configuration options
- Bitwarden's official update documentation
Prerequisites
Create new virtual machine for example using this tutorial Fast initialization of Debian VM using Ansible.
Bitwarden hardware requirements
I suggest following VM resources:
- Disk size at least 16GB
- RAM at least 2GB
- CPU at least 2 cores
Get Let's Encrypt certificate
Bitwarden works only in HTTPS and needs valid certificate. You can obtain such certificate using instruction from post Obtaining Let's Encrypt certificate using Cloudflare.
After you followed the instruction Let's Encrypt certificate will be in folder /etc/letsencrypt/live/<your_domain_name>/.
Bitwarden installation
Install Docker
Bitwarden's official installation relies on Docker images, which is why you need to install Docker in your virtual machine. To install Docker run following command (taken from the Official Docker Docs how to Install Docker Engine on Debian):
$ sudo apt-get update && \
sudo apt-get install ca-certificates curl && \
sudo install -m 0755 -d /etc/apt/keyrings && \
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && \
sudo chmod a+r /etc/apt/keyrings/docker.asc && \
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null && \
sudo apt-get update && \
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Create Bitwarden user
As recommended in official installation guide create Bitwarden user in order to isolate this applications from other processes and add this user to docker group.
Set strong password for this user.
$ sudo adduser bitwarden && \
sudo usermod -aG docker bitwarden
Create Bitwarden directory
Create directory for Bitwarden:
$ folderpath=/opt/bitwarden && \
sudo mkdir -m 700 ${folderpath} && \
sudo chown bitwarden:bitwarden ${folderpath}
-m - set folder permissions
Install Bitwarden
Installation of Bitwarden, as stated in official documentation, should be performed as bitwarden user.
IMPORTANT Change user to bitwarden:
$ su - bitwarden
Enter Bitwarden folder:
$ cd /opt/bitwarden
Download Bitwarden installation script:
$ curl -Lso bitwarden.sh "https://func.bitwarden.com/api/dl/?app=self-host&platform=linux" && chmod 700 bitwarden.sh
Run the script:
./bitwarden.sh install
Answer the prompts in the installer:
- Enter the domain name for your Bitwarden instance:
- Enter domain name of your Bitwarden host.
- Do you want to use Let's Encrypt to generate a free SSL certificate? (y/n):
n- no, because it only uses HTTP challenge.
- Enter the database name for your Bitwarden instance (ex. vault):
- For this installation we will use name
vault.
- For this installation we will use name
- Enter your installation id:
- You can retrive this id using a valid email at https://bitwarden.com/host. More information can be found here in official Bitwarden FAQ
- Enter your installation key:
- You can retrive this key using a valid email at https://bitwarden.com/host. More information can be found here in official Bitwarden FAQ
- Enter your region (US/EU):
- Enter your location. Only applicable if you're connecting a self-hosted account or organization to Bitwarden paid subscription.
- Do you have a SSL certificate to use? (y/n):
y- yes, we will use certificate that was generated usingcertbot.
- Is this a trusted SSL certificate (requires ca.crt, see docs)? (y/N):
n- no,certbotdon't generateca.crt.
- Do you want to generate a self-signed SSL certificate? (y/n):
n- no, because you will use Let's Encrypt certificate.
Configure Bitwarden SSL
IMPORTANT Perform following actions as bitwarden user.
Create folder in which Bitwarden searches for certificate (if it's not already created):
$ mkdir -p /opt/bitwarden/bwdata/ssl/<your_domain_name>
-p - make directory structure
Docker containers can't read symlinks which certbot uses in certificate folder (/etc/letsencrypt/live) that is why we need to copy it to folder used by Bitwarden. To achieve this we will use the fact that certificate renewal command is run 2 times per day by certbot.timer (which invokes certbot.service) and certbot has option to run command after successful certificate renewal. Both timer and service are located in /usr/lib/systemd/system/.
Create script file in /etc/letsencrypt/renewal-hooks/deploy/ folder that will be run after a successful certificate renewal:
$ sudo vim /etc/letsencrypt/renewal-hooks/deploy/copy-certificate
cp /etc/letsencrypt/live/<your_domain_name>/fullchain.pem /opt/bitwarden/bwdata/ssl/<your_domain_name>/certificate.crt
cp /etc/letsencrypt/live/<your_domain_name>/privkey.pem /opt/bitwarden/bwdata/ssl/<your_domain_name>/private.key
Make file executable:
$ sudo chmod 744 /etc/letsencrypt/renewal-hooks/deploy/copy-certificate
Force certificate renewal:
$ sudo certbot renew --force-renewal
Check if certificate and certificate private key was copied to Bitwarden folder:
$ ls -la /opt/bitwarden/bwdata/ssl/<your_domain_name>/
Configure Bitwarden
IMPORTANT Perform following actions as bitwarden user.
Configure following values in ./bwdata/env/global.override.env file for proper email sending by Bitwarden. The following example will use values for Gmail.
$ vim /opt/bitwarden/bwdata/env/global.override.env
globalSettings__mail__smtp__host=smtp.gmail.com
globalSettings__mail__smtp__port=587
globalSettings__mail__smtp__ssl=true
globalSettings__mail__smtp__username=[email protected]
globalSettings__mail__smtp__password=my-gmail-password
(...)
adminSettings__admins=[email protected]
adminSettings__admins - this is email address that has access to Bitwarden System Administrator Portal. You access System Administrator Portal through address https://<your_domain_name>/admin.
Note
When using Gmail with self-hosted services, it’s best to enable two-factor authentication on your Gmail account and create an application password for each service. Then, use this application password as your Gmail password when configuring the service.
Important note: When you change your Gmail account password, all application passwords will be deleted and you need to recreate them manually.
Run Bitwarden
IMPORTANT Perform following actions as bitwarden user.
After configuring Bitwarden you can start it. It will pull Docker images needed to run Bitwarden and run them as containers.
$ /opt/bitwarden/bitwarden.sh start
After the command completes, verify that the Docker containers are running:
$ docker ps
Firewall configuration
Currently on Linux, Docker manage firewall rules using iptables and dynamically creates rules that are needs at the system start. Debian by default uses nftables instead of iptables, but have compatibility layer enabled, so iptablescommands are still valid and Docker works correctly.
Docker creates its own firewall rules (such as forwarding traffic between containers), so it's better to disable your custom firewall rules in the system, and instead enable firewall for the whole virtual machine in Proxmox. For Bitwarden you would only need to open port 443 on the firewall.
Bitwarden web interface
Now you can access your Bitwarden instance at the following address: https://<your_domain_name>.
Register new account, log in and you have Bitwarden ready to use.
Update Bitwarden
IMPORTANT Change user to bitwarden:
$ su - bitwarden
Run following commands to update all containers and the database that Bitwarden uses:
$ /opt/bitwarden/bitwarden.sh updateself
$ /opt/bitwarden/bitwarden.sh update
Old Docker images can use some space so you can check it by using command:
$ docker images
To remove old images that are no longer used you can use command:
$ docker image prune
Update Docker using unattended-upgrades
Install unattended-upgrades package:
$ sudo apt install unattended-upgrades
You can enable Docker automatic upgrades by editing file /etc/apt/apt.conf.d/50unattended-upgrades:
$ sudo vim /etc/apt/apt.conf.d/50unattended-upgrades
(...)
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${distro_codename}-updates";
"origin=Debian,codename=${distro_codename},label=Debian";
"origin=Debian,codename=${distro_codename},label=Debian-Security";
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
// --- Add these lines ---
// Docker repository
"origin=Docker,suite=${distro_codename},component=stable";
// -----------------------
(...)
};
(...)
Test if config is correct by running command:
$ sudo unattended-upgrades --dry-run --debug
If you see the following line in the command output, then there's likely a problem with your configuration (ensure you didn't make any typos):
Marking not allowed <apt_pkg.PackageFile object: filename:'/var/lib/apt/lists/download.docker.com_linux_debian_dists_bookworm_stable_binary-amd64_Packages' a=bookworm,c=stable,v=,o=Docker,l=Docker CE arch='amd64' site='download.docker.com' IndexType='Debian Package Index' Size=230910 ID:12>