stilla

Vaultwarden on OpenBSD

Leaving KeePassXC behind and getting a Hetzner CAX cloud server in one move.

Table of contents:

IPv6

IPv6 address for vio0? (or 'autoconf' or 'none') [none] autoconf

This you?1 Let’s fix that:

inet autoconf
inet6 $addr

Add IPv6 gateway:

fe80::1%vio0

Add the missing Hetzner recursive name servers:

nameserver 185.12.64.1 # resolvd: vio0
nameserver 185.12.64.2 # resolvd: vio0
nameserver 2a01:4ff:ff00::add:1
nameserver 2a01:4ff:ff00::add:2
lookup file bind

HTTPD

I’m not using a subdomain for this because I couldn’t care less. Add /etc/httpd.conf and run rcctl -d start httpd as root.

server "example.com" {
  listen on * port 80
  root "/htdocs/example.com"
  location "/.well-known/acme-challenge/*" {
    root "/acme"
    request strip 2
  }
}

Enabling HTTPS

The OpenBSD way of getting certificates2:

authority letsencrypt {
  api url "https://acme-v02.api.letsencrypt.org/directory"
  account key "/etc/ssl/private/letsencrypt.key"
}

domain example.com {
  alternative names { www.example.com }
  domain key "/etc/ssl/private/example.com.key"
  domain certificate "/etc/ssl/example.com.crt"
  domain full chain certificate "/etc/ssl/example.com.pem"
  sign with letsencrypt
}

Almost there but not exactly. So what’s going on? In layman’s terms: relayd will look for example.com.crt, but for Bitwarden clients to work, we’ll need the full chain certificate.3

authority letsencrypt {
  api url "https://acme-v02.api.letsencrypt.org/directory"
  account key "/etc/ssl/private/letsencrypt.key"
}

domain example.com {
  alternative names { www.example.com }
  domain key "/etc/ssl/private/example.com.key"
  domain certificate "/etc/ssl/example.com.crt.leaf"
  domain full chain certificate "/etc/ssl/example.com.crt"
  sign with letsencrypt
}

Now run acme-client -v example.com and crontab -e.

0       0       *       *       *       acme-client example.com && rcctl restart relayd

relayd

Is this really necessary? I don’t know. You’re free to try and use httpd only.4 5

table <vaultwarden-default-host> { 127.0.0.1 }
table <vaultwarden-websocket-host> { 127.0.0.1 }

http protocol vaultwarden-https {
  match request header append "X-Real-IP" value "$REMOTE_ADDR"
  match request header append "Host" value "$HOST"
  match request header append "X-Forwarded-For" value "$REMOTE_ADDR"
  match request header append "X-Forwarded-By" value "$SERVER_ADDR:$SERVER_PORT"
  match request path "/*" forward to <vaultwarden-default-host>
  match request path "/notifications/hub" forward to \
    <vaultwarden-websocket-host>

  match request path "/notifications/hub/negotiate" forward to \
    <vaultwarden-default-host>

  tcp { nodelay, sack, backlog 128 }

  tls keypair example.com
  tls { no tlsv1.0, ciphers HIGH }
  # tls ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256


  http websockets
}

relay vaultwarden-https-relay {
  listen on egress port 443 tls
  protocol vaultwarden-https
  forward to <vaultwarden-default-host> port 8000
}

Vaultwarden

Begin with pkg_add vaultwarden and move onto the config.

DATA_FOLDER=data
ENABLE_WEBSOCKET=true
DOMAIN=https://example.com
LOG_FILE=data/vaultwarden.log
LOG_LEVEL=error

Disable signups after yourself.

Going live!

As root: rcctl -d start relayd vaultwarden. You can now import your KeePassXC (csv) exported data into your vault.6 The .csv file is unencrypted, wipe it after you’re done.


  1. OpenBSD IPv6-only Networking & Firewall 

  2. Enable HTTPS with acme-client(1) and Let’s Encrypt on OpenBSD 

  3. Self hosted - Problem connecting server - Android #272  

  4. Run Your Own LastPass on Hardened OpenBSD 

  5. OpenBSD 7.5 - relayd -> vaultwarden - websockets not working 

  6. Import Data to your Vault