Self-hosting Bitwarden / VaultWarden on FreeBSD

The time has come for me to consider another application for my TOTP data (think 6-digit codes produced by Google Authenticator or an RSA device. I’ve been using an app called 2STP – I have long liked it. Support for it ended about 7 years ago, yet it continued to slug along on my phone and on my watch.

Recently, it stopped working on my watch. That was the tipping point.

I decided to move to BitWarden. I’ll use BitWarden apps on my phone and watch, and use VaultWarden for the storage engine. VaultWarden is a “Bitwarden server API implementation written in Rust compatible with upstream Bitwarden clients”.

In this post:

  • FreeBSD 14.1
  • vaultwarden 1.32.0_2
  • BitWarden 2024.7.1 (8086)

I also referred to a blog post by Ben Lavery-Griffiths.

Installing

I created a new jail dedicated to this task. Those steps are outside the scope of this post.

When installing, I did the following

pkg install vaultwarden
cp /usr/local/etc/rc.conf.d/vaultwarden.sample /usr/local/etc/rc.conf.d/vaultwarden

/usr/local/etc/rc.conf.d/vaultwarden contains configuration settings for vaultwarden – I’m not sure how this works. It doesn’t seems to be the traditional rc.conf settings I expect to see. However, it does work. Really, all it’s doing is setting a bunch of environment variables. I suspect that’s what VaultWarden is expecting.

Things I changed in that file:

-ROCKET_ADDRESS=127.0.0.1
+ROCKET_ADDRESS=127.0.0.81

This was in a jail and I wanted something other than 127.0.0.1

Without the following, I could not create a new account.

-SIGNUPS_ALLOWED='false'
+SIGNUPS_ALLOWED='true'

I changed it back later.

I don’t run an smtp service here (it’s dma only), so I supplied values for SMTP_HOST & SMTP_FROM

I also set DOMAIN to the hostname I’m using for my service.

Starting

Here is the start:

[root@bw:~] $ service vaultwarden enable
vaultwarden enabled in /etc/rc.conf
[root@bw:~] $ service vaultwarden start
Starting vaultwarden.

From /var/log/daemon.log:

Sep 29 21:07:30 bw vaultwarden[84275]: [2024-09-29 21:07:30.121][rocket::server][WARN] Received SIGTERM. Requesting shutdown.
Sep 29 21:07:30 bw vaultwarden[84275]: [2024-09-29 21:07:30.122][vaultwarden][INFO] Vaultwarden process exited!
Sep 29 21:07:30 bw vaultwarden[3529]: /--------------------------------------------------------------------\
Sep 29 21:07:30 bw vaultwarden[3529]: |                        Starting Vaultwarden                        |
Sep 29 21:07:30 bw vaultwarden[3529]: |--------------------------------------------------------------------|
Sep 29 21:07:30 bw vaultwarden[3529]: | This is an *unofficial* Bitwarden implementation, DO NOT use the   |
Sep 29 21:07:30 bw vaultwarden[3529]: | official channels to report bugs/features, regardless of client.   |
Sep 29 21:07:30 bw vaultwarden[3529]: | Send usage/configuration questions or feature requests to:         |
Sep 29 21:07:30 bw vaultwarden[3529]: |   https://github.com/dani-garcia/vaultwarden/discussions or        |
Sep 29 21:07:30 bw vaultwarden[3529]: |   https://vaultwarden.discourse.group/                             |
Sep 29 21:07:30 bw vaultwarden[3529]: | Report suspected bugs/issues in the software itself at:            |
Sep 29 21:07:30 bw vaultwarden[3529]: |   https://github.com/dani-garcia/vaultwarden/issues/new            |
Sep 29 21:07:30 bw vaultwarden[3529]: \--------------------------------------------------------------------/
Sep 29 21:07:30 bw vaultwarden[3529]: 
Sep 29 21:07:30 bw vaultwarden[3529]: [DEPRECATED]: `SMTP_SSL` or `SMTP_EXPLICIT_TLS` is set. Please use `SMTP_SECURITY` instead.
Sep 29 21:07:30 bw vaultwarden[3529]: [2024-09-29 21:07:30.258][start][INFO] Rocket has launched from http://127.0.0.81:4567

Note the expect IP address there.

This is what is looks like:

[root@bw:~] $ ps auwwx
USER   PID %CPU %MEM    VSZ   RSS TT  STAT STARTED    TIME COMMAND
www  39535 66.8  0.0 364276 38096  -  SJ   19:52   0:01.31 /usr/local/bin/vaultwarden
root 26703  0.0  0.0  12876  2728  -  SsJ  19:29   0:00.01 /usr/sbin/syslogd -s
root 26746  0.0  0.0  12916  2500  -  IsJ  19:29   0:00.01 /usr/sbin/cron -s
www  39534  0.0  0.0  12828  2300  -  SsJ  19:52   0:00.00 daemon: /usr/local/bin/vaultwarden[39535] (daemon)
root 26759  0.0  0.0  13376  3232  2  IJ   19:29   0:00.02 /bin/sh -i
root 38654  0.0  0.0  14840  4416  2  IJ   19:51   0:00.02 zsh
root 38766  0.0  0.0  14356  4272  2  SJ   19:51   0:00.02 bash
root 39543  0.0  0.0  13452  2976  2  R+J  19:52   0:00.00 ps auwwx
[root@bw:~] $ 

TLS? Who wants TLS?

The docs recommend a reverse proxy to implement TLS. Here we go:

pkg install nginx

This is my nginx configuration:

    server {
        listen 80;
        server_name mine.example.com;
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl;
	http2 on;

        ssl_certificate     /usr/local/etc/ssl/mine.example.com.cer;
        ssl_certificate_key /usr/local/etc/ssl/mine.example.com.key;

	server_name mine.example.com;

	location / {
		proxy_pass http://127.0.0.81:4567;
	}

	access_log /var/log/nginx/mine.example.com-access.log;
	error_log  /var/log/nginx/mine.example.com-error.log;
    }

There’s that same IP address and port number again. Look for yours in the logs.

Once nginx was running, Here’s what was listening:

[root@bw:~] $ sockstat -4
USER     COMMAND    PID   FD  PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      
www      nginx      84281 8   tcp4   *:80                  *:*
www      nginx      84281 9   tcp4   *:443                 *:*
www      nginx      84281 12  tcp4   203.0.113.231:443     203.0.113.82:60294
root     nginx      84280 8   tcp4   *:80                  *:*
root     nginx      84280 9   tcp4   *:443                 *:*
www      vaultwarde 84276 34  tcp4   127.0.0.81:4567       *:*
root     syslogd    84242 5   udp4   *:514                 *:*

Here’s some of the stuff I saw in the logs, nothing relevant here, just showing.

[root@bw:/var/log] $ tail -F /var/log/daemon.log 
Sep 29 21:06:16 bw vaultwarden[84275]: [2024-09-29 21:06:16.563][request][INFO] POST /identity/accounts/register
Sep 29 21:06:16 bw vaultwarden[84275]: [2024-09-29 21:06:16.564][vaultwarden::api::core::accounts][ERROR] Registration not allowed or user already exists
Sep 29 21:06:16 bw vaultwarden[84275]: [2024-09-29 21:06:16.564][response][INFO] (identity_register) POST /identity/accounts/register => 400 Bad Request

The rest of the signup

After browsing to https:///mine.example.com, I created myself an account, and it all just worked.

In my BitWarden app, I signed in by pointing to a self hosted install, using the same URL. It also just worked.

The data to backup

The data I think I need to backup:

[root@bw:/usr/local/www/vaultwarden/data] $ ls -l
total 172
drwxr-xr-x  2 www www      2 Sep 29 19:52 attachments
-rw-r--r--  1 www www 335872 Oct  1 00:26 db.sqlite3
-rw-r--r--  1 www www  32768 Oct  1 00:26 db.sqlite3-shm
-rw-r--r--  1 www www      0 Oct  1 00:26 db.sqlite3-wal
drwxr-xr-x  2 www www      2 Sep 29 19:52 icon_cache
-rw-r--r--  1 www www   1675 Sep 29 19:52 rsa_key.pem
drwxr-xr-x  2 www www      2 Sep 29 19:52 sends
drwxr-xr-x  2 www www      2 Sep 29 19:52 tmp

I’m sure the main stuff to backup is the db.* files. Yet, I’m sure it should all be backed up. It’s about 88K – easily accommodated. See the backup docs.

I have yet to do this, and I’m relying on my jail-based backups. Oh. I see I have to implement those backups on that host.

Things to fix up

Everything logs to both /var/log/daemon.log and /var/log/messages – this is hardcoded in the rc.d script. I’d prefer that to be removed and configurable via /etc/rc.conf settings

Copying data

For copying data out of 2STP, I used https://github.com/ewdurbin/evacuate_2stp

Future use

I need to decide if I’m going to continue using this implementation or switch to letting BitWarden host my 88K of data.

Is it worth the effort required to maintain etc?

I still plan to sign up with BitWarden and give them money, regardless.

Website Pin Facebook Twitter Myspace Friendfeed Technorati del.icio.us Digg Google StumbleUpon Premium Responsive

6 thoughts on “Self-hosting Bitwarden / VaultWarden on FreeBSD”

  1. I am a long time happy user of OTP Auth [1] on iOS (also available for iPadOS and WatchOS). The features I like:
    – Folders can be created to organize the entries
    – Tabbing an entry will put the code into the clipboard (and then could be pasted on macOS)
    – Selected entries can be added to the Widget
    – In edit mode the secret and QR Code can be shown again
    – With the Pro version (in-App purchase, $3.99) custom icons or emojis can be assigned to entries
    – Password protected exports / backup
    – Supports iCloud sync (I am not using that yet)

    I just discovered that a desktop [2] version also is available. I guess I should consider that with iCloud Sync as well. I aware that this kind of is not the preferred idea of 2FA, but also the copy & paste between iPhone and Mac is probably not.
    [1] https://apps.apple.com/app/otp-auth/id659877384
    [2] https://apps.apple.com/app/otp-auth/id1471867429

      1. Hello Dan
        You are right, I also prefer to self host the things I am using. My comment was primarily to show you an alternative to 2STP you mention as introduction into this article. At my day job we also have BitWarden in use. But my personal opinion about this is, that such a setup is even more against the basic idea of having a proper 2FA setup with the one thing you should have. With any of the password safe services (even self hosted) at the end you have everything (username, password and 2FA) stored in a single location. Better not to put all eggs into one bucket.
        When I have to activate 2FA for a service, I also save the image of the QR code (and if available also the ASCII secret) into a local encrypted .dmg / .sparseimage (do backups!) to be able to scan it again later e.g. with another device (in case the main one breaks down). This also works in case for some ugly online services where only a single credential is possible but access needs to be shared between multiple people (BitWarden and friends also solve this). Scanning the initial QR later one works just fine.

        There are also command line options available to use for 2FA, a friend recommends zbarimg (FreeBSD Port: graphics/zbar) and oathtool (FreeBSD Port: security/oath-toolkit). Save the QR code and use ‘zbarimg’ do decode the secret and then use ‘oathtool -b –totp secret’ to get the 6 digits for 2FA. This could also be used with pass (https://www.passwordstore.org/) through pass-otp (https://github.com/tadfisher/pass-otp).

        Best regards,
        Fabian

    1. Just now, I tried that URL on my instance. I first had to remember the hostname for my instance. That’s how reliable it has been: I’ve use the hostname only once, to set up BitWarden, and now I’ve forgotten it. I had to ssh into the server to find out….

      When I go to /admin, without being logged in, I get:

      The admin panel is disabled, please configure the 'ADMIN_TOKEN' variable to enable it

      So yes, adding a restrict clause seems a good idea for those times when ADMIN_TOKEN is configured. Travis: can you share your restriction to get us started please?

  2. You use nginx so I would assume it would be something like this:

    location /admin {
    allow 1.2.3.4;
    deny all;
    }

    I use CloudFlare so my WAF rule looks like this:

    (http.request.uri wildcard r"/admin" and http.host eq "my.domain.example")

Leave a Comment

Scroll to Top