Linux/Windows VPS Server 899/- ($8.99) Monthly

Contact Info

SA Khaleq Plaza, Gabtoli, Mirpur, Dhaka-1216

+88 01531693993

[email protected]

Get Started

How to set up Vaultwarden on a VPS

Introduction

In this tutorial you will learn how to install Vaultwarden via a Docker image.

Vaultwarden is an alternative implementation of the Bitwarden serverside which itself is not completely open-source and hides features behind a pay-wall. The Vaultwarden project, maintained by dani-garcia, mimics the client interfaces to be almost fully compatible with the broad range of Bitwarden clients.

DANGER ZONE – READ CAREFULLY

The vaultwarden serverside does (when properly configured) not store sensitive data in plain text at any give point in time. The decryption of the users data happens only on the endpoint either

in an app, browser extension or inside the web application. Make sure you’re comfortable with these security parameters before storing critical data and passwords.

IF YOU LOOSE A USERS MASTER KEY, ALL THEIR DATA IS LOST – FOREVER. Enabling existing work arounds for this issue undermines many, if not all, security benefits of the applications design.

What will be achieved

  • Install and set up Docker.
  • Configure Traefik, ModSecurity and Vaultwarden
  • Profit

Prerequisites

  • Hetzner Account
  • Control of a publicly resolvable domain
  • SMTP Server (can be any free webmail service if you don’t have your own server)

Step 1 – Setup new Server

Do not host Vaultwarden and other applications on the same host if not absolutely required. The attack surface should be as small as possible and any added complexity from other applications will introduce unnecessary uncertainty.

  1. Open Hetzner Cloud Console
  2. Click “New Project +” and create a new project
  3. Click “Add Server” and select a datacenter which is closest to your users
  4. Select Ubuntu 24.04 as OS
  5. Depending on the amount of users, select at least a CPX21 instance
  6. Go to “SSH keys” and paste your SSH key. On Windows 10 you can get it via powershell cat $env:USERPROFILE\.ssh\id_rsa.pub
  7. Go to “Cloud config” and insert the code below
  8. Name the server appropriately and click “Create & Buy now”
#cloud-config
package_upgrade: true
apt:
  sources:
    docker.list:
      source: deb [arch=amd64] https://download.docker.com/linux/ubuntu $RELEASE stable
      keyid: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88

packages:
  - apt-transport-https
  - ca-certificates
  - curl
  - gnupg-agent
  - software-properties-common
  - docker-ce
  - docker-ce-cli
  - containerd.io
  - docker-buildx-plugin
  - docker-compose-plugin
  - fail2ban
  
groups:
  - docker
  
system_info:
  default_user:
    groups: [docker]

This cloud-init script will take care of the Docker installation, so its a controlled process which is less prone to error.

Step 2 – Set DNS Records

Setting the DNS records now gives the DNS system enough time to propagate the changes far enough so that the Let’s Encrypt resolvers will resolve the hostname to your IP. This is necessary to obtain a valid SSL certificate to encrypt client connections and proof authenticity of our host system.

Go to the admin panel of the company that hosts your domain name and create an A record with the subdomain of your choosing and the IPv4 address of the newly created server.

Step 3 – Setup the docker-compose.yml file

Opinions on this matter may differ but you could place your docker-compose.yml file virtually anywhere. We will use the /opt directory.

  • Create a folder called vaultwarden in /optmkdir /opt/vaultwarden && cd /opt/vaultwarden
  • Create necessary filesmkdir /opt/docker && mkdir /opt/docker/waf-rules touch /opt/docker/waf-rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf touch /opt/docker/waf-rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf mkdir /opt/docker/le && chmod -R 600 /opt/docker/le
  • Create log directoriesmkdir /opt/docker/waf touch /opt/docker/waf/waf.log && chmod o+w /opt/docker/waf/waf.log
  • Add a docker-compose.yml filenano docker-compose.ymlCopy & paste the code below to the new docker-compose.yml file. You do not need to edit anything as we will add the variables in an additional file in the next step. If you want to receive notifications, edit the SMTP keys as described in the table below the code.The docker-compose.yml file will create three containers with the images traefikowasp/modsecurity-crs, and vaultwarden/server.services: traefik: image: traefik:latest container_name: traefik command: - --providers.docker=true - --providers.docker.exposedByDefault=false - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 - --certificatesresolvers.myresolver.acme.tlschallenge=true - --certificatesresolvers.myresolver.acme.email=${LETS_ENCRYPT_MAIL} - --certificatesresolvers.myresolver.acme.storage=acme.json - --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json - --log.level=DEBUG restart: unless-stopped ports: - 80:80 - 443:443 volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - /opt/docker/le:/letsencrypt waf: image: owasp/modsecurity-crs:apache container_name: waf environment: PARANOIA: 1 ANOMALY_INBOUND: 10 ANOMALY_OUTBOUND: 5 PROXY: 1 REMOTEIP_INT_PROXY: "172.20.0.1/16" BACKEND: "http://vaultwarden:80" BACKEND_WS: "ws://vaultwarden:80/notifications/hub" ERRORLOG: "/var/log/waf/waf.log" volumes: - /opt/docker/waf:/var/log/waf - /opt/docker/waf-rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf:/etc/modsecurity.d/owasp-crs/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf - /opt/docker/waf-rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf:/etc/modsecurity.d/owasp-crs/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf labels: - traefik.enable=true - traefik.http.middlewares.redirect-https.redirectScheme.scheme=https - traefik.http.middlewares.redirect-https.redirectScheme.permanent=true - traefik.http.routers.vw-ui-https.rule=Host(`${VAULTWARDEN_DOMAIN}`) - traefik.http.routers.vw-ui-https.entrypoints=websecure - traefik.http.routers.vw-ui-https.tls=true - traefik.http.routers.vw-ui-https.service=vw-ui - traefik.http.routers.vw-ui-http.rule=Host(`${VAULTWARDEN_DOMAIN}`) - traefik.http.routers.vw-ui-http.entrypoints=web - traefik.http.routers.vw-ui-http.middlewares=redirect-https - traefik.http.routers.vw-ui-http.service=vw-ui - traefik.http.routers.vw-ui-https.tls.certresolver=myresolver - traefik.http.services.vw-ui.loadbalancer.server.port=8080 - traefik.http.routers.vw-websocket-https.rule=Host(`${VAULTWARDEN_DOMAIN}`) && Path(`/notifications/hub`) - traefik.http.routers.vw-websocket-https.entrypoints=websecure - traefik.http.routers.vw-websocket-https.tls=true - traefik.http.routers.vw-websocket-https.service=vw-websocket - traefik.http.routers.vw-websocket-http.rule=Host(`${VAULTWARDEN_DOMAIN}`) && Path(`/notifications/hub`) - traefik.http.routers.vw-websocket-http.entrypoints=web - traefik.http.routers.vw-websocket-http.middlewares=redirect-https - traefik.http.routers.vw-websocket-http.service=vw-websocket - traefik.http.routers.vw-websocket-https.tls.certresolver=myresolver - traefik.http.services.vw-websocket.loadbalancer.server.port=3012 vaultwarden: image: vaultwarden/server:latest container_name: vaultwarden restart: unless-stopped environment: WEBSOCKET_ENABLED: "true" SENDS_ALLOWED: "true" PASSWORD_ITERATIONS: 500000 SIGNUPS_ALLOWED: "true" SIGNUPS_VERIFY: "true" ADMIN_TOKEN: "${ADMIN_TOKEN}" # DOMAIN: "SMTP domain host name" # SMTP_HOST: "smtp server" # SMTP_FROM: "sender email e.g: [email protected]" # SMTP_FROM_NAME: "sender name" # SMTP_SECURITY: "starttls" # SMTP_PORT: 587 # SMTP_USERNAME: "smtp username" # SMTP_PASSWORD: "smtp password" # SMTP_TIMEOUT: 15 LOG_FILE: "/data/vaultwarden.log" LOG_LEVEL: "warn" EXTENDED_LOGGING: "true" TZ: "${TZ}" volumes: - /opt/docker/vaultwarden:/data networks: default: driver: bridge ipam: driver: default config: - subnet: 172.20.0.0/16If you want to receive notifications, uncomment the SMTP keys and replace the placeholders with your own information:PlaceholderDescriptionSMTP domain host nameEnter the hostname of your SMTP server (Google smtp settings [your mail provider]sender email e.g: [email protected]Change to the email you wish to send notifications fromsender nameReplace with Vaultwarden or something to instantly tell this e-mail is from the password serversmtp usernameIn most cases your e-mail address; except on AWS SES where you would need to create extra IAM userssmtp passwordYour smtp password
  • Save the variables in .envVAULTWARDEN_DOMAIN=vaultwarden.example.com ADMIN_TOKEN=your_admintoken [email protected] TZ=Europe/BerlinPlaceholderDescriptionvaultwarden.example.comThis will take you to the Vaulwarden UI.your_admintokenWe will create the token in the next step.[email protected]Put an e-mail you regulary read (SSL notifications will be send there)Europe/BerlinIf you don’t live in the GTM+1 Timezone change this
  • Put nano editor in background with CTRL + T followed by CTRL + Z.
  • Generate Admin token and copy outputsudo apt install argon2 echo -n "MySecretPassword" | argon2 "$(openssl rand -base64 32)" -e -id -k 65540 -t 3 -p 4Replace MySecretPassword with a password of your choice.
  • Copy the output and replace all five occurrences of $ with $$ to avoid variable interpolation. Example:$$argon2id$$v=19$$m=65540,t=3,p=4$$M29tVXp3ZVl6UC9EZDVRdW1EWE9GQWg2cXVRaXdiMnMrVlIrU2xjbXFyST0$$TZUeinxme5vCB++KdBy5UuBJvcPtqk4xXMfQpeaY6AA
  • Bring nano back to front by typing fg and hitting ENTER
  • Replace your_admintoken with the edited output from above, e.g.:ADMIN_TOKEN=$$argon2id$$v=19$$m=65540,t=3,p=4$$M29tVXp3ZVl6UC9EZDVRdW1EWE9GQWg2cXVRaXdiMnMrVlIrU2xjbXFyST0$$TZUeinxme5vCB++KdBy5UuBJvcPtqk4xXMfQpeaY6AA
  • Save file with CTRL+X and then press Y and ENTER

Step 4 – Configure Fail2Ban

  • Create filter for the vaultwarden applicationnano /etc/fail2ban/filter.d/vaultwarden.localInclude the following lines[INCLUDES] before = common.conf [Definition] failregex = ^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$ ignoreregex =Source
  • Create filter for the vaultwarden admin applicationnano /etc/fail2ban/filter.d/vaultwarden-admin.localInclude the following lines[INCLUDES] before = common.conf [Definition] failregex = ^.*Invalid admin token\. IP: <ADDR>.*$ ignoreregex =Source
  • Create filter for the ModSecurity applicationnano /etc/fail2ban/filter.d/waf.localInclude the following lines[INCLUDES] before = common.conf [Definition] failregex = ^.*\[client <ADDR>\] ModSecurity: Access denied with code 403 .*$ ignoreregex =
  • Create jail for the vaultwarden applicationnano /etc/fail2ban/jail.d/vaultwarden.localInclude the following lines[vaultwarden] enabled = true port = 80,443,8081 filter = vaultwarden banaction = %(banaction_allports)s logpath = /data/vaultwarden.log maxretry = 3 bantime = 14400 findtime = 14400Source
  • Create jail for the vaultwarden admin applicationnano /etc/fail2ban/jail.d/vaultwarden-admin.localInclude the following lines[vaultwarden-admin] enabled = true port = 80,443 filter = vaultwarden-admin banaction = %(banaction_allports)s logpath = /data/vaultwarden.log maxretry = 3 bantime = 14400 findtime = 14400Source
  • Create jail for the ModSecurity applicationnano /etc/fail2ban/jail.d/waf.localInclude the following lines[waf] enabled = true port = 80,443 filter = waf action = iptables-allports[name=waf, chain=FORWARD] logpath = /opt/docker/waf/waf.log maxretry = 1 bantime = 14400 findtime = 14400

Reload the ruleset with this command:

sudo systemctl reload fail2ban

Step 5 – Starting Instance

To startup your instance, run:

cd /opt/vaultwarden && docker compose up -d

Traefik needs some time to properly start up, discover all services and issue certificates. This can take up to 5 minutes so please be a little patient. You can run docker ps -a to check the status and check the logs for errors with docker compose logs.

Click here for commands to troubleshoot

curl -k https://vaultwarden.example.com:443
curl http://$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' vaultwarden):80
docker exec -it traefik wget -qO- http://vaultwarden:80
docker exec -it traefik wget -qO- http://waf:8080
docker exec -it waf curl http://vaultwarden:80
docker inspect vaultwarden_default
docker logs traefik

You can now connect to your Vaultwarden instance via any modern web browser by accessing your domain and create an account for yourself:

https://vaultwarden.example.com

To access the admin interface, go here and enter the password from earlier on (in this example “MySecretPassword”):

https://vaultwarden.example.com/admin

There you can manipulate all important settings, however the configuration defined in the docker-compose.yml is a good starting point. You might want to disable public registration though. Just go to General Settings and uncheck Allow new singups and press save on the bottom of the page.

How to connect through Bitwarden applications

To connect your devices to your new Vaultwarden server, please follow this guide from Bitwarden.

Step 6 – Optional: Tuning ModSecurity

ModSecurity is a web application firewall that can prevent common attacks on web applications such as Vaultwarden. Depending on the protection requirements, it might be necessary to increase the aggressivity of ModSecurity. In order to do so, increase PARANOIA or decrease ANOMALY_INBOUND in the docker-compose.yml file. Be warned, changes to the default values will require manual tuning of ModSecurity which will take quite some time to get just right. There is a good guide here.

Step 7 – Enable Hetzner Firewall

  1. Open Cloud Console, select your project, go to Firewall and click on Create Firewall.Add three inbound rules to the Firewall:
  2. First rule
    Protocol: TCP
    Port: 80
    IP: Any IPv4Any IPv6
  3. Second rule
    Protocol: TCP
    Port: 443
    IP: Any IPv4Any IPv6
  4. Third rule
    Protocol: TCP
    Port: 22
    IP: <your-own-IP>

    If you don’t know your IP address, you can go to https://ip.hetzner.com/ and copy and paste it from there.
    Note: Residential internet connections will most likely change their public IPv4 address frequently or might even be behind CGNAT. This means you need to change the third firewall rule any time you want to access your server to reflect your current IPv4 address.
  5. Apply the Firewall to your server.
    • Go to Apply to and select + SELECT RESOURCES ➔ Server.
    • Select you server and click on APPLY TO 1 SERVER.
  6. Click Create Firewall

Graphic representation on how this all works

Architecture diagram

Conclusion

In the previous steps you learned how to create a new server that has Docker installed on it, how to set up Vaultwarden by using a Docker image, and how to set up the Hetzner Firewall.

Share this Post

Support Agent

Typically replies within 30 second during office hours (BST, GMT +6)

View Price in USD ($)