Illustrative DEMO studio with a seated operator, laptop and HTTPS, Vaultwarden and backup diagram.

WIUNIX ACADEMY

Self-host Vaultwarden with HTTPS, restricted admin access and consistent backups

Deploy community Vaultwarden with a digest-pinned image, loopback backend, private admin PHC, closed signups and a coordinated recovery plan.

Intermediate60 min readUpdated
Make this guide yours

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

Ubuntu24.04 LTSReviewed against official documentation ·

USE YOUR OWN VALUES

Configure this tutorial

Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.

Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.

Set values for my environment7 editable values

THE OUTCOME

What will you have at the end?

Run a controlled password-service pilot with explicit HTTPS and recovery boundaries.

01HTTPS vault.example.comClient → Nginx443
02127.0.0.1:8159Vaultwarden1.37.4 • UID23159
03/srv/academy-vaultwardenStopped writer → private backup
04203.0.113.10/32Restricted admin + private PHC
Supported operating systems and versions

OS paths are documented; isolated checks and operational acceptance are distinguished.

Before you begin

  • Dedicated VM, rootful Docker28+, DNS and trusted TLS.
  • Encrypted backup destination and restricted management access.

A small password service with a recovery boundary

Deploy a single-node community Vaultwarden service for a small trusted pilot, with Nginx HTTPS for vault.example.com, a loopback backend at 8159, protected admin access and a stopped-writer recovery set under /srv/academy-vaultwarden. Allow roughly 60 minutes after Docker, DNS and certificate preparation. Use dummy vault content until the acceptance and recovery rehearsal are complete.

Vaultwarden is an independent implementation of the Bitwarden client API, not the official Bitwarden server or a Bitwarden-supported deployment. Operators own patching, client compatibility, storage, monitoring and recovery. Server administration can delete users and alter authentication; an admin hash is distinct from each user’s master password and client KDF. A backup cannot recover a forgotten master password by itself.

Edit public examples, apply, then copy

Open the settings form, edit the public examples, press Apply, then copy the updated panel. Pending or invalid edits disable copying. Never enter a password, admin token, PHC hash, private key, SMTP credential or vault item in this article’s form.

Values and custom progress remain in sessionStorage for this tab, language, article and environment; default progress uses localStorage. Reset restores the reviewed examples. Personalizing text does not run commands, own DNS, reserve a port or prove access. 203.0.113.10/32 is a documentation-only example: replace it with the single trusted administrator source network before enabling HTTPS.

Start with an empty dedicated VM

Use a fresh dedicated VM with sudo, root-controlled parent directories, signed apt repositories, sufficient storage/inodes and a backup destination with encryption managed outside this service. Rootful Docker Engine ≥28 must already be installed through its supported OS procedure; this guide does not install or mix Docker packages. Older Docker versions have a documented loopback-publication exposure caveat.

Prepare IPv4 DNS A (do not publish an AAAA record for this IPv4-only proxy), trusted certificate/full chain and matching private key for vault.example.com, a renewal procedure and public TCP443. Nginx connects directly to Docker on the same VM. No CDN, upstream proxy, remote backend or subpath hosting is included. Changing that topology requires reviewing real client-IP trust and the admin allowlist, not trusting arbitrary forwarded headers.

Reserve numeric UID/GID 23159 and container name academy_vaultwarden. The admin source CIDR 203.0.113.10/32 must be reachable and genuinely yours; do not allow all addresses. Keep console/SSH recovery access. Use an ordinary official client only after verifying its exact current version against upstream release notes; do not import an existing personal vault for a smoke test.

Pin the reviewed security release

Reviewed on 2026-10-21: upstream latest API reports 1.37.4, released 2026-10-05, including security fixes. The project-maintained vaultwarden/server image is pinned by the multi-platform manifest digest below. A digest does not update itself: watch upstream advisories and stage upgrades on a recovery copy.

The 1.37.4 notes remove legacy registration/prelogin APIs and change X-Forwarded-For trust selection. CLI Send receive with Bitwarden CLI 2026.4.2 or older is specifically incompatible; this guide disables Sends. A bundled modified web vault does not establish universal compatibility with every desktop, mobile, browser-extension or future Bitwarden release.

01

LET’S BUILD

Check engine, identity and listeners

Run on the dedicated VM before provisioning. Stop on any conflict; these checks do not reserve resources against another administrator. The root directory’s parent must already exist and be controlled by root.

bash

Dedicated Vaultwarden VM

set -eu
sudo python3 - <<'PY'
import socket,subprocess
version=subprocess.check_output(['docker','version','--format','{{.Server.Version}}'],text=True).strip()
assert int(version.split('.')[0]) >= 28, 'Docker Engine >=28 required'
security=subprocess.check_output(['docker','info','--format','{{.SecurityOptions}}'],text=True)
assert 'rootless' not in security, 'This recipe requires rootful Docker'
assert subprocess.run(['docker','container','inspect','academy_vaultwarden'],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL).returncode != 0, 'Container name in use'
for kind in ('passwd','group'):
    assert subprocess.run(['getent',kind,'23159'],stdout=subprocess.DEVNULL).returncode != 0, 'UID/GID in use'
for port in (443,8159):
    with socket.socket() as s: s.bind(('0.0.0.0',port))
print('Engine, name, numeric identity and ports: OK')
PY
How do I know it worked?

Engine ≥28, unused name/UID/GID, and free443/8159.

02

LET’S BUILD

Install the selected OS utilities

Check the OS codename before changing packages. Nginx can start during installation; use this recipe only on the dedicated VM. Package candidates are listed rather than inventing identical versions across distributions.

bash

Dedicated Vaultwarden VM

set -eu
. /etc/os-release
test "$VERSION_CODENAME" = 'noble'
sudo apt-get update
apt-cache policy nginx argon2 python3
sudo apt-get install --no-install-recommends nginx argon2 python3 openssl ca-certificates curl nano sqlite3
nginx -v
dpkg-query -W nginx argon2 python3 openssl sqlite3
How do I know it worked?

The codename matches and installed utility versions are recorded.

Explore all tutorials ↗