Security: What I Actually Do
No theoretical checklist—this is what’s in place and why.
SSH: keys only, no passwords
On every box that has SSH (both Windows hosts, TrueNAS, and all Ubuntu VMs):
- Password auth disabled. Only public-key auth. Same key pair for the Ubuntu VMs and the Windows/TrueNAS hosts; I use one SSH config and one key path.
- Windows (Host 1 & 2): OpenSSH Server with a custom firewall rule: TCP 22 allowed only from the LAN subnet. The default “OpenSSH” rule is disabled so the scope is explicit. Authorized keys live in the usual Windows path for admin accounts.
- TrueNAS: SSH enabled, password auth off, key-based only. An init script reapplies iptables at boot so SSH is only accepted from the LAN subnet.
- Ubuntu VMs: Same key; fail2ban on top (see below). No SSH from the internet—only from the LAN—because the router doesn’t port-forward 22.
So even if something on the LAN is compromised, an attacker still needs the key. And nothing is listening for SSH from the internet.
Router (MikroTik hEX)
- No management from WAN. SSH and Winbox/WebFig are only reachable from the LAN. No port forwards for the router itself.
- Firewall: Input chain allows established/related, drops invalid, allows SSH from LAN (non-standard port), allows ICMP, then drops the rest. Forward chain: fasttrack for established, drop invalid, allow hairpin NAT, then drop new inbound that isn’t DSTNAT (so only my port-forwarded 80/443 get through).
- Brute-force protection: Multi-stage address lists. After a few failed SSH attempts from an IP, that IP is added to a temporary blacklist (e.g. 24 hours). So even from the LAN, repeated guessing gets blocked.
- Source NAT: Masquerade for outbound is restricted to the LAN subnet. That way when traffic is port-forwarded in, the source IP isn’t rewritten from the router’s perspective and nginx sees the real client IP—which I use for Pi-hole admin (allow only LAN subnet in nginx).
TLS and nginx
- Protocols: TLS 1.2 and 1.3 only; no TLS 1.0/1.1. Set in the main nginx.conf on both nodes.
- Ciphers: Modern suite (ECDHE, AES-GCM, CHACHA20-POLY1305). No legacy ciphers.
- Headers: HSTS, X-Content-Type-Options, X-Frame-Options, Content-Security-Policy where it makes sense. Same pattern for all public server blocks.
- Pi-hole (and any admin) blocks: In the relevant server blocks I use
allow <LAN subnet>; deny all;so those hostnames return 403 unless the request is from my LAN. The router’s masquerade rule is set so nginx sees the real client IP for forwarded traffic, so the allow rule works correctly.
Fail2ban on Ubuntu VMs
On all five Ubuntu VMs (Docker host, both nginx nodes, both Pi-hole VMs):
- Jail:
sshd. After N failed attempts in a time window, the source IP is banned for 10 minutes. I use a moderate bantime so I don’t lock myself out for long if I typo. - Ignore: LAN subnet and localhost are in
ignoreipso I never ban my own machines. - Config: One jail.local (with the subnet placeholder) is in the repo; I generate a substituted copy and deploy it to each VM so they all have the same policy.
Keepalived
VRRP is authenticated with a shared secret (PASS type) so a random device on the LAN can’t claim the VIP. The secret is in my local values file, not in the repo. Both nodes use the same auth pass and same VRRP ID.
Secrets and repo
- No real values in git. All configs and docs in the repo use placeholders (
detellem.com,<SUBNET>, etc.). Real values live invalues.yaml.local, which is gitignored. A validation script (make test) checks that no value from that file appears in any committed file—so I can’t accidentally commit an IP or key. - Passwords and keys: Stored in Bitwarden or in the local values file. Nothing sensitive in the repo.
What I don’t do (yet)
- VLANs — One flat LAN. I’ve considered segmenting (e.g. IoT, guest) but haven’t added a managed switch. For now I rely on host firewalls and nginx allow/deny.
- Restrict management by IP — SSH and Pi-hole admin are “LAN only” but any device on the LAN can try. Tighter would be to allow only specific source IPs (e.g. my daily driver and one or two static reservations). I may do that later.
- VPN for remote access — When I’m away I don’t VPN in; I don’t expose SSH or admin UIs to the internet. So “security” is “no attack surface from the internet except 80/443 to the VIP.”