/ ledger
The ledger
every entry in the manual, newest first. filter by system, or search the whole manual with ctrl+k.
start here
new to the manual? three entries, in the order i would hand them to you: the foundation, the meat, the launch.
- i Knowing the network before building on it
The baseline before any of the homelab work: understanding the ISP gateway, the router, and the DNS path, then the Xfinity to AT&T Fiber migration and the bridge-mode versus IP-passthrough difference.
- ii Moving services off my workstation and into Proxmox
The homelab services started on a gaming PC. This phase moved them onto a Proxmox host: an Omada Controller LXC, a Docker monitoring VM, a self-hosted RustDesk server, and validated backups.
- iii stayz3ro.dev on Cloudflare: one zone, a static blog and a VPS
The second Cloudflare cutover. A freshly registered domain, apex and www on Pages, service subdomains on a VPS behind Caddy, and email locked shut on a domain that sends none.
46 of 46 entries
- entry-48 One Caddy VM and one wildcard certificate
I put the lab services behind one internal Caddy on the proxy VM, with a single wildcard from DNS-01 and a Cloudflare token locked to the home address. The lab names were too long to use, the Pi-holes blocked the new client, and Chrome flagged n8n.chrisalorenzo.com/signin, so n8n became flow.
- entry-49 Putting Forgejo behind the proxy without opening it
Forgejo ran with its HTTP port on loopback only. Moving it onto the internal proxy meant publishing that port to the LAN and then letting exactly one host reach it. ufw does not see Docker published ports, so the control had to go in the DOCKER-USER chain: match the original destination port after DNAT, scope every rule to the container address, and put the drop in before the port is published.
- entry-50 Rotating the tokens behind 21 mirrors
Twenty-one Forgejo push mirrors needed a new GitHub token, swapped by creating the new mirror, syncing it, verifying the refs, and only then deleting the old one. The sync-all call returns 500 when an old mirror is already broken, so four repositories waited on the queue and were checked by hand. The pull mirrors on the relay VPS moved to a read-only token, and nothing old was revoked until both sides matched.
- entry-51 Shipping logs to Loki, journal first
I added Loki and Alloy to the monitoring VM, proved an SSH failure alert, and shipped the journal from n8n and Forgejo. A storage budget, a log sample review, and a container that could not write its own state shaped the rollout.
- entry-52 Umami behind a Cloudflare-only firewall
I deployed self-hosted analytics with a public tracker and a private dashboard. The work was in a DNS record that disagreed with the firewall, an Astro script tag that lost its configuration, and an old analytics beacon filling the CSP report.
- entry-41 Forgejo as primary, GitHub as a mirror
All 21 homelab repos now live on self-hosted Forgejo, with GitHub as a push mirror and a pull mirror on a cloud VM. Private GitHub repos cannot be locked without Pro, and the 443 hang was a WSL2 path MTU blackhole.
- entry-45 The warning alerts that vanished after I added phone push
A valid Alertmanager route sent warnings to my phone but quietly stopped sending them to Discord. One route test exposed the missing path, and one final child route restored it.
- entry-47 A long session of parallel coding agents, and the one guardrail I broke
I used several Claude sessions and other CLI tools for a repo migration. Peer confirmation, the permission classifier, and a cold-read handoff test all held. The secrets rule did not, and the tokens now wait for rotation.
- entry-46 Protect the branch where the pushes actually land
My GitHub ruleset rejected my own push mirror with GH013, because a personal account cannot exempt a mirror identity. Branch protection belongs on the self-hosted side, where the pushes actually land, and it is free there.
- entry-44 The HTTPS request that hung while ping worked
A request from my WSL2 workstation to my self-hosted git server hung forever. Ping, SSH, and a small TLS alert all succeeded. The path was dropping large packets and sending nothing back.
- entry-43 One firewall bug, two hosts, and a weekly report
Building the n8n and Forgejo VMs turned up a Docker firewall gap that UFW never saw. The same class of bug had been quietly breaking my VPS status page for a week. Then the first n8n workflow on the new VM found four bugs of its own.
- entry-19 The HA DNS pair finally syncs: from Gravity Sync to Nebula Sync
Nebula Sync replaced an unverified replication layer on the Pi-hole v6 pair. A live session on 2026-10-05 deployed it, proved the alert path with a real failure, passed a controlled failover, and rotated a VRRP password that had been exposed in screenshots.
- entry-35 Moving the blog and status page to a new domain
On 2026-10-04 the blog and the public status page moved under chrisalorenzo.com, the old hosts began redirecting, and the status admin moved behind a tailnet-only path. Here is what was checked from outside and what is still open.
- entry-34 Patching four cloud servers took more than apt upgrade
I cut off my own SSH session during an upgrade, found a kernel without its initramfs, and rescued a small VM from the console. Four servers, 99 package updates, and some unfinished hardening.
- entry-36 October 4, 2026: checking what finished during a Proxmox upgrade disconnect
Two connection resets during three-node maintenance left different package states. Reconnecting and checking the evidence determined what needed to run next.
- entry-33 Moving my home network from Omada to UniFi, looking back
I replaced the router and core switch under everything in the lab, Proxmox hosts, DNS, backups, and remote access included, without changing the network they sit on.
- entry-32 Permission denied, and the server was fine
A read-only audit of two Oracle Cloud VMs and a GCP VM failed to log in. Where the error was Permission denied (publickey), the keys were already authorized. Batch mode never offered them, because the private keys have passphrases.
- entry-31 Self-hosted alerting with Uptime Kuma and ntfy
I added real checks and tested alerts to an empty Kuma install, then put ntfy behind Caddy and learned why the credential a test sends matters more than the one I meant to paste.
- entry-26 What a parallel review taught me about publication gates
A transfer-pack review exposed stale infrastructure facts, overlooked disclosure patterns, and private names in public commit messages. The useful work was defining the gates and proving each correction.
- entry-30 Renaming ten repositories without breaking anything
Ten repositories renamed in one evening. GitHub redirects cover git URLs and remotes, Pages project paths do not, and a rename-only sweep per repository made every changed line provable.
- entry-29 Seven days on a power strip: a Proxmox node saga
A hardware swap passed validation, then the node reset dozens of times a day. Diagnostics came back clean. The fix was not a new part: it was moving the node off a shared UPS onto a plain power strip and waiting a week. Root cause is still unproven.
- entry-28 The API key my coding agent was actually sending
Every model on one provider returned a 400 insufficient credits error inside a terminal coding agent while the same provider worked from its own CLI. The agent was configured with a different key, one with no balance.
- entry-27 The standby router that couldn't tell time
A post-cutover health check found the standby Tailscale subnet router with a clock almost an hour and a half behind. It looked like an NTP problem. It was a DNS problem wearing an NTP problem's clothes.
- entry-25 Swapping the network core without changing the network
The ER605 and Omada switch came out and a UniFi Dream Machine Pro and USW-24-PoE went in, with the same subnet, gateway, DHCP, and DNS as before. Part 2 of 2: the equivalent-state cutover, done in two stages with about two minutes of real downtime.
- entry-18 When the homelab outgrew its spreadsheet, I built a command center
Building a private FastAPI and React dashboard around the lab inventory: 23 seeded device records, 14 services, Prometheus telemetry, audited changes, and explicit topology relationships.
- entry-17 Where 875GB of disk actually went, and the backup that came out of it
A 953GB drive down to 88GB free, traced to a bloated WSL vhdx, Docker layer cache, and an orphaned OneDrive folder that was syncing to nothing. Fixed with sparse VHD mode and a distro cleanup, then backed up for real with rclone to Proton Drive and OneDrive.
- entry-13 stayz3ro.dev on Cloudflare: one zone, a static blog and a VPS
The second Cloudflare cutover. A freshly registered domain, apex and www on Pages, service subdomains on a VPS behind Caddy, and email locked shut on a domain that sends none.
- entry-16 Publishing homelab evidence without leaking it
A repeatable, OCR-assisted, multi-pass workflow for redacting homelab screenshots before they go into public repos: locate the evidence, detect IPs, MACs and personal identifiers with tesseract, verify by hand on the redacted output, then embed the cleared images in validation docs.
- entry-15 A public status page with a private admin
Deploying Uptime Kuma behind Caddy on the public VPS: HTTPS and headers validated from outside, backend ports left closed, and the admin account claimed over a private SSH tunnel before the certificate made the hostname discoverable.
- entry-12 Moving my portfolio from GitHub Pages to Cloudflare Pages
chrisalorenzo.com was a GitHub Pages project site with DNS parked at the registrar. This is the migration to Cloudflare Pages, DNS and all, plus the parts that bit me.
- entry-11 The DNS panel that wasn't authoritative
Deploying Caddy for status.stayz3ro.dev should have taken an hour. It took a night, because every DNS record I added went to a zone nothing on the internet was reading anymore.
- entry-38 The update checker that succeeded without checking anything
An npm update checker exited successfully after skipping its work. Its systemd service lacked the NVM environment that made the same script work in a terminal.
- entry-10 Building a backup system I actually tested
Proxmox Backup Server with real restore tests, then a verified encrypted offsite backup to Backblaze B2 with Kopia. The result is a backup system I trust because I broke it on purpose and watched it recover.
- entry-14 New network hardware, validated on a bench before it touches production
A UniFi Dream Machine Pro and a 24-port PoE switch arrived to replace the ER605 and Omada core. Before either went anywhere near production, both went through adoption, port and PoE testing, reboot persistence, and a real backup restore on an isolated bench with no path to production. Part 1 of 2: the intake and isolated baseline. Part 2 covers the cutover.
- entry-39 Retiring the fallback disks, and the watchdog that outlived them
Two old fallback disks came out of a three-node Proxmox cluster over two days in July. Pulling the hardware was the easy part. The automation that watched for it had to go too, and a host that boots is not the same as a host you have validated.
- entry-37 Updating the BIOS and deferring the CPU
A BIOS-only maintenance window on one Proxmox node, with the CPU replacement deliberately left for later. The window also turned up a storage problem after reboot that needed real recovery work.
- entry-40 Rebalancing RustDesk and a subnet router across Proxmox nodes
RustDesk and a second Tailscale subnet router moved to the newer Proxmox node. A real remote session proved RustDesk worked at the new location, and access was checked from outside the LAN to prove the subnet-router failover actually held.
- entry-09 Moving HA DNS off the Raspberry Pis and onto Proxmox
The HA DNS pair started on two Raspberry Pis. This phase rebuilt it as Proxmox VMs provisioned with OpenTofu, moved the production VIP across with a tested rollback path, then stood up the cluster underneath it.
- entry-08 Hardening a public VPS before it hosts anything
A fresh Netcup VPS: non-root access, key-only SSH with a safe reload workflow, UFW, Fail2Ban, then moving SSH off the public internet once a domain pointed at it.
- entry-07 Cutting over from mesh Wi-Fi to a managed network, live
Moving routing and DHCP off a Deco mesh onto a TP-Link ER605 and a managed switch, with the Deco dropped to AP mode, while HA DNS and the Proxmox services stayed up.
- entry-06 Moving services off my workstation and into Proxmox
The homelab services started on a gaming PC. This phase moved them onto a Proxmox host: an Omada Controller LXC, a Docker monitoring VM, a self-hosted RustDesk server, and validated backups.
- entry-05 Remote access without opening ports
Adding Tailscale to the homelab for private administration, plus a subnet router so the whole LAN is reachable, with no public SSH anywhere.
- entry-04 Monitoring the DNS stack
Adding Prometheus, Grafana, Blackbox and Node Exporter, and Alertmanager to the HA DNS setup, and testing the alerts by breaking things on purpose.
- entry-03 Building highly-available DNS for my home lab
Two Raspberry Pi nodes running Pi-hole and Unbound, a Keepalived virtual IP, and monitoring that watches the client path. The build and the failover tests.
- entry-02 Taking control of DNS with Pi-hole
The first real change to the homelab: a single Pi-hole node in the DNS path for filtering and query visibility, and why DNS is a good place to start.
- entry-01 Knowing the network before building on it
The baseline before any of the homelab work: understanding the ISP gateway, the router, and the DNS path, then the Xfinity to AT&T Fiber migration and the bridge-mode versus IP-passthrough difference.