Recently, I needed to use TOTP, but Bitwarden, which I have been using all along, requires a subscription fee ($10/year). I felt it was unnecessary to pay so much for this feature, and I didn’t want to switch to another Google Authenticator.
So, I looked into it and found that you can self-host Bitwarden to use TOTP for free, and deploying it with Docker is very quick.
Effect

Reposted Tutorial
This article is transcribed by SimpRead, original source blog.hentioe.dev
Preface
Bitwarden is a popular open-source password manager that provides password management services via browser extensions, web, Windows/Linux/Android/iOS clients, and so on. I have been a long-time user and subscribed to its paid service. But recently I changed my mind, planning to cancel the subscription and deploy it on my own server.
This article introduces how to self-host Bitwarden’s official server, third-party servers, and how to enable TOTP for free. This is a beginner-friendly tutorial. The process is very simple!
Reasons
I have several reasons for self-hosting the Bitwarden server:
- To control my own passwords instead of entrusting others.
- To implement more complex multi-layer backups myself to reduce the risk of password loss.
- To use the TOTP feature for free.
Personally, I still recommend ordinary users who need TOTP to use Bitwarden’s official paid service, since the cost is lower and it supports open-source software.
Prerequisites
You need a server, SSL/TLS certificates, a domain name, and some basic Docker usage knowledge. For users who want to cancel paid services, the cost of meeting these prerequisites may not be cheaper than Bitwarden’s official subscription. It’s actually suitable for those who already have server resources.
Deploying Server
This section introduces how to deploy the official server, third-party server (Vaultwarden), and corresponding Docker Compose configuration templates. Let me first introduce the differences between two clients:
- Official Server:
- Updates from the official source, compatibility guaranteed.
- Requires payment to use TOTP, organizations, etc.
- Supports features like SSO, LDAP needed by large organizations.
- Large memory usage.
- Third-party (Vaultwarden):
- Very low memory usage.
- Free use of TOTP, organizations, etc.
- Does not include features like SSO, LDAP needed by large organizations.
- Updates may be delayed causing compatibility issues.
Official Server
Bitwarden’s official server deployment offers two schemes. The old scheme is especially complicated and not covered here. The new scheme is still in beta but usable. We will deploy the new scheme, which only requires one image and is very simple.
Create the bitwarden/docker-compose.yml file:
services:
self-host:
image: bitwarden/self-host:beta
restart: always
env_file:
- .env
ports:
- 5500:8080
volumes:
- ./_data:/etc/bitwarden
environment:
BW_DB_PROVIDER: sqlite
BW_DB_FILE: /etc/bitwarden/db.sqlite3
Create the .env file:
BW_INSTALLATION_ID=
BW_INSTALLATION_KEY=
BW_DOMAIN=
globalSettings__disableUserRegistration=false
adminSettings__admins=
Before deployment, you need to fill in the variables in .env as follows:
BW_INSTALLATION_ID: Your installation ID, which needs to be applied from Bitwarden’s official website.BW_INSTALLATION_KEY: Your installation key, which also needs to be applied from Bitwarden’s official website.BW_DOMAIN: Your domain name, e.g.,example.com.globalSettings__disableUserRegistration: Whether to disable user registration. Set tofalseduring initial deployment.adminSettings__admins: Administrator’s email address.
You need to visit this page and submit your email to get the installation ID and KEY for free. Copy them into the respective variables in .env.
After filling in all variables, run docker compose up -d to start the service. Then run docker compose logs -f to check the logs:
self-host-1 | Adding group `bitwarden' (GID 1000) ...
self-host-1 | Done.
self-host-1 | Adding user `bitwarden' ...
self-host-1 | Adding new user `bitwarden' (1000) with group `bitwarden (1000)' ...
self-host-1 | Not creating home directory `/home/bitwarden'.
self-host-1 | Adding new user `bitwarden' to supplemental / extra groups `users' ...
self-host-1 | Adding user `bitwarden' to group `users' ...
self-host-1 | 2024-09-12 05:30:17,137 INFO Included extra file "/etc/supervisor.d/admin.ini" during parsing
self-host-1 | 2024-09-12 05:30:17,137 INFO Included extra file "/etc/supervisor.d/api.ini" during parsing
self-host-1 | 2024-09-12 05:30:17,137 INFO Included extra file "/etc/supervisor.d/events.ini" during parsing
self-host-1 | 2024-09-12 05:30:17,137 INFO Included extra file "/etc/supervisor.d/icons.ini" during parsing
self-host-1 | 2024-09-12 05:30:17,137 INFO Included extra file "/etc/supervisor.d/identity.ini" during parsing
self-host-1 | 2024-09-12 05:30:17,137 INFO Included extra file "/etc/supervisor.d/nginx.ini" during parsing
self-host-1 | 2024-09-12 05:30:17,137 INFO Included extra file "/etc/supervisor.d/notifications.ini" during parsing
self-host-1 | 2024-09-12 05:30:17,137 INFO Included extra file "/etc/supervisor.d/scim.ini" during parsing
self-host-1 | 2024-09-12 05:30:17,137 INFO Included extra file "/etc/supervisor.d/sso.ini" during parsing
self-host-1 | 2024-09-12 05:30:17,148 INFO RPC interface 'supervisor' initialized
self-host-1 | 2024-09-12 05:30:17,148 CRIT Server 'unix_http_server' running without any HTTP authentication checking
self-host-1 | 2024-09-12 05:30:17,148 INFO supervisord started with pid 1
self-host-1 | 2024-09-12 05:30:18,155 INFO spawned: 'identity' with pid 65
self-host-1 | 2024-09-12 05:30:18,159 INFO spawned: 'admin' with pid 66
self-host-1 | 2024-09-12 05:30:18,163 INFO spawned: 'api' with pid 67
self-host-1 | 2024-09-12 05:30:18,167 INFO spawned: 'icons' with pid 68
self-host-1 | 2024-09-12 05:30:18,171 INFO spawned: 'nginx' with pid 69
self-host-1 | 2024-09-12 05:30:18,175 INFO spawned: 'notifications' with pid 70
Wait a moment. If the logs show as above, the deployment was successful. If some logs start with WARN exited, that means some services failed to start. In that case, you may need to look for solutions in issues.
The above configuration template uses the SQLite database because it is very lightweight and fully sufficient for small-scale use.
After deployment according to the above configuration, data will be saved in the _data directory. To migrate, first stop the service and then fully migrate the data directory. Before access, reverse proxy the 5500 port and configure the SSL certificates. Finally, refer to the Registration and Usage section.
From my experience, the official server uses a huge amount of memory (ARM64), which is not suitable for servers with limited resources. To write this article, I deployed a standalone instance, and even with no access, it used more than 1GB of memory. I strongly recommend trying Vaultwarden, which will be introduced below.
Third-party (Vaultwarden)
Vaultwarden is a third-party server implementation of Bitwarden that is highly compatible with the official client and has almost full feature availability. Compared to the official server, it has a very simple architecture, is implemented in Rust, and uses minimal resources. I personally use Vaultwarden instead of the official server.
Create the vaultwarden/docker-compose.yml file:
services:
server:
image: vaultwarden/server
restart: always
ports:
- 5600:80
environment:
- DOMAIN=${DOMAIN}
- SIGNUPS_ALLOWED=${SIGNUPS_ALLOWED}
- ORG_CREATION_USERS=${ORG_CREATION_USERS}
- ADMIN_TOKEN=${ADMIN_TOKEN}
volumes:
- ./_data:/data/
Create the .env file:
DOMAIN=
SIGNUPS_ALLOWED=
ORG_CREATION_USERS=
ADMIN_TOKEN=
Before deployment, you need to fill in variables in .env as follows:
DOMAIN: Your domain (actually including the protocol), e.g.,https://example.com.SIGNUPS_ALLOWED: Whether to disable user registration. Set tofalseduring initial deployment.ORG_CREATION_USERS: Users allowed to create organizations.ADMIN_TOKEN: Administrator Token, recommended to be a PHC string hashed by Argon2.
Keep the ADMIN_TOKEN variable empty at first because we will generate it by running a command inside the container later. After filling in other variables, run docker compose up -d to start the service. Then run docker compose logs -f to check the logs:
server-1 | /--------------------------------------------------------------------\
server-1 | | Starting Vaultwarden |
server-1 | | Version 1.32.0 |
server-1 | |--------------------------------------------------------------------|
server-1 | | This is an *unofficial* Bitwarden implementation, DO NOT use the |
server-1 | | official channels to report bugs/features, regardless of client. |
server-1 | | Send usage/configuration questions or feature requests to: |
server-1 | | https://github.com/dani-garcia/vaultwarden/discussions or |
server-1 | | https://vaultwarden.discourse.group/ |
server-1 | | Report suspected bugs/issues in the software itself at: |
server-1 | | https://github.com/dani-garcia/vaultwarden/issues/new |
server-1 | \--------------------------------------------------------------------/
server-1 |
server-1 | [2024-09-12 06:02:16.966][start][INFO] Rocket has launched from http://0.0.0.0:80
This indicates successful deployment. Next, we execute the command in the container to generate the ADMIN_TOKEN:
docker compose exec server /vaultwarden hash
Enter a password at least 8 characters long as prompted, and you will get a valid ADMIN_TOKEN. Copy it into .env, then run docker compose up -d again to update the deployment.
The above configuration template does not specify an external database, and it defaults to SQLite. It is very lightweight and fully sufficient for small-scale use.
After deployment according to the above configuration, data will be saved in the _data directory. To migrate, first stop the service and then fully migrate the data directory. Before access, reverse proxy the 5600 port and configure the SSL certificate. Finally, refer to the Registration and Usage section.
Vaultwarden’s resource usage is very low; in my long-term personal use, it only occupies a few dozen MB.
Registration and Usage
After ensuring successful deployment, visit your bound domain and go to the registration page to create your user account. After completion, modify the .env file to disable registration:
Official Server:
globalSettings__disableUserRegistration=true
Vaultwarden:
SIGNUPS_ALLOWED=false
Run docker compose up -d again to update the deployment.
This is necessary for personal use unless you want your self-hosted server to be a public service available to everyone.
Client Configuration
Go to Bitwarden’s login page and click on “Self-Hosted”:

If you have already logged in to a Bitwarden account, you don’t need to log out. Click your avatar to add another account.
After entering the “Self-Hosted” page, enter your domain and click “Log In”:

After completing the above setup and saving, you can use the account from your self-hosted server to log in and use the service. If it is the official server, you still need to purchase the ID/KEY to use the TOTP feature. For Vaultwarden, you can use the TOTP feature for free (automatically enabled in the client).
Import Data
Use the Bitwarden client or web version to export your vault as JSON/CSV format, then log in to your self-hosted backend page and import it. Of course, you do not have to completely disable Bitwarden’s official service; it can serve as a backup in case the self-hosted service breaks. Later, this article will continue to introduce how to automatically sync passwords to the official service.
Sync Data
To be updated.
Conclusion
This is the tutorial for deploying the Bitwarden server. I personally prefer open-source software, so I insist on using Bitwarden instead of commercial products like LastPass, KeepPass, or 1Password. Some commercial products even have had data leakage incidents. However, for self-hosting, server security should also be taken seriously.For users who want to use TOTP for free but do not have the conditions to self-host, I do not recommend choosing free third-party public services because they are rarely trustworthy. I suggest using an independent TOTP manager (such as Ente Auth), which is usually free.