中文 English

你能 SSH 出去,却 SSH 不回来:一次被 Fail2Ban 误伤的反向访问排障实录

发布时间: 2026-05-30
SSH Fail2Ban nftables VPN 故障排查 网络 安全 Linux

先说结论

这次问题的表象很简单:本机可以主动 SSH 到远端内网机器,但远端机器反过来 SSH 回本机时,连接直接被拒绝。sshd 明明在监听,路由也没断,最后却卡在“反向访问”这一步。

真正的根因不是 sshd 挂了,而是 Fail2Ban 把承载反向连接的 VPN 网关地址 封掉了。对本机来说,来自远端的连接并不是以“远端机器本身”的地址出现,而是以网关地址出现。于是,错误的封禁对象把整条回程链路一并打断了。

reverse ssh cover

1. 问题背景:单向能通,反向却断

这类故障非常容易让人陷入第一层直觉:既然我能从本机连到远端,那网络大体应该没问题;既然远端反过来连不上本机,那大概率是本机 sshd 出故障了。

但真实情况往往更绕。

我这次遇到的场景是:

  1. 本机可以正常 ssh 到远端内网机器。
  2. 远端内网机器也确实能“看到”本机。
  3. 但是远端机器一旦尝试反向 ssh root@本机,就直接失败。
  4. 本机 sshd 服务和 22 端口监听都正常。
  5. 日志里没有明显的服务崩溃。

如果只看结果,很像是“本机拒绝了远端”。但如果看链路,就会发现真正需要验证的是:到底是谁在对本机发起连接,源地址又是谁。

reverse ssh path map

2. 问题表现:Connection refused 不是“sshd 挂了”

排查初期,我先从最基础的地方开始:

ss -lntp | grep ':22'
systemctl status ssh

结果都正常。sshd 在监听,systemd 服务也在运行。于是我又从远端机器做了一个最小化连接测试,得到的不是超时,而是 Connection refused

这个细节很重要。

一般来说:

  1. timeout 更像是路径中间断了,或者包被静默丢弃。
  2. refused 则说明有一端明确回了拒绝。

这就提示我:问题很可能不是“完全到不了”,而是“到了,但被某个规则主动拒绝了”。

3. 第一层排查:本机 sshd 没问题

为了不被表象带偏,我继续做了三件事。

先看监听

ss -lntp | grep ':22'

确认 sshd0.0.0.0:22 上监听,没有绑定错地址,也没有只监听回环。

再看日志

journalctl -u ssh -n 100 --no-pager

日志里能看到大量正常的连接尝试、认证失败和成功记录,说明 SSH 服务链路本身是活的。

最后看防火墙

这一步才是关键。

我检查了 nftables 规则,发现有一张 fail2ban 创建的表,里面 sshd 的封禁集合已经包含了一个我非常熟悉的地址:VPN 网关地址

debug timeline

4. 问题分析:为什么“远端机器”会变成“网关地址”

这一步是整个问题的核心。

很多人会默认“远端机器连回来,源地址就应该是远端机器自己的地址”。但在内网、VPN、隧道、网关转发这些场景下,事情不一定这么简单。

对本机来说,实际看到的连接源头并不是远端机器的真实地址,而是:

  1. VPN 出口地址。
  2. 隧道终端地址。
  3. 或者某种会话转发后的中间地址。

换句话说,远端机器是通过一个共享的网关路径回来的。当本机收到包时,它只看得见网关,不一定看得见最终客户端。

这就解释了为什么 Fail2Ban 会误伤:

  1. 之前某些 SSH 失败行为积累到了阈值。
  2. Fail2Ban 把“网关地址”判定成了攻击源。
  3. 一旦网关被封,所有经由该网关回来的连接都会被一起拦掉。

所以表面上看是“远端不能 SSH 回本机”,实际上是“回程链路的中间地址被封了”。

5. 根因:Fail2Ban 误封了 VPN 网关

最终我在 fail2ban-client 里确认了这一点:

fail2ban-client status sshd

输出里,Banned IP list 就是那个网关地址。

这意味着:

  1. sshd 没坏。
  2. 路由没断。
  3. 反向访问的客户端也不是“凭空消失”。
  4. 真正断掉的是 Fail2Ban 触发的防火墙拒绝。

这类故障最容易误导人的地方在于:SSH 层和防火墙层都没有明显报错,只有客户端看到一个非常笼统的 Connection refused。如果没有抓包和状态检查,很容易把它归到“网络不稳定”或者“服务没起来”。

6. 如何修复:先解封,再白名单

修复策略分两步。

第一步:立刻解封当前网关

fail2ban-client set sshd unbanip <VPN_GATEWAY_IP>

这一步能让当前反向连接马上恢复。

第二步:把这个网关加入 ignoreip

我在 /etc/fail2ban/jail.local 里为 sshd 添加了白名单:

[sshd]
ignoreip = 127.0.0.1/8 ::1 <VPN_GATEWAY_IP>

这不是“关掉防护”,而是把明确可信的中间层地址从 SSH 爆破检测中排除出去。

如果你的环境里有多个稳定入口网关,也可以把它们做成一个受控的白名单;但不要为了图省事把整个大网段一股脑放进去。Fail2Ban 的价值就在于它能替你挡住明显不可信的来源,白名单应当尽量精确。

之后重载

fail2ban-client reload

然后再从远端测试,反向 SSH 就恢复了。

7. 为什么这个问题很隐蔽

它隐蔽在三个地方。

第一,单向访问是好的

因为本机能主动连出去,所以人很容易把“网络”判定为整体正常。

第二,服务状态也是好的

sshd 在监听,系统服务正常,很多人会继续去查认证配置,而忽略防火墙和封禁状态。

第三,封禁对象并不直观

如果你没有意识到“反向 SSH 的源地址是网关而不是终端”,那你就很难第一时间想到 Fail2Ban 会误伤整条路径。

这就是为什么我这次没有直接“改配置碰碰运气”,而是先做了:

  1. ss 看监听。
  2. tcpdump 看包从哪里来。
  3. fail2ban-client status 看谁被封。
  4. 最后才改 ignoreip

8. FAQ

Q1: 为什么显示的是 Connection refused,不是超时?

因为连接并不是完全被丢弃,而是被防火墙或规则链显式拒绝了。拒绝比静默丢包更容易在客户端侧表现成 refused

Q2: 既然是 Fail2Ban 误伤,直接关掉它不就行了?

不建议。

Fail2Ban 仍然有价值,只是需要把“可信中间层地址”从 SSH 规则里排除。直接关闭相当于为了修一个误封,把整套防爆破能力一并拿掉。

Q3: 怎么避免以后再出现同类问题?

最有效的办法是:

  1. 先弄清楚你的远端流量到底经过了谁。
  2. 只对白名单中真正可信的入口做排除。
  3. 定期检查 fail2ban-client status sshd,确认被封的到底是谁。
  4. 如果你依赖 VPN / 隧道 / 网关转发,务必把它们当成“链路节点”来管理,而不是把终端机和网关混成同一个地址。

Q4: 有没有更安全的做法?

有。

如果你的网络条件允许,可以把管理入口单独隔离到一个专门的管理网段,或者在 SSH 前面再加一层更明确的访问控制。这样即使 Fail2Ban 做了封禁,也不会误伤业务流量。

9. 参考资料

这次写作时我参考了几份官方资料:

  1. Fail2Ban documentation
  2. Fail2Ban project repository
  3. OpenSSH sshd_config manual
  4. nftables wiki

10. 总结

这次问题表面上是“远端机器不能 SSH 回来”,真正的根因却是“回程路径上的 VPN 网关被 Fail2Ban 封了”。

这类故障最值得记住的不是某条命令,而是一个排障习惯:

当服务看起来正常、路由看起来正常、单向通信也正常时,别忘了检查中间层是否在替你改写源地址。

只要源地址变了,安全策略就可能把“合法访问”误判成“攻击流量”。

这次把 ignoreipunbanip 处理对了,反向 SSH 就恢复了。更重要的是,我也把这条链路重新理解了一遍:不是所有看起来像“远端”的连接,最后都会以“远端”的身份出现在本机上。