Files
oikos/infrastructure/vps-hardening.md
dtoro f5cd320433 Bootstrap Homelab-Docs wiki
Initial documentation of the hubris Proxmox homelab as a cross-linked
markdown wiki. Per-node pages, cross-cutting infrastructure pages, an
investigation log, and an operations cheatsheet. Each node and topic
ends with a Changelog section so changes can be tracked in-place going
forward.

Refreshed against live state on 2026-04-28 — 14 active LXCs (109
syncthing currently stopped) + 1 VM (108 haos). Reflects post-A/B-test
state of the 2026-04-21 hubris crash-loop investigation.
2026-04-28 22:53:09 +02:00

4.5 KiB

VPS hardening — 82.165.190.79 / 100.122.165.149

IONOS VPS that runs the Netbird control plane and the public ingress traefik. Hardened 2026-04-23 from its stock-Plesk state.

At a glance

  • Hostname: inspiring-ramanujan.82-165-190-79.plesk.page
  • OS: Debian 13
  • Mesh: netbird 100.122.165.149 (peer of the lab mesh; routes 192.168.8.0/24 via hubris).
  • Public: 82.165.190.79 (ens6).
  • Public DNS: IONOS wildcard *.hubris.network → 82.165.190.79.

SSH

  • Key-only (PasswordAuthentication no, PermitRootLogin prohibit-password) via drop-in at /etc/ssh/sshd_config.d/10-hubris-hardening.conf. Original config backed up at /etc/ssh/sshd_config.bak.<ts>.
  • Mesh-only: public :22 is dropped by the nftables firewall. SSH reaches the VPS only over wt0. ListenAddress itself is still 0.0.0.0 — gating is firewall-layer.
  • Authorized root keys: PVE (root@hubris), Mac Mini (d.toro.v@pm.me). Add a new device with ssh-copy-id root@100.122.165.149 from a mesh peer before disabling its access paths.

Firewall — nftables (inet hubris-fw)

Config at /etc/nftables.conf, service enabled.

  • Public iface ens6. Wireguard iface wt0.
  • INPUT on ens6 allow-list: DHCP (67→68), rate-limited ICMP/ICMPv6. Everything else drops.
  • wt0 fully accepted in INPUT. lo accepted.
  • FORWARD chain at priority filter-10 (runs before Docker's FORWARD) hosts the fail2ban ban enforcement — see below.
  • Set banned4 (typed ipv4_addr, flag timeout) holds fail2ban's drops.
  • Coexists with Docker's ip nat / ip filter tables (iptables-nft compat). Do NOT flush ruleset in this config — it'll wipe Docker's state too.

fail2ban

  • Jail traefik-4xx tails /var/log/traefik/access.log (bind-mounted from container). Filter at /etc/fail2ban/filter.d/traefik-4xx.conf matches 401/403/404/429 from blog-public@file or artifacto-public@file routers only — netbird-grpc traffic isn't considered.
  • Tunables: findtime=600, maxretry=30, bantime=3600.
  • Action at /etc/fail2ban/action.d/nft-hubris.conf adds/removes elements from inet hubris-fw banned4 with per-element timeout.

CRITICAL invariant — wireguard / fail2ban

Bans must never affect wt0 or wireguard UDP. The FORWARD chain explicitly accepts the following before the ban check:

  • udp 51820 (wireguard)
  • udp 3478 (STUN)
  • ct state established,related

The INPUT ban rule is scoped to iifname "ens6".

Violating this takes the mesh down for every home device (they share one public IP) and the only recovery is IONOS console → nft flush set inet hubris-fw banned4.

Traefik access log

  • Written to /var/log/traefik/access.log on the host via a bind mount added to /opt/docker-compose.yml (traefik volumes include /var/log/traefik:/logs) plus --accesslog.filepath=/logs/access.log.
  • CLF format. Real client IP arrives correctly because docker userland-proxy is off — see public ingress.

Plesk / mail / FTP / Dr.Web

Stopped and disabled (not uninstalled). All of: dovecot, dovecot.socket, postfix, postfix@-, pc-remote, xinetd, plesk-task-manager, plesk-web-socket, sw-cp-server, sw-engine, plesk-repaird, plesk-repaird.socket, drwebd.

psa.service is masked (was a one-shot boot bootstrap). /etc/cron.d/plesk-backup-manager-task renamed to .disabled.

Reverse: systemctl unmask psa; systemctl enable --now <svc>.

Auto-patching

  • unattended-upgrades enabled (stock).
  • Drop-in at /etc/apt/apt.conf.d/52hubris-reboot.conf sets auto-reboot at 04:00 UTC when /var/run/reboot-required is set.
  • Runs inside the stock apt-daily-upgrade.timer.

Recovery paths

Ordered by preference:

  1. SSH via mesh — primary. Any mesh peer with an authorized key.
  2. IONOS web console (my.ionos.com → VPS → Console) — uses the system password, not SSH keys. Bypasses any firewall misconfig.
  3. IONOS rescue mode — boot rescue, mount rootfs, edit /etc/nftables.conf or /etc/ssh/sshd_config.d/10-hubris-hardening.conf to a known-good state, reboot.

Changelog

2026-04-28 — wiki entry created

Initial documentation.

2026-04-23 — hardened

nftables firewall, mesh-only SSH, fail2ban traefik jail, Plesk disabled, auto-reboot 04:00 UTC, wireguard/fail2ban invariant established.