Skip to main content

HomeLab Part 2: Self-Hosted Password Manager on Proxmox with Vaultwarden, Caddy, and Tailscale

·1513 words·8 mins
Jack Warner
Author
Jack Warner
A little blog by me
Table of Contents

The Hardware
#

In Part 1, we ran everything on a Raspberry Pi. This time, we’re stepping up to a proper server — a MINISFORUM MS-A2.

Spec Details
CPU AMD Ryzen 9 9955HX, 16C/32T
RAM Dual DDR5-5600MHz 64GB
Storage M.2 PCIe 4.0 SSD (1TB + 1TB)
Networking Dual 10Gbps SFP+ & 2.5G RJ45
Wireless WiFi 6E & BT 5.3
Expansion Built-in PCIe x16 Slot

This thing is wildly overpowered for a password manager, but the goal is to build a proper homelab foundation. Vaultwarden is just the first container.

Why Not the Raspberry Pi Approach?
#

In Part 1, we forwarded ports 80 and 443 to the internet, pointed a domain at our public IP, and used Nginx Proxy Manager with Let’s Encrypt. That works, but it means your password vault is sitting on the public internet. Anyone can find it. Anyone can try to attack it.

This time: zero public exposure. No port forwarding. No public DNS. No attack surface. We use Tailscale to create a private encrypted tunnel between our devices. Your vault is invisible to the internet.

Step 1: Install Proxmox
#

  • Download the latest Proxmox VE ISO from proxmox.com/en/downloads
  • Flash it to a USB drive using balenaEtcher or Rufus
  • Boot the MINISFORUM from USB (mash Del or F7 for boot menu)
  • Walk through the installer:
    • Pick your boot drive (one of the two NVMe SSDs)
    • Set a strong root password and write it down
    • Configure the network — plug the 2.5G RJ45 into your router
    • Note the IP address it assigns
  • After reboot, access the web UI at https://<your-ip>:8006
  • Your browser will warn about the certificate — click through it, that’s normal

Step 2: Remove Enterprise Repos
#

Proxmox will nag you about a subscription and fail to update from enterprise repos. Let’s fix that.

SSH into your Proxmox host or use the web UI shell:

# Add the free community repo
echo "deb http://download.proxmox.com/debian/pve trixie pve-no-subscription" > /etc/apt/sources.list.d/pve-no-subscription.list

# Disable enterprise repos
mv /etc/apt/sources.list.d/pve-enterprise.sources /etc/apt/sources.list.d/pve-enterprise.sources.disabled
mv /etc/apt/sources.list.d/ceph.sources /etc/apt/sources.list.d/ceph.sources.disabled

# Update
apt update && apt upgrade -y

Note: Your enterprise repo files might be .list instead of .sources depending on the Proxmox version. Check with ls /etc/apt/sources.list.d/ and adjust accordingly.

Step 3: Download a Container Template
#

  • In the Proxmox sidebar, click on local (pve)
  • Click CT Templates
  • Click Templates → search for debian-12-standardDownload

Step 4: Create the Vaultwarden Container
#

Click Create CT in the top right. Fill out each tab:

Tab Setting Value
General CT ID 100
General Hostname vaultwarden
General Unprivileged container ✅ Checked
General Nesting ✅ Checked
General Password Set a strong password
Template Template debian-12-standard
Disks Storage / Size local-lvm / 8 GB
CPU Cores 2
Memory Memory / Swap 1024 MB / 512 MB
Network Bridge vmbr0
Network IPv4 DHCP
Network Firewall ✅ Checked
DNS Leave defaults

Don’t check “Start after created.” We need to add TUN device support first.

Enable TUN Device for Tailscale
#

On the Proxmox host shell, add TUN support to the container config:

echo "lxc.cgroup2.devices.allow: c 10:200 rwm" >> /etc/pve/lxc/100.conf
echo "lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file" >> /etc/pve/lxc/100.conf

Now start the container and open its console.

Step 5: Install Docker
#

apt update && apt upgrade -y
apt install -y curl ca-certificates gnupg
curl -fsSL https://get.docker.com | sh

Step 6: Deploy Vaultwarden
#

docker run -d --name vaultwarden \
  -e SIGNUPS_ALLOWED=true \
  -e INVITATIONS_ALLOWED=false \
  -e SHOW_PASSWORD_HINT=false \
  -v /vw-data/:/data/ \
  -p 127.0.0.1:8080:80 \
  --restart unless-stopped \
  vaultwarden/server:latest

Key security decisions here:

  • SIGNUPS_ALLOWED=true — temporarily, so we can create our account. We’ll disable this immediately after.
  • SHOW_PASSWORD_HINT=false — no password hints leaked to attackers.
  • 127.0.0.1:8080:80 — only listens on localhost. Can’t be accessed directly from the network.

Step 7: Set Up Caddy as Reverse Proxy with HTTPS
#

Vaultwarden requires HTTPS for the Web Crypto API. We’ll use Caddy with a self-signed certificate.

Install Caddy
#

apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install -y caddy

Generate Self-Signed Certificate
#

mkdir -p /etc/caddy/certs
openssl req -x509 -newkey rsa:4096 \
  -keyout /etc/caddy/certs/key.pem \
  -out /etc/caddy/certs/cert.pem \
  -days 365 -nodes -subj "/CN=vault.local"
chown caddy:caddy /etc/caddy/certs/*
chmod 640 /etc/caddy/certs/*

Configure Caddy
#

cat > /etc/caddy/Caddyfile << 'EOF'
vault.local {
  tls /etc/caddy/certs/cert.pem /etc/caddy/certs/key.pem
  reverse_proxy 127.0.0.1:8080
}
EOF

Add the hostname to the container’s hosts file:

echo "127.0.0.1 vault.local" >> /etc/hosts

Restart Caddy:

systemctl restart caddy

Set Up Local DNS on Your Computer
#

On macOS:

sudo nano /etc/hosts
# Add this line (use your container's IP from `hostname -I`):
192.168.0.XXX    vault.local

On Windows, edit C:\Windows\System32\drivers\etc\hosts as Administrator with the same line.

Now visit https://vault.local in your browser. Click through the certificate warning and create your account.

Step 8: Lock Down Signups
#

Immediately after creating your account:

docker stop vaultwarden
docker rm vaultwarden
docker run -d --name vaultwarden \
  -e SIGNUPS_ALLOWED=false \
  -e INVITATIONS_ALLOWED=false \
  -e SHOW_PASSWORD_HINT=false \
  -v /vw-data/:/data/ \
  -p 127.0.0.1:8080:80 \
  --restart unless-stopped \
  vaultwarden/server:latest

Your data persists in /vw-data/, but no one else can register.

Step 9: Enable MFA on Vaultwarden
#

Log into your vault and:

  1. Click your profile icon → Account Settings
  2. Go to SecurityTwo-step login
  3. Set up Authenticator app (TOTP) with Google Authenticator, Authy, or similar
  4. Save your recovery code somewhere safe — print it or write it down, don’t store it in the vault itself

Step 10: Set Up Tailscale for Zero-Trust Access
#

This is the key difference from Part 1. Instead of exposing the vault to the internet, we create a private network.

Install Tailscale in the Container
#

curl -fsSL https://tailscale.com/install.sh | sh
tailscaled &
tailscale up

It will give you a URL to authenticate. Open it, sign in (I recommend a dedicated homelab email, not your personal one), and approve the device.

tailscale ip -4
# Note this IP (e.g., 100.x.x.x)

Install Tailscale on Your Devices
#

Install Tailscale on every device you want to access the vault from — Mac, Windows, iPhone, Android. Sign in with the same account.

Update Your Hosts File
#

Point vault.local to the Tailscale IP instead of the LAN IP:

# macOS
sudo nano /etc/hosts
# Change to:
100.x.x.x    vault.local

Now https://vault.local routes through the encrypted Tailscale tunnel. You can access it from anywhere — home, work, coffee shop — without any ports open.

Enable MagicDNS (Optional)
#

Go to login.tailscale.com/admin/dns and enable MagicDNS. This lets you access machines by name (e.g., vaultwarden) from any device on your Tailscale network without editing hosts files.

Enable Container Firewall
#

In the Proxmox web UI, go to 100 (vaultwarden)Network → double-click eth0 → check Firewall. With no allow rules, the container is completely locked to Tailscale traffic only.

Step 11: Enable Auto-Start
#

Inside the container:

systemctl enable tailscaled
systemctl enable docker

Step 12: Harden the Proxmox Host
#

Install Tailscale on the Host
#

curl -fsSL https://tailscale.com/install.sh | sh
tailscale up

Now you can access the Proxmox web UI via Tailscale too: https://<proxmox-tailscale-ip>:8006.

SSH Key-Only Authentication
#

On your local machine:

ssh-keygen -t ed25519 -C "my-key"
ssh-copy-id root@<proxmox-tailscale-ip>

Verify you can log in without a password, then disable password auth:

# On the Proxmox host
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd

Install fail2ban
#

apt install -y fail2ban

Enable 2FA on Proxmox Web UI
#

In the web UI: DatacenterPermissionsTwo FactorAddTOTP. Scan with your authenticator app.

Enable Automatic Security Updates
#

apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

Enable Node Firewall
#

In the web UI: Node (pve)FirewallOptions → set Firewall to Yes.

Then add rules under FirewallRules:

Direction Action Protocol Dest. Port Comment
In ACCEPT tcp 8006 Proxmox Web UI
In ACCEPT tcp 22 SSH

Step 13: Set Up Backups
#

In the Proxmox web UI:

  1. DatacenterBackupAdd
  2. Storage: local
  3. Schedule: daily
  4. Selection: Include → select 100 (vaultwarden)
  5. Mode: Snapshot
  6. Compression: ZSTD
  7. Retention tab: Keep last 7
  8. Create

Your entire container gets backed up daily. If anything goes wrong, restore in one click.

Architecture Overview
#

Here’s what we built:

You (any device with Tailscale)
  ├── Encrypted Tailscale Tunnel
  ├── Proxmox Host (hardened)
  │   ├── SSH key-only auth
  │   ├── fail2ban
  │   ├── 2FA on web UI
  │   ├── Firewall enabled
  │   └── Automatic security updates
  └── CT 100: Vaultwarden
      ├── Unprivileged LXC container
      ├── Caddy reverse proxy (HTTPS)
      ├── Signups disabled
      ├── MFA enabled
      ├── Firewall (Tailscale-only access)
      └── Daily backups (7-day retention)

Part 1 vs Part 2
#

Feature Part 1 (Raspberry Pi) Part 2 (Proxmox)
Hardware Raspberry Pi 5 MINISFORUM MS-A2 (Ryzen 9)
Container Docker on bare metal Docker in unprivileged LXC
HTTPS Let’s Encrypt via NPM Self-signed via Caddy
Access Public internet (port forward) Tailscale only (zero public exposure)
DNS Cloudflare public DNS Local hosts / MagicDNS
Auth Password only Password + MFA
Host hardening Minimal SSH keys, fail2ban, 2FA, firewall
Backups None Daily snapshots, 7-day retention
Cost ~$123 we dont talk about it
Attack surface Ports 80/443 open to internet Nothing open to internet

Related