Updated: 2024-04-16
Overview
This article looks at the Bitwarden online password manager - why it is useful, and how to run it on your own server infrastructure as an alternative to using a hosted service.
The self-hosting part of this article talks primarily about using bitwarden (or the vaultwarden open-source reimplementation) for a single person or small group. If using it for a larger company, then the hosted service is strongly recommended; my employer uses it and we’ve had good experiences. In fact, as the hosted service is free for single-person use, and extremely cheap for even families, the primary reason to host it yourself is pure stubborn-ness and the open-source principle.
The Problem
We all need to log on to multiple sites around the internet.
For a few people, “single sign on” with a well-known organisation works; eg once you have a google login, many other sites offer “log in via google” option. However that provides a lot of undesirable information to your central credential provider. And not all sites support such integration.
The alternative is to manage multiple passwords, as reusing the same password in multiple places is a bad idea. However those passwords soon get difficult to keep track of - the best solution is to have some central database of passwords somewhere.
Most web browsers offer a password database, and provide nice integration with websites - autofilling login fields, saving passwords when we create new accounts somewhere, and updating the password database entries when we change passwords via HTML forms. However that only works when using that same browser on that same machine - and needs to be backed up. Browsers have started to offer “password sync” so that the same passwords are both backed up and available from the same browser on other machines - but:
- you are required to sign up for the sync service
- using a remote service might provide undesirable info to the provider of that service, depending on the implementation (Mozilla’s “firefox sync” is currently ok, not sure about others)
- it is not portable across browsers
- doesn’t help with credentials for things other than web-sites
- and raises the risk of all your passwords being stolen at once (again, dependent on implementation)
Browser-based password storage is also vulnerable to security bugs - browsers are complex applications with a poor history of security holes.
An alternative to letting the browser store passwords is to install a password manager application locally on your machine. This makes credentials available for more than websites, but:
- is less convenient to use (need to regularly copy-and-paste between browser and password manager app)
- is not cross-machine
- and needs to be backed up
At least these dedicated apps are more secure than browsers, being much simpler and less exposed to the internet.
Various “hosted password repos” exist (lastpass etc) which allow credentials to be stored “in the cloud”. They get backed up, are available on any machine and not just from browsers. Typically these providers also provide browser-plugins to make saving/updating credentials easy. However this puts a lot of trust in the repo provider. These services are also subscription-based.
Bitwarden is one of these “hosted password repos”, but one with several advantages that are discussed in the next section.
About Bitwarden
Bitwarden is a service for hosting credentials, similar to the “hosted password repos” described above, but with a difference:
- it is completely open-source (both client and server side)
- the protocol is documented (at least indirectly; people have read the code and written documentation for it)
- the protocol ensures the server never sees unencrypted credentials (client code does encryption/decryption)
The Bitwarden site is very professional, as are the (wide) selection of available client software. The company backing it may be small, but their work seems very good.
The Bitwarden code can be divided into two parts:
- server-side
- client-side
The server-side code consists of a single application, providing a REST api for reading/writing credentials.
The client-side code can be divided into:
- native graphical and commandline desktop apps for various OSes (linux, mac, windows)
- cross-platform commandline app
- web-based desktop app (known as “vault”)
- mobile apps
- browser plugins
The desktop apps allow users to configure their “account”, save/search/view credentials, and various other options. These apps make REST calls to the server component to actually read/write credentials. These apps are available in both graphical and commandline versions - and nicely, all apps support configuring the server address, making it easy to use an alternative back-end.
The cross-platform commandline app is a nodejs-based tool that provides the same functionality as the native desktop apps, but does not offer a GUI. As with all other desktop apps, it performs encryption/decryption locally and makes calls to the central REST server to persist data.
The web-based desktop app is a pure javascript “single page app” that provides the same functionality as the native GUI desktop apps, and also makes REST calls to the server component in the same way. The javascript, html, etc. is served by the server-side component (acting as a simple HTTP server of static files). Note that there is no logic on the web-server-side; the server simply provides the javascript files which are then executed client-side within the user’s browser.
Browser plugins provide “auto-fill” and “auto-save” integration by calling the REST api of the Bitwarden server (whichever address and port is configured). The Firefox browser plugin is cross-platform; not sure about plugins for other browsers.
The official (open-source) server-side component from bitwarden.com
is implemented in C# and relies on Microsoft-SQLServer as a database. The Bitwarden developers have (somehow) created docker containers which wrap these and can be executed on Linux. However these containers are very resource-intensive. The original developers probably do not care about resources - they use these containers to run their bitwarden.com
hosting operation for over 100,000 customers - and thus requiring a large database is not a significant problem. They kindly offer the containers for others to use - but don’t care that this approach is not convenient for people wanting to host an instance for a small number of users (possibly one).
Fortunately, various people have implemented alternative less-resource-intensive versions of the server-side rest component: rubywarden, bitwarden-go and vaultwarden (formerly called bitwarden_rs
). The vaultwarden
(Rust-based) implementation seems to be the most active and the least resource-intensive. The remainder of this article looks at how to install this on Linux.
Note: the “team” and “enterprise” accounts offered for the official hosted service have a few extra features (groups, audit-logs, etc) which are not available in their free version. Whether these are available in different self-hosted variants depends on how complete the “back end” implementation is. The vaultwarden
implementation (discussed later) appears to implement all of the commercial features.
Using the Hosted Service
Before looking at installing a Vaultwarden server, it is worth looking at the hosted offering available from bitwarden.com
. They offer truly free accounts for single users - with all necessary features. And what is special about Bitwarden is that all data is encrypted before being sent to the hosted service - and thus you do not need to worry about hacks of the central service, or even dishonest sysadmins, as long as:
- you use a reasonable master password
- and the client app you are using has not been modified
Paid plans are available for “family” and “business” use. The most important feature of these plans is the ability to share a password across multiple accounts - updating it from one account will update it in all accounts, even though the central service does not know the master passwords of any account.
The prices are extremely reasonable. Currently:
- personal account, including sharing with 1 other user: free
- premium personal account: $10/year
- family account, with up to 5 users: US$3.33/month ($40 per year)
- team account: US$3/user/month
- enterprise account: US$6/user/month.
Despite the good security design of Bitwarden, I still like the idea of hosting my passwords myself - so on to the rest of the article. To repeat the warning from the introduction section: the “organisations” feature needed to share passwords between users (family or team) is not available in the open-source code from company Bitwarden. The open-source code communicates with bitwarden.com
for this feature and requires a license.
About the VaultWarden alternative implementation
As noted above, the standard back-end (rest server) implementation uses c# and sqlserver, and is distributed as a set of docker containers that have relatively high resource requirements. This is not very friendly for self-hosting, at least for small-scale use.
The vaultwarden project reimplements the back-end in a simpler manner. vaultwarden
can use an SQLite embedded database, ie no separate “database server” process is needed and data is stored in a single file on the server local filesystem. vaultwarden
is a single process that listens for HTTP requests on a single port, responding to REST api URLs. It can also act as a simple static HTTP web server in order to serve the static files that make up the “vault” web-based client application, if configured to do so.
According to its author, vaultwarden
requires only 10MB of ram.
Requirements
The vaultwarden server should be accessible only over https; this is particularly important when serving the “vault” web client app, as any attacker who can modify that code effectively has access to passwords.
vaultwarden
may be configured directly with appropriate HTTPS certificates, or it may be configured to listen on a local port with a reverse-proxy-server on the same machine terminating https requests and forwarding them as http to vaultwarden
. I personally use nginx already, and so use it as a front-end to vaultwarden.
When using https, a certificate is required. One can be obtained for free via “letsencrypt”. HTTPS also requires that the host has a suitable DNS name.
vaultwarden
should of course be started via systemd, and run using a dedicated user account.
Installing Vaultwarden via Docker
There is a Docker image for the official Bitwarden version (ie not the alternative rust-based server). However this image is very heavy on resources; among other things it starts an instance of Microsoft SQLServer internally (!). That’s not an option for me, due to resource usage.
When I wrote the first version of this article, vaultwarden
(then bitwarden_rs
) was only available in source form. The github project now provides prebuilt Docker images which presumably make installation quite easy. See the vaultwarden install page for details - a page that did not exist when this article was first written, and which actually does a very good job of documenting the install process.
I’m running a very low-resource (cheap) VPS for my personal use (this website, email, and a few other things). While a container is not too heavy, installing locally is even lighter and this is what is described in the next section.
Installing Vaultwarden without Docker
The code is not available as a prebuilt download; it is necessary to build from source. However that is not too hard.
The following build-steps can be performed as any standard user on the host system. Sudo rights will be needed to actually install the results.
Install prerequisites
apt install git
apt install libssl-dev
apt install pkg-config
apt install gcc
Build the Vault Web Client (optional)
As noted earlier, the Vault web client is optional; if all users are content to install native desktop clients or browser-plugins to interact with the REST back-end, then there is no need to build the Vault webserver component. Vault is simply a set of static files that are served up to a user’s browser which then interact with the server via rest calls just like the standard browser-native-plugin interfaces do; Vault has no “back end” components of its own.
Vault is implemented using Microsoft Typescript, and various other technologies which require the “source code” to be processed to generate javascript/html/css/etc. The resulting javascript is also compressed (minimised) in the usual way. There is therefore a “build” process necessary, even though the result is a set of static files which the rest-server simply provides to the user on request. Once loaded into the user’s browser, the code acts just like any other Bitwarden desktop client application.
Although the build process uses npm and nodejs, they are not needed at runtime.
Clone git repository for the “web vault” (which is a very lightly modified fork of the Bitwarden web client):
git clone https://github.com/dani-garcia/bw_web_builds.git
cd web
git tag
- `git checkout … # most recent tag
Set up build-time tool nodejs (as a normal user, not system-wide):
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
- logout/login to activate the software installed above
nvm install --lts
Build:
npm run sub:init
npm install
npm run dist
Build the Vaultwarden Alternative Server
Install rustc/cargo if you don’t already have it (as a normal user, not system-wide):
curl https://sh.rustup.rs -sSf | sh
- logout/login to activate the software installed above
Clone git repository vaultwarden
(choose latest tag):
git clone https://github.com/dani-garcia/vaultwarden.git
cd vaultwarden
git tag
git checkout ... # most recent tag
Build:
cargo build --release --features sqlite
Note that although vaultwarden needs few resources to run, the build requires a reasonable amount of RAM: certainly > 4GB.
Install Server and Vault
Rather than run it from where it was built, it seems best to put it somewhere “official”. And in fact, given it requires few resources to run but quite a lot to build, it makes sense to build on one system and just copy the binary to the system (typically a VM) that will run it.
# choose user and install-dir
USER=vaultwarden
DEST=/var/lib/vaultwarden
# create user for vaultwarden to run as
adduser --system --no-create-home --disabled-login $USER
# move required files to $DEST
mkdir $DEST
cp -r target/release/vaultwarden $DEST
cp .env.template $DEST/.env
mkdir $DEST/data
cp -r ../web/build $DEST/web-vault # -- only if you have built the web-client, and have enabled it in the .env file
chown -R $USER $DEST
Configure the Server
Configuration of the server can be done in two different ways:
- via environment variables
- via data in the database, configured via a commandline tool or the admin web interface
File “.env
” in the installation dir created above (copied from env.template
in the checkout of vaultwarden
) defines all the available config options (env-vars). Paths are relative.
See the vaultwarden README.md for more information on config options.
Notes:
- defaults to running on localhost:8000, ie not accessible over internet
- the server can be configured to listen on an external interface and serve requests directly but the best option is probably to use a reverse proxy to forward to the local daemon, eg nginx.
Edit $DEST/.env
and set:
- update ROCKET (rust http library) settings:
- when using a reverse-proxy, set
ROCKET_ADDRESS=localhost
so the server only listens on the internal network interface - optionally, update
ROCKET_PORT
to change the default port on which the server runs
- when using a reverse-proxy, set
- set DOMAIN to
https://{yourdomain}
Config item ROCKET_TLS
is needed only if you are exposing vaultwarden
directly to the internet; when using a reverse-proxy (eg nginx) the certificate configuration is in the reverse-proxy and not in vaultwarden
.
vaultwarden
provides optional support for web-socket connections from clients to servers. Web socket connections can run in two ways: on a dedicated port, or tunnelled over HTTP. The vaultwarden
server supports only the dedicated port approach, but a reverse-proxy can be used to tunnel the connections - ie accept a connection on the standard https port then forward to the websocket port on vaultwarden
. Config options WEBSOCKET_ENABLED
, WEBSOCKET_ADDRESS
and WEBSOCKET_PORT
should be appropriately set in .env
and then the reverse-proxy configured to match; see here for nginx config.
By default config-item SIGNUPS_ALLOWED
is enabled, allowing anyone on the internet who can reach the vault site (or the appropriate REST endpoint) to create an account. When this is disabled, users can only create an account when “invited” (sadly the UI is somewhat confusing when account-creation is disabled; everything works until the last step, then fails). If you are willing to purchase a license from bitwarden.com
for a Family or Commercial organisation, then you can (as a normal user) create an organisation within a project and any member of that organisation can then invite other users even when SIGNUPS_ALLOWED
is disabled. Otherwise, users can be invited by logging into the Vault admin interface (/admin) - assuming bitwarden has been configured with a valid outbound email account. See config items SMTP_*
for email configuration.
Configure nginx reverse proxy
This assumes you have used letsencrypt to generate a certificate (eg into /etc/letsencrypt/live/{domain}/fullchain.pem)
cat > /etc/nginx/sites-available/bitwarden.your.domain <<EOF
server {
# SSL configuration
listen 8443 ssl http2;
listen [::]:8443 ssl http2;
ssl_certificate /etc/letsencrypt/live/your.domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your.domain/privkey.pem;
# ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# ssl_ciphers HIGH:!aNULL:!MD5;
server_name your.domain;
location / {
proxy_pass http://localhost:8000/;
}
# websockets support
location /notifications/hub {
proxy_pass http://localhost:3012;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /notifications/hub/negotiate {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://localhost:8000;
}
}
EOF
Running on localhost using http theoretically allows local attackers to make requests to the server, but in practice that seems a very small vulnerability as Bitwarden does not store any unencrypted data - it never has any master keys itself. If you have a different opinion, please let me know in the comments.
For configuration examples for other reverse-proxies, see file PROXY.md in the vaultwarden
git repo.
As noted in vaultwarden
documentation, the use of a separate port for websockets is due to current limitations in the Rocket library; this will not be necessary in some future version.
The proxy_set_header
commands are primarily to ensure that when vaultwarden
logs authentication failures, then the IP address it logs is the real external one, and not localhost
. This allows fail2ban
to be used (see later).
Set up Systemd
Ensure the vaultwarden daemon starts on server boot.
mkdir /etc/vaultwarden
cat > /etc/vaultwarden/vaultwarden.conf <<EOF
SERVER_ADMIN_EMAIL=vwadmin@your.domain
EOF
cat > /etc/systemd/system/vaultwarden.service <<EOF
# See https://www.freedesktop.org/software/systemd/man/systemd.unit.html for details
[Unit]
Description=vaultwarden server
After=network.target auditd.service
[Service]
User=vaultwarden
Group=nogroup
# todo: use RootDirectory to jail the app
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/lib/vaultwarden/data
PrivateTmp=true
EnvironmentFile=/etc/vaultwarden/vaultwarden.conf
WorkingDirectory=/var/lib/vaultwarden
ExecStart=/var/lib/vaultwarden/vaultwarden
Restart=on-failure
RestartPreventExitStatus=255
Type=simple
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable vaultwarden.service --now
journalctl --unit=vaultwarden.service
The SERVER_ADMIN_EMAIL
variable defines an address which you can use with “create account” even when configuration has disabled account-creation. That means you should use some Bitwarden client app to create this account (set initial password) immediately after starting the server. This account-id should only be used for administration purposes - and to invite new users when account-creation has been disabled.
If vaultwarden
logging config (in .env
) is still using the defaults (log to STDOUT) then any log output is visible using journalctl --unit vaultwarden
.
The Admin Portal
In configuration file .env
, option ADMIN_TOKEN
must be explicitly set to a suitable value to enable the admin interface. After this is set (and vaultwarden restarted if needed), URI “/admin” can be used to log in using the TOKEN value specified in the config-file.
The admin-portal can be used to set configuration items, but these are also mostly (all?) settable directly in file .env
anyway. More useful is the user-administration tab, and in particular the ability to send invites to users.
Using Fail2Ban
The fail2ban monitoring app can be used to prevent attackers from making guessing attacks on the Vault login page (or vaultwarden
rest-service login endpoint); login failures are written to a logfile and fail2ban can be set up to scan this logfile, detect the failures, and update the local firewall to (temporarily) block the source IP address from which the login failures came. The temporary point is important in case you accidentally make a few incorrect login attempts yourself!
Fail2ban must be set up to scan whatever file vaultwarden
has been configured to write to, eg /var/log/syslog
or some dedicated file. It can also scan the systemd journal. The exact pattern to be matched depends somewhat on how logging is configured. The current version of fail2ban (included in Ubuntu LTS 20.04) includes a filter-file for bitwarden - but unfortunately it doesn’t match what vaultwarden
generates. Here’s an alternative file that works for syslog (at the moment anyway).
cat >>/etc/fail2ban/filter.d/vaultwarden.conf <<EOF
# Fail2Ban filter for vaultwarden
# Detecting unauthorized access
# Typically logged in /var/log/messages syslog
# Message format to match:
# somedate somehost vaultwarden[somepid]: [somedate][error][ERROR] Username or password is incorrect. Try again. IP: 1.2.3.4. Username: no@body.
[INCLUDES]
# Read syslog common prefixes
before = common.conf
[DEFAULT]
vwdate = \[.{23}\]
[Definition]
_daemon = vaultwarden
failregex = ^%(__prefix_line)s%(vwdate)s\[error\]\[ERROR\] Username or password is incorrect. Try again. IP: <HOST>\. Username: \S+.\s*$
ignoreregex =
EOF
cat >>/etc/fail2ban/jail.d/local.conf <<EOF
[vaultwarden]
enabled = true
logpath = /var/log/syslog
EOF
systemctl restart fail2ban
tail -f /var/log/fail2ban.log # and now do some incorrect logins...
Unfortunately, syslog by default converts multiple identical messages occurring within a short time-period into the following form:
{prefix}: message repeated 5 times: [ ERROR: Username or password is incorrect. Try again. IP: 1.2.3.4. Username: no@body.]
The only current solution for working with fail2ban is to disable this syslog option:
sed -i 's/RepeatedMsgReduction\ on/RepeatedMsgReduction\ off/' /etc/rsyslog.conf
- systemctl restart rsyslog
The Client/Server Protocol
TODO: write some info on how Bitwarden saves passwords and interacts with clients.
The Browser Plugin
Plugins are available for Firefox, Chrome and Safari. These integrate with the browser to auto-save and auto-fill passwords. When installed, a Bitwarden icon appears next to the nav-bar in the browser; clicking on this gives access to the same features available in the bitwarden desktop client apps. As in the desktop apps, the “gear” icon gives access to the config settings - including setting the path to the bitwarden server.
The built-in password manager in many browsers uses pop-up windows to ask whether to save a new password, etc. - bitwarden instead modifies the HTML to add a prompt at the top of the page. While elegant, I have had problems with one site where the login page keeps refreshing (multiple times per second) until this bar is “dismissed” - I suspect that javascript in this page acts badly when bitwarden changes the page dom.
Reminder: because vaultwarden offers the same server API as bitwarden’s own server implementation, the same clients can be used - including the browser plugin.
Notes
When you “log in” to vault (or any other client), you are prompted to provide your “master password”. However this password never leaves your browser; the login page uses complex javascript to compute a hash of the master password which verifies your identity but is not sufficient to decrypt any secrets you have stored in bitwarden. This is in effect “log in with password” where the login-pwd can be derived from the master-pwd but not the other way around.
Bitwarden supports both passwords and TOTP codes. In fact, any entry consists of (user-email, encrypted-url) => (set of encrypted properties)
, where the properties can be anything you need.
There is no need to take particular care when backing up the credentials DB - that is fully encrypted with keys that only the client apps have.
The user interface includes a number of options like “number of iterations for PBKDF”, etc. These options are stored in the central repository so they are available to each client, but are implemented within the client itself.
The Vault web interface is nicely implemented, and elegant to use.
All entries in the repository can be exported if desired, ie there is no “lock in”. Import is supported for the main password manager formats. There is also a “password generator” available.
Sharing of passwords between multiple users is done by defining an “organisation” and then linking multiple users to the same organisation. Each “organisation” effectively has its own vault, and its own copy of the relevant data. Each user who is a “member” of the organisation effectively has access to the “master password” of that organisation. There is a complex structure of rights that an “organisation admin” can configure for users.
The Vault interface for creating organisations lists various free/paid plans. However the vaultwarden
implementation supports all enterprise features for all organisation types - so when using vaultwarden
as server, just choose the “free” tier. Fixing this UI quirk would mean forking the Vault code, which is not worth the effort.
There is a command-line tool for interacting with the Bitwarden server; this is implemented in javascript and can be installed with the nodejs package-manager “npm”.
Each item stored has:
- a “name” (descriptive name for showing in the interface)
- a “url” (can be searched for)
- the username for the service to be logged into
- the password itself, and/or a TOTP code
- a free-text notes field
- zero or more “custom fields” associated with the entry
- a “folder” for organising (grouping) entries
Searches for entries is done client-side, not server-side: the entire repo is “synced” to the local machine and then a search is done locally. Modifications to data include a synchronous update of the central data.
Bitwarden code manages master passwords responsibly; when you provide such a password to any bitwarden app, it immediately derives some other more temporary/limited token from that password and then erases the original master password from memory. In particular, communication with the REST service is done via a “session key” which has a limited time duration; after this has expired the account is considered “locked” and a new session key must be created (by a client app, using the master password). Login is a somewhat different process from a session key - it authenticates against the bitwarden server.
The fact that the rest-server does not actually have access to any credentials means that the fact that vaultwarden
is not from a well-known source is not a major concern. Only clients can leak decrypted information.
A Bitwarden server uses WebSockets to notify client applications of changes. The vaultwarden
server itself provides WebSocket support on a different port than its REST services (3012 by default) - though this different port is not visible if using a reverse_proxy
in front (eg the nginx config shown above). This means that client apps connect to bitwarden rest server on the relevant port and stay connected for long periods of time. This functionality is not critical, eg the CLI client does not do this.
Bitwarden’s standard server can interact with LetsEncrypt to allocate certificates automatically; however this requires that it runs on ports 80/443 on its host. Certificate renewal occurs only each time the server is started, ie it is necessary to restart the server at least every 90 days (duration of a letsencrypt certificate). It isn’t clear whether the vaultwarden
implementation also supports this.