Vps Pacman
vps-pacman Host Configuration
Role: Edge VPS (L1 gateway) — public SSL terminator and reverse proxy
Platform: x86_64-linux (Vultr VPS, QEMU guest)
Architecture Role
vps-pacman is the L1 gateway in our two-layer architecture. It is the only host with a public IP. It terminates SSL, handles ACME wildcard certificate issuance, runs OAuth2-Proxy for public SSO, and reverse-proxies all traffic to lab-matrix over Tailscale.
Services
| Category | Service | Purpose |
|---|---|---|
| Web | Nginx | Public SSL terminator (QUIC/kTLS), reverse proxy to L2 |
| Certs | ACME | Wildcard *.alienzj.org via Cloudflare DNS-01 |
| SSO | OAuth2-Proxy | Public SSO gateway (auth.alienzj.org) |
| Proxy | Sing-box | Multi-port egress proxy |
| Relay | Syncthing relay | Public syncthing relay server |
| Firewall | Fail2ban | Log-based intrusion prevention |
Imported Modules
modules/modules.nix: Server role, network profiles (jp/vps, ts0), SSH hardening, sing-boxmodules/storage.nix: VPS block storage with diskomodules/nginx.nix: L1→L2 proxy map (homelabServices) — auto-generates vhosts for all public services, proxying to lab-matrix over Tailscale MagicDNS
Nginx Proxy Map
The L1→L2 mapping is defined declaratively in hosts/vps-pacman/modules/nginx.nix:
homelabServices = {
id = "lab-matrix"; # Kanidm
atuin = "lab-matrix"; # Atuin shell history
mail = "lab-matrix"; # Stalwart
git = "lab-matrix"; # Forgejo
rss = "lab-matrix"; # FreshRSS
bw = "lab-matrix"; # Vaultwarden
books = "lab-matrix"; # Calibre
immich = "lab-matrix"; # Immich
links = "lab-matrix"; # Linkwarden
pdf = "lab-matrix"; # Stirling-PDF
affine = "lab-matrix"; # Affine
ollama = "id3-eniac"; # Ollama (separate host)
jupyter = "id3-eniac"; # Jupyter (separate host)
};
Each entry auto-generates a vhost at <name>.alienzj.org that proxies to the target host over Tailscale. Services can run on any L2 host in the tailnet — lab-matrix for most services, id3-eniac for GPU-accelerated workloads (Ollama, JupyterHub). Adding a new service on any host is a one-line change to this map.
Tailscale DNS Resolution
Backend hostnames are resolved via the Tailscale MagicDNS resolver. There are two modes, controlled by the modules.services.web.nginx.homelabProxy.tailnetSuffix option in the host config:
| Mode | Config | How it works |
|---|---|---|
| MagicDNS FQDN (default for servers) | tailnetSuffix = "tailf0ad7b.ts.net" | Resolves ${host}.${tailnetSuffix}. Works when --accept-dns is disabled on the node (common on servers to avoid Tailscale taking over system DNS). |
| Short hostname | tailnetSuffix = null | Resolves bare ${host}. Requires tailscale set --accept-dns=true on the node so the resolver auto-appends the MagicDNS search domain. |
Find your tailnet suffix with tailscale dns status on any node in the tailnet.
Traffic between L1 and L2 is plain HTTP inside the Tailscale WireGuard tunnel — TLS is terminated at L1 and not re-established to L2. The WireGuard link provides ChaCha20-Poly1305 encryption, so double TLS is unnecessary.
Setup
See Homelab Bootstrap Guide for the step-by-step operational setup — SSH key provisioning, secrets preparation, OAuth2-Proxy env file, and ACME certificate issuance.