VPS Tutorials
Self-Hosting Vaultwarden on a VPS (2026): Complete Guide to Docker Compose, Caddy HTTPS, Backups and Security Hardening
This tutorial is for users who want to self-host a Bitwarden-compatible password management service on a low-cost VPS. You will learn how to deploy Vaultwarden with Docker Compose, bind the service only to a local port, provide HTTPS with Caddy, disable public registration, configure SMTP invitations, enable two-step verification, and above all master backups, restore, upgrades and common troubleshooting.
Self-Hosting Vaultwarden on a VPS (2026): Complete Guide to Docker Compose, Caddy HTTPS, Backups and Security Hardening
A password manager is one of the best self-hosted services to run long-term on a VPS. It does not eat disk space like photo albums, cloud drives or media libraries, and it does not eat GPU and RAM like AI services; but its value is high: browser extensions, mobile apps, desktop clients, team sharing, random passwords, two-step verification and cross-device sync all directly affect your daily security.
Vaultwarden is a Bitwarden-compatible server implemented in Rust. It is not the official Bitwarden service, but it is compatible with most Bitwarden clients, uses very few resources, and is very well suited to personal VPS, small teams and home scenarios. Many 1C1G or even smaller VPS can run it.
But precisely because it stores passwords, deployment cannot stop at "the page opens". This article explains things from a production mindset: how to deploy Vaultwarden with Docker Compose, how to get automatic HTTPS with Caddy, why public registration should be disabled, how to configure SMTP, which directories must be backed up, what to watch out for when restoring, and how to reduce public exposure risk.
Yes.
Vaultwarden's advantages are being lightweight, stable and having a mature client ecosystem. You can keep using the Bitwarden browser extension, mobile app and desktop client; you just change the server address to your own domain.
Suitable scenarios:
- You want to self-host a password sync service and do not want your vault hosted on a third-party cloud.
- You have a VPS that stays online long-term and a domain.
- You need to provide password sharing for family or a small team.
- You want low-cost operation with limited machine resources.
- You are willing to seriously do backups and restore drills.
Unsuitable scenarios:
- You are unwilling to maintain server security updates.
- You have no second backup and only want to keep your vault on a single VPS.
- You will expose the admin backend, registration entry and weak passwords to the public internet together.
- You expect the server to help you recover your master password if you forget it.
The core principle of a password manager is: even if the server is compromised, plaintext passwords should not be easily obtained; but the server is still very important, because it affects sync, login, invitations, organization sharing and data availability.
Vaultwarden has very low resource requirements:
| Use case | Recommended configuration |
|---|---|
| Personal use | 1 vCPU / 512MB-1GB RAM / 10GB SSD |
| Family or small team | 1-2 vCPU / 1GB RAM / 20GB SSD |
| Many users and more attachments | 2 vCPU / 2GB RAM / 40GB+ SSD |
What really matters is not CPU, but:
- Whether the data directory is persistent.
- Whether the domain uses HTTPS.
- Whether registration is disabled.
- Whether SMTP can properly send invitation and verification emails.
- Whether backups include the database, attachments and key files.
If your VPS runs many Docker services at the same time, it is recommended to read the Docker and reverse proxy basics first. For further reading, see: Complete Guide to Caddy Reverse Proxy on a VPS and Setting Up the Portainer Docker Management Panel on a VPS.
Before starting, prepare:
- An Ubuntu 22.04 / 24.04 or Debian 12 VPS.
- Docker Engine and the Docker Compose plugin installed.
- A domain, for example
vault.example.com. - Ports 80/443 open.
- Basic firewall and SSH security configured.
Confirm Docker is available:
docker version
docker compose version
Confirm the domain already resolves to the VPS:
dig vault.example.com +short
If the domain is not resolving yet, do not rush to request certificates. Caddy, Nginx or Let's Encrypt all need DNS and ports 80/443 to work properly.
It is recommended to put Vaultwarden in /opt/vaultwarden:
sudo mkdir -p /opt/vaultwarden
sudo chown -R "$USER":"$USER" /opt/vaultwarden
cd /opt/vaultwarden
Create the data directory:
mkdir -p vw-data
By default Vaultwarden puts important data in /data inside the container, so we must mount it to a host directory, otherwise data may be lost after deleting the container.
Create .env:
nano .env
Write the following content, modifying it for your own domain:
VAULTWARDEN_DOMAIN=https://vault.example.com
VAULTWARDEN_ADMIN_TOKEN=换成一串很长的随机值
VAULTWARDEN_SMTP_HOST=smtp.example.com
VAULTWARDEN_SMTP_PORT=587
[email protected]
[email protected]
VAULTWARDEN_SMTP_PASSWORD=换成邮箱授权码
Generate a random admin token:
openssl rand -base64 48
ADMIN_TOKEN is used to access Vaultwarden's /admin admin page. This value is very sensitive; do not write it into a public repository, and do not send it to others.
If you do not want to enable the admin backend for now, you can leave ADMIN_TOKEN unset, or disable the admin entry after deployment once you confirm the configuration is fine. For personal users, use the admin page as little as possible.
Create docker-compose.yml:
nano docker-compose.yml
Write:
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
env_file:
- .env
environment:
DOMAIN: ${VAULTWARDEN_DOMAIN}
SIGNUPS_ALLOWED: "false"
INVITATIONS_ALLOWED: "true"
WEBSOCKET_ENABLED: "true"
ADMIN_TOKEN: ${VAULTWARDEN_ADMIN_TOKEN}
SMTP_HOST: ${VAULTWARDEN_SMTP_HOST}
SMTP_PORT: ${VAULTWARDEN_SMTP_PORT}
SMTP_FROM: ${VAULTWARDEN_SMTP_FROM}
SMTP_USERNAME: ${VAULTWARDEN_SMTP_USERNAME}
SMTP_PASSWORD: ${VAULTWARDEN_SMTP_PASSWORD}
SMTP_SECURITY: starttls
volumes:
- ./vw-data:/data
ports:
- "127.0.0.1:8000:80"
There are several key points here:
vaultwarden/server:latestis the commonly used official image../vw-data:/datastores the database, attachments, keys and configuration.127.0.0.1:8000:80means only local access to the container port is allowed.SIGNUPS_ALLOWED: "false"disables public registration.INVITATIONS_ALLOWED: "true"allows the administrator to invite users.DOMAINmust be filled with the final HTTPS domain, otherwise attachments, email links and client sync may behave abnormally.
If you have not configured SMTP yet, you can first delete the SMTP-related environment variables and add them after the web page opens. But before formal use, it is recommended to configure email, otherwise invitations, verification and some notification experiences will be worse.
In /opt/vaultwarden, run:
docker compose up -d
Check status:
docker compose ps
View logs:
docker compose logs -f --tail=100
Test locally:
curl -I http://127.0.0.1:8000
If it returns HTTP/1.1 200 OK, 301 or a similar response, the container is accessible locally.
Note: because we only bound 127.0.0.1, public access to http://服务器IP:8000 will not work; this is expected behavior. The next step is to reverse proxy in from 443 with Caddy or Nginx.
Assuming your domain is vault.example.com, the Caddyfile can be written like this:
vault.example.com {
encode zstd gzip
header {
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
X-Robots-Tag "noindex, nofollow"
}
reverse_proxy 127.0.0.1:8000 {
header_up X-Real-IP {remote_host}
}
}
After saving, run:
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
Visit:
https://vault.example.com
The official Vaultwarden documentation also recommends using a reverse proxy to terminate TLS, rather than exposing the container's HTTP port directly to the public internet. This way only 443 is exposed publicly, and the container port remains visible only locally.
If you do not have Caddy yet, see: Complete Guide to Caddy Reverse Proxy on a VPS.
After opening the web page for the first time, create your main account.
After creation, confirm again that public registration is disabled:
docker compose exec vaultwarden printenv SIGNUPS_ALLOWED
It should output:
false
Why disable registration? Because Vaultwarden is a password management service; if the registration entry stays open long-term, public scanners, spam accounts and credential-stuffing attacks will all increase your maintenance burden.
For adding users, it is recommended to use the invitation flow: the administrator invites family members or team members, and they create accounts via email. This is more controllable than public registration.
SMTP is mainly used for:
- Inviting new users.
- Email verification.
- Certain security notifications.
- Organization-related emails.
Common email configuration approach:
VAULTWARDEN_SMTP_HOST=smtp.qq.com
VAULTWARDEN_SMTP_PORT=587
[email protected]
[email protected]
VAULTWARDEN_SMTP_PASSWORD=邮箱授权码
After modifying .env, restart:
docker compose up -d
Then go to the admin page or account settings to test email.
Note: many email providers do not allow using the login password directly for SMTP; you need to generate a separate "authorization code" or "app-specific password". If you use a corporate email, also confirm SPF, DKIM, DMARC and sending limits, otherwise emails may go to spam.
Vaultwarden supports the two-step verification capabilities of common Bitwarden clients. It is strongly recommended to enable TOTP for at least the administrator account.
Recommended approach:
- Log in to the Web Vault.
- Go to account security settings.
- Enable two-step login.
- Use a TOTP tool such as Aegis, 1Password, Bitwarden Authenticator or Google Authenticator to store verification codes.
- Save recovery codes to an offline location.
Do not store TOTP only in the same Vaultwarden account. Otherwise, once you cannot log in to Vaultwarden and also cannot get the TOTP, you will lock yourself out.
A safer approach is: keep the master password in your head, save recovery codes offline, and store TOTP on a separate device or in a separate app.
Vaultwarden's /admin page is convenient; you can view users, invitations, configuration and status. But it is also a high-value entry point.
If you enable ADMIN_TOKEN, at minimum you should:
- Use a sufficiently long random token.
- Not write the token into a public repository.
- Not save it in a shared browser environment.
- Restrict the access source of
/adminas much as possible.
A more conservative approach is to restrict the admin path in Caddy. For example, only allow specific IPs:
vault.example.com {
@admin path /admin*
@not_allowed_admin {
path /admin*
not remote_ip 你的固定IP/32
}
respond @not_allowed_admin "admin access denied" 403
reverse_proxy 127.0.0.1:8000
}
If you do not have a fixed IP, you can consider accessing the admin backend via WireGuard, Tailscale, Cloudflare Zero Trust, etc. The less the admin entry is exposed, the lower the risk.
Vaultwarden uses SQLite by default, and the data directory is usually vw-data. The official backup documentation clearly states that important data includes:
db.sqlite3: the main database.attachments/: attachments.sends/: Send files, back up as needed.config.json: if you save configuration via the admin page.rsa_key*: used to sign login tokens.
The simplest backup command:
cd /opt/vaultwarden
mkdir -p /opt/backups/vaultwarden
docker exec vaultwarden /vaultwarden backup
tar czf /opt/backups/vaultwarden/vw-data-$(date +%F-%H%M).tar.gz vw-data
Since Vaultwarden 1.32.1, the image provides a built-in SQLite backup command:
docker exec -it vaultwarden /vaultwarden backup
Do not keep backup files only on the same VPS. It is recommended to also sync them to:
- Another VPS.
- Object storage.
- A home NAS.
- An encrypted cloud drive directory.
If you already have a backup solution, you can add Vaultwarden to it. Further reading: What Is the Difference Between VPS Snapshots, Backups and Images.
Before restoring, stop the service:
cd /opt/vaultwarden
docker compose down
Keep the current directory:
mv vw-data vw-data.broken.$(date +%F-%H%M)
Extract the backup:
tar xzf /opt/backups/vaultwarden/vw-data-你的日期.tar.gz -C /opt/vaultwarden
Start:
docker compose up -d
docker compose logs -f --tail=100
Then check:
- Whether you can log in.
- Whether password entries exist.
- Whether attachments can be downloaded.
- Whether organization sharing works normally.
- Whether mobile and browser extensions can sync.
A backup is not "successful just because the file exists"; it is only reliable after a restore drill. This is especially true for password managers, because the last thing you want is to try restoring for the first time during a real failure.
If you use latest, the upgrade process is simple:
cd /opt/vaultwarden
cp docker-compose.yml docker-compose.yml.bak.$(date +%F)
cp .env .env.bak.$(date +%F)
docker compose pull
docker compose up -d
After upgrading, check the logs:
docker compose logs -f --tail=100
A more cautious approach is to pin the version tag, for example:
image: vaultwarden/server:1.36.0
Personal users can use latest for convenience, but for production or team scenarios it is more recommended to pin versions and upgrade after testing. Either way, back up vw-data before upgrading.
First check the container:
docker compose ps
docker compose logs --tail=100
Then check the local port:
ss -lntp | grep 8000
curl -I http://127.0.0.1:8000
If local curl does not work, the problem is in the container; if local works but the domain does not, the problem is usually in Caddy, DNS, firewall or security group.
Check whether the Caddy reverse proxy address matches the Compose port:
Compose: 127.0.0.1:8000:80
Caddy: reverse_proxy 127.0.0.1:8000
The ports on both sides must correspond. After modifying, run:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
Focus on checking DOMAIN:
docker compose exec vaultwarden printenv DOMAIN
It should be a complete HTTPS address:
https://vault.example.com
Do not write a bare domain, and do not write HTTP.
Check the SMTP configuration:
docker compose logs --tail=200 | grep -i smtp
Common causes:
- The SMTP password is not an authorization code.
- The port and encryption method do not match.
- The email provider restricts third-party clients.
- Outbound SMTP from the VPS is restricted by the cloud provider.
- The sender address and account do not match.
If port 25 is blocked, prefer 587 + STARTTLS or 465 + SSL.
Use the invitation feature. You can invite new users via the admin page or an organization. Do not temporarily open public registration long-term just to invite one person.
If you must enable it temporarily:
SIGNUPS_ALLOWED: "true"
Immediately change it back after creation:
SIGNUPS_ALLOWED: "false"
Then:
docker compose up -d
Before going live, confirm item by item:
- The domain uses HTTPS.
- The container port is bound only to
127.0.0.1. -
SIGNUPS_ALLOWED=false. - The administrator account uses a strong master password.
- The administrator has enabled TOTP.
- SMTP is available.
-
/adminis restricted or not enabled. -
vw-datahas been backed up to a second location. - A restore drill has been performed once.
- The VPS system is updated regularly.
- SSH does not use weak passwords.
A password manager is not an ordinary blog. Even though Vaultwarden itself is lightweight enough, deployment habits must be more cautious.
The core steps of setting up Vaultwarden on a VPS are not complicated: create the data directory, write the Docker Compose file, persist /data, use 127.0.0.1:8000:80 to avoid directly exposing the container port, then reverse proxy https://vault.example.com in with Caddy.
What really determines security is these subsequent details: disable public registration, configure SMTP to invite users, enable two-step verification for the administrator, restrict /admin, regularly back up vw-data, and actually test restore.
If you only have one low-spec VPS, Vaultwarden is a self-hosted service well worth deploying; but if you are unwilling to maintain backups and security updates, the official Bitwarden cloud service may actually be less trouble. The freedom of self-hosting must come with the responsibility of self-hosting.