Hardening my homelab: 9 findings to 0
I pointed a vulnerability scanner at my own homelab server, fixed what it found, and scanned again. Here are the real numbers, including the one finding I couldn’t make disappear and why that’s okay.
Same scanner, same target, before and after
The server runs Ubuntu Server 24.04 with OpenSSH, a game-server panel (AMP), Webmin and Docker. I scanned it with Greenbone Community Edition (OpenVAS) using the “Full and fast” config across all TCP ports plus the Nmap top 100 UDP ports, and filtered both reports the same way (QoD ≥ 70, no overrides).
| Severity | Before | After |
|---|---|---|
| Critical | 0 | 0 |
| High | 0 | 0 |
| Medium | 6 | 1 (false positive) |
| Low | 3 | 0 |
| Log (info only) | 43 | 51 |
Medium and Low findings went from 9 to 1, and that one is a documented false positive, so 0 real findings are left. Info-level entries went up because a new HTTPS endpoint produces informational TLS notes with no severity.
What I changed
- Weak SSH MACs (Low). I added an sshd drop-in with explicit strong
MACs,KexAlgorithms(post-quantum hybridsntrup761x25519first) andCiphers. I validated it withsshd -t, reloaded instead of restarting, and tested a new login before closing my old session. Nowssh -m umac-64@openssh.comgets “no matching MAC found”. - Cleartext panel login over HTTP (Medium). I put a hardened nginx TLS reverse proxy in front of the panel (TLS 1.2/1.3, ECDHE + AEAD ciphers, HSTS, non-root and read-only container). The firewall now drops plain HTTP except from localhost and the encrypted Tailscale network.
- Weak key exchange and CBC/3DES ciphers on the panel’s built-in SFTP servers (4 × Medium). The vendor offers no setting to fix the crypto, so I reduced exposure instead: rules in ufw’s
before.rulesallow those ports only over Tailscale and drop them everywhere else, ahead of any user rule. Residual risk: the weak algorithms still exist, but only authenticated tailnet devices can reach them. - TCP timestamps (Low). I set
net.ipv4.tcp_timestamps = 0so the server stops leaking its uptime. - ICMP timestamp replies (Low). The firewall drops ICMP type 13. Normal ping still works.
Proving a false positive
Greenbone reported “Linux Home Folder Accessible” because a request for /.ssh/known_hosts returned HTTP 200. The panel is a single-page app that returns its login page for every unknown path, so a 200 doesn’t mean a file was served, and no known_hosts content ever came back. When I moved the panel behind the HTTPS proxy, the finding moved with it to the new port. That confirms it comes from the app’s routing, not the filesystem. I documented it and accepted it rather than hiding it.
Changing a live server safely
- I backed up every file before touching it and changed one control at a time.
- After each change I checked that my SSH session still worked and the panel still loaded.
- Before rescanning, I checked reachability from the scanner’s own Docker network so the rescan would really test the new rules.
The sanitized configs are in my repo: github.com/oemarino/homelab.
What I learned
- “0 Critical/High” isn’t the finish line. The Medium and Low items (cleartext logins, weak crypto, info leaks) are exactly what an attacker chains together.
- When you can’t fix it, contain it. Limiting a weak service to a VPN-only path is a legitimate control, as long as you write down the residual risk.
- Where a firewall rule lives matters. Putting the rules in
before.rulesmeans a carelessufw allowlater can’t quietly undo them. - Verify with evidence. Every fix was confirmed by a rescan or a direct test, and the false positive was proven, not assumed.