

Discover more from Abort Retry Fail
Vaultwarden on an RPi
Open source bitwarden password manager written in Rust and controlled by you
So, there are plenty of reasons to self-host your password manager, and very few reasons not to do so. In my own case, there were essentially zero reasons not to run my own password manager as I had the hardware available, and I have a domain over at Cloudflare.
How do you do this? With Vaultwarden. Vaultwarden is a free and open source implementation of the bitwarden password manager in the Rust programming language. It is compatible with bitwarden clients for major browsers and operating systems as well. In my case, the server is a Raspberry Pi 4. The network is my home. I flashed a new image to a microSD card, and popped it in the RPi, and powered it up.
The next step, is to install the software that we will require.
curl -sSL https://get.docker.com | sh;
sudo usermod -aG docker pi;
sudo apt install nginx;
wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb;
sudo apt install ./cloudflared-linux-arm64.deb;
Docker is going to be the container system in which we run Vaultwarden. Nginx is a reverse proxy that will allow us to take incoming requests and route them to Vaultwarden’s docker container. Cloudflared will allow us to create argo tunnels between Cloudflare and nginx.
To setup nginx, you first need to create your configuration.
sudo nano /etc/nginx/sites-available/example.com.conf;
upstream vaultwarden-default {
zone vaultwarden-default 64k;
server 127.0.0.1:8080;
keepalive 2;
}
upstream vaultwarden-ws {
zone vaultwarden-ws 64k;
server 127.0.0.1:3012;
keepalive 2;
}
server {
listen 80;
listen [::]:80;
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
root /var/www/vault;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDH+AESGCM:ECDH+CHACHA20:ECDH+AES256:ECDH+AES128:!aNULL:!SHA1:!AESCCM;
client_max_body_size 128M;
proxy_connect_timeout 777;
proxy_send_timeout 777;
proxy_read_timeout 777;
send_timeout 777;
location ~ ^/\.well-known(.*) {
default_type text/plain;
allow all;
}
location / {
proxy_http_version 1.1;
proxy_set_header "Connection" "";
proxy_set_header Host example.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cookie_domain example.com localhost;
sub_filter "localhost" "example.com";
sub_filter_once off;
proxy_redirect http://localhost https://example.com;
proxy_pass http://vaultwarden-default;
}
location /notifications/hub/negotiate {
proxy_http_version 1.1;
proxy_set_header "Connection" "";
proxy_set_header Host example.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://vaultwarden-default;
}
location /notifications/hub {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host example.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Forwarded $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://vaultwarden-ws;
}
}
Quick notes regarding the above:
(1) The proxy_set_header lines, sub filter lines, and proxy redirect lines will aid in translating anything that directs to localhost
to the actual domain you have. This shouldn’t happen anyway, but it does help.
(2) If you try to load this config, it will fail for two reasons. First, you need to change example.com to your actual domain. Second, you need to generate your SSL config before you use the listen 443 lines, or the SSL lines. You can comment those out and run:
sudo certbot certonly --webroot -w /var/www/vault -d example.com
(3) When using Cloudflare’s tunnels, Cloudflare will upgrade the connection to SSL, so we cannot put an SSL redirect into the nginx config.
To save that, press ctl+o
and to exit press ctl+x
. Now you need to enable that configuration.
sudo ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/;
Then you just need to test and run the nginx configuration.
sudo nginx -t && sudo nginx -s reload;
Now, I am going to assume that you have a domain managed by Cloudflare, and that you do not already have a record created for vaultwarden.yourdomain.com.
cloudflared tunnel login;
If this doesn’t automatically open up a browser window, you need to copy and paste the URL that is provided by the above command into your browser, and then select the domain to use within that browser window. When that is done, you need to create the tunnel.
cloudflared tunnel create VAULTWARDEN;
You don’t have to use all caps, that’s just my style. You should get both an ID and a path to a credentials file. Both of these are important. Within your home directory, there is now a .cloudflared
directory, and within that you have both an a key file and a json file. You should go ahead and copy that cert.pem to its unique identifier.
cp $HOME/.cloudflared/cert.pem $HOME/.cloudflared/the-super-long-id-number.pem;
This will be useful should you ever decide to create more tunnels for things. Let’s create our configuration for this.
sudo mkdir /etc/cloudflared;
sudo touch /etc/cloudflared/config.yml;
sudo nano /etc/cloudflared/config.yml;
tunnel: xxxxxxx-xxxxxxx-xxx-xxxxx-xxxxxxx
credentials-file: /home/pi/.cloudflared/xxxxxxx-xxxxxxx-xxx-xxxxx-xxxxxxx.json
origincertpath: /home/pi/.cloudflared/xxxxxxx-xxxxxxx-xxx-xxxxx-xxxxxxx.pem
ingress:
- hostname: example.com
service: http://localhost:80
- service: http_status:404
Now you need to route DNS to the tunnel.
cloudflared tunnel route dns VAULTWARDEN example.com;
Cloudflared can be picky about the syntax of the config file, so play with spacing if it complains. With this tunnel in place, you neither need worry about dynamic DNS setup nor about opening ports in your firewall. To start the tunnel, I like to use a little script I wrote for this purpose. I call it argoctl:
#!/bin/bash
COUNT=0
declare -A DATA
for FILE in $(cd /etc/cloudflared; ls -1); do
ID=$(grep tunnel /etc/cloudflared/$FILE | awk '{print $2}')
DATA[$COUNT]="$ID"
((COUNT++))
DATA[$COUNT]="$FILE"
((COUNT++))
done
case $1 in
start)
for (( i=0 ; i<$COUNT ; i++ )); do
CN=$(echo $(( i + 1 )) )
exec /usr/bin/cloudflared --no-autoupdate --config /etc/cloudflared/${DATA[$CN]} tunnel run ${DATA[$i]} &
((i++))
sleep 10s
done
;;
stop)
sudo killall -9 cloudflared
;;
restart)
$0 stop
$0 start
;;
status)
ps -ef | grep cloudflared | grep -v grep
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
;;
esac
exit 0
This script can handle an arbitrary number of configuration files. If you want more information about Cloudflare’s tunnels, I have an article about it. If you are using my script, just do argoctl start
. If you aren’t, then you can just use cloudflared tunnel run
.
Let’s grab vaultwarden itself!
sudo docker pull vaultwarden/server:latest;
Now, let’s do a first run.
sudo docker run -d --name vaultwarden -v /srv/vaultwarden:/data -e WEBSOCKET_ENABLED=true -p 127.0.0.1:8080:80 -p 127.0.0.1:3012:3012 --restart on-failure vaultwarden/server:latest;
Go to your browser and navigate to your domain. If this all worked properly, you should now be able to create an account on your server. Create the accounts for your users, and then we need to disable public account creation.
sudo docker stop vaultwarden;
sudo docker rm vaultwarden;
sudo docker run -d --name vaultwarden -v /srv/vaultwarden:/data -e WEBSOCKET_ENABLED=true -e SIGNUPS_ALLOWED=false -p 127.0.0.1:8080:80 -p 127.0.0.1:3012:3012 --restart on-failure vaultwarden/server:latest;
If you need to add more users later, you just stop, rm, and run the first docker run command again, and then stop, rm, and run the second docker run command again.
If you need SSL on your host (which I highly recommend), you can easily use Certbot with your installation. I have prior articles on hosting from home that cover that. Additionally, there are features within Cloudflare to handle that.
I hope you found this helpful!