Production IP addresses and domain names have been replaced with generic labels for security reasons.
Context
A cross-cutting project combining three core components of homelab security: secret management (Vaultwarden), secure service exposure (NPM + SSL + DNS), and remote access without public exposure (Tailscale VPN). Together they form a coherent architecture where no secret travels in plaintext and no service is directly exposed to the Internet.
1 — DNS Architecture (AdGuard Home + DoT)
AdGuard Home is deployed as an LXC on PVE-02 (lxc-adguard-01, VLAN 10 MGMT). It acts as the primary DNS resolver for the entire homelab:
Clients (all VLANs) → AdGuard Home :53 (VLAN 10 MGMT)
│
┌───────────────┴──────────────────┐
│ │
Internal zone (Rewrites) DNS upstream (DoT)
*.[my-domain.fr] → [NPM] Cloudflare 1.1.1.1:853
*.homelab.lan → Unbound pfSense Quad9 9.9.9.9:853
Key decisions:
- DNS over TLS (DoT) to Cloudflare (
1.1.1.1:853) and Quad9 (9.9.9.9:853): no DNS queries in plaintext to the Internet - Split-horizon DNS:
*.[my-domain.fr]FQDNs resolve to NPM internally (no WAN egress),*.homelab.lanresolved by pfSense Unbound - Ad and tracker filtering: active blocklists (EasyList, OISD)
- AdGuard is only accessible from internal VLAN 10 interfaces
2 — HTTPS Exposure (NPM + Let's Encrypt wildcard)
Nginx Proxy Manager (lxc-npm-01, VLAN 10 MGMT) is the reverse proxy for all exposed services. It holds the wildcard certificate *.[my-domain.fr] issued by Let's Encrypt via the DNS-01 Cloudflare challenge — no permanent inbound port 80/443 exposure required.
Browser → https://service.[my-domain.fr]
│
NPM (VLAN 10 MGMT :443)
│ Wildcard SSL *.[my-domain.fr]
│ Force SSL, HSTS
│ Access list : 172.16.0.0/16 + Tailscale CGNAT
▼
Internal service (HTTP) → response
Exposed services (excerpt):
| FQDN | Backend | Notes |
|---|---|---|
vaultwarden.[my-domain.fr] |
Vaultwarden :8080 | Never publicly exposed (NXDOMAIN on public DNS) |
grafana.[my-domain.fr] |
Grafana :3000 | Accessible via Tailscale only |
zabbix.[my-domain.fr] |
Zabbix :80 | pfSense NAT port-forward |
n8n.[my-domain.fr] |
n8n :5678 | Internal access only |
3 — Vaultwarden (self-hosted password vault)
Vaultwarden is an open-source implementation of the Bitwarden protocol deployed as an LXC on PVE-02 (lxc-vaultwarden-01, VLAN 10 MGMT), using Docker-in-LXC with a pinned image.
Security decisions
| Decision | Rationale |
|---|---|
| Tailscale-only | No public port-forward; NXDOMAIN on public DNS → unreachable from the Internet |
| Docker-in-LXC | CVE updates with a single command (docker pull of new tag) |
| SQLite (not MariaDB) | Single file to back up, trivial restore |
| Pinned image | No silent version change |
| Monthly KeePassXC export | Offline encrypted backup (break-glass when unavailable) |
# Docker deployment
docker run -d \
--name vaultwarden \
--restart unless-stopped \
-e DOMAIN="https://vaultwarden.[my-domain.fr]" \
-e SIGNUPS_ALLOWED=false \
-v /opt/vaultwarden/data:/data \
-p 8080:80 \
vaultwarden/server:<pinned-version>
SIGNUPS_ALLOWED=false: no account creation possible except by explicit invitation.
Critical fix — SameSite Cookie
Symptom: login accepted but looping back to the login page indefinitely.
Cause: cookie_samesite = none combined with cookie_secure = false — a combination rejected by modern browsers (SameSite=None requires Secure).
Fix: set cookie_secure = true in config + enforce HTTPS-only access → issue resolved.
4 — Tailscale VPN (remote access without open ports)
Tailscale is installed on pfsense-vm (pfSense mirror VM on PVE-02). It acts as a subnet router for the VLAN 10 MGMT network: any authorised device can reach internal services without a single inbound port open on the internet router.
iPhone / MacBook (anywhere in the world)
│ Tailscale (WireGuard underneath)
▼
pfsense-vm — Tailscale subnet router (VLAN 10 MGMT)
│ subnet route 172.16.10.0/24
▼
VLAN 10 services (Vaultwarden, Grafana, Zabbix, NPM…)
- Tailscale Split-DNS:
*.[my-domain.fr]queries from Tailscale devices resolve via internal AdGuard → traffic stays internal, no WAN loop - MagicDNS disabled: managed manually to retain full DNS control
- Authentication: personal account with periodic authorisation key renewal
Global Architecture
┌─────────────────────────────────────────────────────┐
│ INTERNET │
│ vaultwarden.[my-domain.fr] = NXDOMAIN public ✗ │
└──────────────┬──────────────────────────────────────┘
│ Tailscale only
▼
iPhone / MacBook (Tailscale client)
│ WireGuard encrypted
▼
pfsense-vm (subnet router → VLAN 10 MGMT)
│
▼
AdGuard Home (VLAN 10) ── DNS split-horizon
↓ DoT *.[my-domain.fr] → NPM
Cloudflare 1.1.1.1:853 *.homelab.lan → Unbound
│
▼
NPM (VLAN 10) ── wildcard SSL *.[my-domain.fr]
│
▼
Vaultwarden (VLAN 10 :8080) ── SQLite, pinned image
Skills Demonstrated
This project covers personal data and secret protection (B3.1 — Vaultwarden vault, end-to-end Bitwarden encryption), digital identity preservation (B3.2 — VPN-only access, public NXDOMAIN, strong authentication), device and usage hardening (B3.3 — DNS over TLS, no secrets in plaintext, NPM HSTS), and infrastructure cybersecurity (B3.5 — Tailscale zero-trust, AdGuard ACLs), as well as online presence development (B1.3 — wildcard FQDN *.[my-domain.fr], automated SSL) and service availability (B1.5 — Vaultwarden, NPM, DNS available 24/7 with no public exposure).