Homelab hardening & hygiene checklist¶
Things to make sure are done, distilled from the lessons on the other troubleshooting pages. None of them is hard; all of them are easy to forget. Each item says why in one line and links to the details. Work through it once, then revisit it whenever you add a host or a service, and every few months anyway, because "reviewed, it's LAN only" decisions go stale.
Tip
The checkboxes don't save. Copy the list into an issue in your own tracker and tick it off there.
Updates & deploys¶
- Pin every image to an exact version (
tag, ortag@sha256:…when upstream re-pushes tags) — floating tags upgrade things you didn't touch. GitOps & updates - Let Renovate (or similar) open PRs for updates, automerge only low-risk patch/minor during waking hours — updates stay visible and reviewable. GitOps
- Auto-update off for stateful and remote-access services (vault, Git server, databases, home automation, reverse proxy, tunnel) — a bad update there is the one that hurts. GitOps & updates
- Pin the deploy tool's own compose and keep it in git — it's the one stack nothing else redeploys. GitOps & updates
- Authenticate registry pulls and don't pull on every deploy — Docker Hub 429s abort deploys silently. GitOps & updates
- Alert on failed deploys and retry on a schedule — a failed deploy otherwise waits for the next push. GitOps & updates
- Automatic security updates on every host, with reboots only when required, in a window that waits for backups and parity jobs — patches that need a human don't get applied. Host maintenance
- Docker engine upgrades by hand (excluded from unattended upgrades and
needrestart) — an engine upgrade restarts every container. Host maintenance - Router firmware: release notifications on, snapshot before every update and every big rule change — the router is the most exposed box and the hardest to recover. OPNsense maintenance
Containers & compose¶
- Healthcheck on every long-running service, using a probe the image actually has —
Upsays nothing about whether it works. Docker & systemd - Realistic
start_periodon dependencies used withservice_healthy— slow starters fail the whole deploy. GitOps & updates - One restart policy fleet-wide (
unless-stopped) — consistency makes "why is this running?" answerable. Docker & systemd -
stop_grace_period: 60son databases and"shutdown-timeout": 60indaemon.json— 10 s isn't enough for MongoDB and friends. Docker & systemd - Absolute bind-mount paths for all state (
/mnt/appdata/<service>) —./datalives inside the deploy tool's clone. GitOps & updates -
docker compose configshows no empty variables (PUID,PGID,TZ,user:) — empty falls back to root or UTC silently. Docker & systemd - Log size limits in
daemon.json— unbounded JSON logs fill the root disk. Docker post-installation - Docker DNS pinned in
daemon.json— containers don't inherit a broken boot-timeresolv.conf. Hosts, reboots & power - No secrets on command lines (
--requirepass,-a <password>) — they show up inpsfor every user. Docker & systemd - No
docker.sockin dashboards without a socket proxy,no-new-privilegeswhere the image allows it —:roon the socket doesn't limit the API. Docker & systemd - Old stack directories archived and deleted after a migration — a stray
compose upthere starts a duplicate on old data. Docker & systemd
Backups¶
- One backup tool, every host, all of
/mnt/appdataplus host config (/etc, fstab, units, router and plugin configs) — a backup that skips the bootstrap config can't rebuild the host. Backups - Database dumps before the snapshot (
pg_dump, SQLite.backup, app exports) — file copies of live databases can be torn. Backups - Append-only targets with keys restricted to one repo, compaction run on the target — a compromised host can't delete its own backups. Backups
- An offsite copy, upload rate capped — LAN copies don't survive the house; an uncapped upload saturates the uplink. Backups
- A second real copy of irreplaceable data (photos), not just parity — RAID/SnapRAID isn't a backup. Storage
- Restore drill done and written down, repeated every few months — green backups prove nothing until something comes back. Backups
- Repository passphrases stored outside the backed-up hosts — a vault on the dead server can't help restore it. Backups
- Auto-backup switched on in network controllers (Omada, UniFi) and their backup dir inside the backup — it's off by default. Backups
Monitoring & alerting¶
- Dead-man switch on every scheduled job (push monitor, DOWN after ~25 h without a report) — "never ran" sends no failure message. Monitoring
- Alerts sent over the LAN by IP — they must still arrive when DNS, the tunnel or the internet are the problem. Monitoring
- Escalation on repeated failures plus a daily summary — one alert and an auto-"recovered" every night looks like noise. Monitoring
- Monitors check the backend with a keyword, plus one end-to-end check of the public path — an SSO login page returns 200 for dead services. Monitoring
- No monitors on decommissioned hosts — permanent red trains you to ignore red. Monitoring
- Maintenance windows without an end date, covering the whole job — expired windows bring back nightly alert storms. Monitoring
- SMART monitoring with scheduled self-tests on every disk, alerting to something you read —
smartdmailing a host with no MTA is silence. Storage - Disk-space alerts in GiB for pools, percentage elsewhere — 95% of a big pool can be weeks from full. Storage
- Certificate expiry monitored (public, internal CA leaves, router GUI) — a CDN hides an expired origin cert until you need it. Networking & DNS
- Reboot and power-loss alerts, and router/WAN health polled — unplanned reboots otherwise go unnoticed. Monitoring
- An external dead-man for the monitoring host itself — if it's down, nothing reports that it's down. Monitoring
- Alert when your IDS bans an internal IP — a self-ban looks like a random outage. Networking & DNS
Access, auth & secrets¶
- No secrets in git; secret variables or gitignored
.env, files0600, a pre-commit scanner — history and every clone keep them forever. Security pitfalls - Anything that ever leaked is rotated, not just deleted — deletion doesn't un-leak it. Security pitfalls
- A unique password per service — one reused base password opens everything. Security pitfalls
- Login required on every app, no "disabled for local addresses" behind a proxy — through a proxy every client is "local". Security pitfalls
- SSO in front of public apps, deny by default, with a stricter policy for root-equivalent admin UIs — one wildcard policy shouldn't be all that stands between the internet and your deploy tool. Cloudflare Tunnel & NPM
- No admin UIs on the internet; reach them over a VPN or SSH forward — a login page is an attack surface. Security pitfalls
- MFA on every account that can change things (SSO provider, Tailscale, Git server, password manager, media server owner account, home automation users) — passwords leak. Tailscale
- Login bans enabled once the app sees real client IPs, trusted proxies narrowed to exact addresses — otherwise you ban the proxy or trust forged headers. Networking & DNS
- SSH keys only, no root login,
AllowUsers, and a conscious decision about passwordless sudo — the admin account is the whole fleet. SSH - Per-publisher notification tokens, write-only on their topics, anonymous access off — a leaked token can't read your alerts or spoof other sources. Monitoring
- Least-privilege API users for scripts, unused API users deleted — a dashboard key shouldn't be able to reboot the router. OPNsense maintenance
Network & DNS¶
- Published ports bound to the interface that needs them,
DOCKER-USERfiltering where needed — Docker bypasses the host firewall. Firewall - Brokers and streams authenticated (MQTT, RTSP, Zigbee2MQTT UI) — anonymous MQTT means anyone on the LAN controls your devices. Security pitfalls
- Explicit DNS records and tunnel ingress, ending in a 404 catch-all; stale records and tunnels deleted — a wildcard keeps dead names alive and publishes new ones instantly. Networking & DNS
- SPF
-all, DMARCp=rejectand CAA on domains that send no mail — free protection against spoofing. Networking & DNS - NIC set to
auto,wait-onlineworking —network-online.targetotherwise lies at boot. Networking & DNS - Network mounts as automounts with
nofail, bare mountpoint immutable — a slow NAS shouldn't block boot or swallow writes. Storage - DNS rate limits sized for your servers — a per-subnet limit drops queries silently and shows up as proxy 502s. Networking & DNS
- Firewall rule sources checked (e.g. Tailscale rules use
100.64.0.0/10, not the interface net) — a /32 "net" alias matches nobody. Networking & DNS - DHCP reservations for infrastructure and known devices, alerts on new MACs, IoT on its own VLAN — monitors by IP drift and unknown devices sit on the flat LAN. Security pitfalls
- Rules that say "Block" actually block (and are
quickwhere it matters) — descriptions lie; the rule type doesn't. VLANs
Power & hardware¶
- A UPS for router, switch and servers, with NUT ordered shutdown — every flicker is otherwise a hard power-off. Hosts, reboots & power
-
kernel.panic = 10andsystemd-pstore— the next crash reboots and leaves a log. Hosts, reboots & power - A cold-boot test of the whole rack, checking everything comes back without a hand — a power cut is the reboot test you didn't schedule. Hosts, reboots & power
- Disks referenced by serial /
by-id, bays labelled —sdXletters change between boots. Storage - A written bootstrap order for rebuilding the control host — at 2 a.m. you won't remember that the Git server can't deploy itself. GitOps & updates
Housekeeping¶
- Leftover services, plugins, repos and proxy hosts removed, not just disabled — every leftover is attack surface and a false lead.
- Stale DNS records, DHCP mappings, firewall rules and dashboard links to retired hosts cleaned up — they make the real setup hard to reason about.
- Live inventory generated, not hand-typed — hand-written host and port lists rot.
- "Accepted risk, LAN only" decisions re-reviewed every few months — the LAN changes underneath them.