Fail2ban Router Whitelist + NAT Hairpin Gotcha
Why: Damien got "Connection refused" SSHing to skyhouse.dev:2222 from his own home network, and separately noticed fail2ban had banned 192.168.1.1 (his own router) and worried it meant a compromised LAN device.
Two separate findings came out of digging into this, both worth remembering.
1. The GL.iNet router doesn't support NAT hairpin/loopback
When Damien is on the home LAN and connects to skyhouse.dev, DNS correctly resolves to the public IP (Cloudflare-proxied-off, origin A record, confirmed matching curl ifconfig.me from the server). But the router refuses to route a request for its own WAN IP back into the LAN — no hairpin support. Testing echo > /dev/tcp/<public-ip>/2222 directly from the server reproduces the same immediate "Connection refused," confirming it's a router-level limitation, not an sshd/ufw/port-forward problem. sshd, ufw (2222/tcp allowed), and the forward itself are all fine — verified via ss -tlnp, ufw status, and a successful connect to the LAN IP.
Workaround: when home, connect via the LAN IP instead of the hostname:
ssh -p 2222 plex@192.168.1.136
Real fix would be either a hairpin/loopback NAT toggle in the GL.iNet firmware (if v4.x exposes one — not yet checked) or a per-client hosts entry / local split-horizon DNS on Damien's own devices. Server-side /etc/hosts already does split-horizon, but that only helps processes running on the server itself, not his laptop/phone.
2. The router banned itself — self-inflicted, not malware
fail2ban's sshd jail had 192.168.1.1 banned after 3 failed logins as user damien (not the real server account, plex) at 10:52:26 that morning. Confirmed 192.168.1.1 is genuinely the router (matches default gateway + MAC via ip neigh), not a spoofed address, and it hairpins Damien's own SSH traffic when he connects to the hostname from inside the house (see above). Initially this looked like it might be a NAT/conntrack artifact, coincidentally timed with a stream of unrelated internet-wide SSH scanners hitting port 2222 in the same window — but the real explanation turned out to be simpler and traced back to Damien directly: a Claude Code dev session on his local machine had attempted to push/deploy to the server and used the local machine's username (damien) for the SSH connection instead of checking the docs for the actual server account (plex). Three failed password attempts, hairpinned through the router, tripped the 3-strike ban.
Since 192.168.1.1 is also the source address for all of Damien's own legitimate hairpin-path pubkey logins (confirmed many clean Accepted publickey for plex from 192.168.1.1 entries from prior sessions), letting it get caught in fail2ban's net was actively harmful, not just noise.
Lesson for future AI dev sessions: never assume the local $USER is the remote account when SSHing/deploying to skyhouse.dev — the server account is plex, documented in /home/plex/docs/server-context.md and the project memory. Check docs before constructing remote commands.
- Action: unbanned
192.168.1.1immediately (fail2ban-client set sshd unbanip 192.168.1.1). - Action: added a permanent
ignoreipexception in/etc/fail2ban/jail.local[DEFAULT]so this can't recur:ignoreip = 127.0.0.1/8 ::1 192.168.1.1. - Restarted fail2ban to apply; the 7 other currently-banned IPs (all genuine external scanners) persisted across the restart as expected.
Net effect: home-LAN SSH now has a documented workaround (use the LAN IP), and the router can no longer accidentally lock itself — and Damien's own hairpin traffic — out of sshd.
← Back to Admin Hub