你能 SSH 出去,却 SSH 不回来:一次被 Fail2Ban 误伤的反向访问排障实录
先说结论
这次问题的表象很简单:本机可以主动 SSH 到远端内网机器,但远端机器反过来 SSH 回本机时,连接直接被拒绝。
sshd明明在监听,路由也没断,最后却卡在“反向访问”这一步。真正的根因不是
sshd挂了,而是Fail2Ban把承载反向连接的 VPN 网关地址 封掉了。对本机来说,来自远端的连接并不是以“远端机器本身”的地址出现,而是以网关地址出现。于是,错误的封禁对象把整条回程链路一并打断了。
1. 问题背景:单向能通,反向却断
这类故障非常容易让人陷入第一层直觉:既然我能从本机连到远端,那网络大体应该没问题;既然远端反过来连不上本机,那大概率是本机 sshd 出故障了。
但真实情况往往更绕。
我这次遇到的场景是:
- 本机可以正常
ssh到远端内网机器。 - 远端内网机器也确实能“看到”本机。
- 但是远端机器一旦尝试反向
ssh root@本机,就直接失败。 - 本机
sshd服务和 22 端口监听都正常。 - 日志里没有明显的服务崩溃。
如果只看结果,很像是“本机拒绝了远端”。但如果看链路,就会发现真正需要验证的是:到底是谁在对本机发起连接,源地址又是谁。
2. 问题表现:Connection refused 不是“sshd 挂了”
排查初期,我先从最基础的地方开始:
ss -lntp | grep ':22'
systemctl status ssh
结果都正常。sshd 在监听,systemd 服务也在运行。于是我又从远端机器做了一个最小化连接测试,得到的不是超时,而是 Connection refused。
这个细节很重要。
一般来说:
timeout更像是路径中间断了,或者包被静默丢弃。refused则说明有一端明确回了拒绝。
这就提示我:问题很可能不是“完全到不了”,而是“到了,但被某个规则主动拒绝了”。
3. 第一层排查:本机 sshd 没问题
为了不被表象带偏,我继续做了三件事。
先看监听
ss -lntp | grep ':22'
确认 sshd 在 0.0.0.0:22 上监听,没有绑定错地址,也没有只监听回环。
再看日志
journalctl -u ssh -n 100 --no-pager
日志里能看到大量正常的连接尝试、认证失败和成功记录,说明 SSH 服务链路本身是活的。
最后看防火墙
这一步才是关键。
我检查了 nftables 规则,发现有一张 fail2ban 创建的表,里面 sshd 的封禁集合已经包含了一个我非常熟悉的地址:VPN 网关地址。
4. 问题分析:为什么“远端机器”会变成“网关地址”
这一步是整个问题的核心。
很多人会默认“远端机器连回来,源地址就应该是远端机器自己的地址”。但在内网、VPN、隧道、网关转发这些场景下,事情不一定这么简单。
对本机来说,实际看到的连接源头并不是远端机器的真实地址,而是:
- VPN 出口地址。
- 隧道终端地址。
- 或者某种会话转发后的中间地址。
换句话说,远端机器是通过一个共享的网关路径回来的。当本机收到包时,它只看得见网关,不一定看得见最终客户端。
这就解释了为什么 Fail2Ban 会误伤:
- 之前某些 SSH 失败行为积累到了阈值。
Fail2Ban把“网关地址”判定成了攻击源。- 一旦网关被封,所有经由该网关回来的连接都会被一起拦掉。
所以表面上看是“远端不能 SSH 回本机”,实际上是“回程链路的中间地址被封了”。
5. 根因:Fail2Ban 误封了 VPN 网关
最终我在 fail2ban-client 里确认了这一点:
fail2ban-client status sshd
输出里,Banned IP list 就是那个网关地址。
这意味着:
sshd没坏。- 路由没断。
- 反向访问的客户端也不是“凭空消失”。
- 真正断掉的是
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 会误伤整条路径。
这就是为什么我这次没有直接“改配置碰碰运气”,而是先做了:
ss看监听。tcpdump看包从哪里来。fail2ban-client status看谁被封。- 最后才改
ignoreip。
8. FAQ
Q1: 为什么显示的是 Connection refused,不是超时?
因为连接并不是完全被丢弃,而是被防火墙或规则链显式拒绝了。拒绝比静默丢包更容易在客户端侧表现成 refused。
Q2: 既然是 Fail2Ban 误伤,直接关掉它不就行了?
不建议。
Fail2Ban 仍然有价值,只是需要把“可信中间层地址”从 SSH 规则里排除。直接关闭相当于为了修一个误封,把整套防爆破能力一并拿掉。
Q3: 怎么避免以后再出现同类问题?
最有效的办法是:
- 先弄清楚你的远端流量到底经过了谁。
- 只对白名单中真正可信的入口做排除。
- 定期检查
fail2ban-client status sshd,确认被封的到底是谁。 - 如果你依赖 VPN / 隧道 / 网关转发,务必把它们当成“链路节点”来管理,而不是把终端机和网关混成同一个地址。
Q4: 有没有更安全的做法?
有。
如果你的网络条件允许,可以把管理入口单独隔离到一个专门的管理网段,或者在 SSH 前面再加一层更明确的访问控制。这样即使 Fail2Ban 做了封禁,也不会误伤业务流量。
9. 参考资料
这次写作时我参考了几份官方资料:
10. 总结
这次问题表面上是“远端机器不能 SSH 回来”,真正的根因却是“回程路径上的 VPN 网关被 Fail2Ban 封了”。
这类故障最值得记住的不是某条命令,而是一个排障习惯:
当服务看起来正常、路由看起来正常、单向通信也正常时,别忘了检查中间层是否在替你改写源地址。
只要源地址变了,安全策略就可能把“合法访问”误判成“攻击流量”。
这次把 ignoreip 和 unbanip 处理对了,反向 SSH 就恢复了。更重要的是,我也把这条链路重新理解了一遍:不是所有看起来像“远端”的连接,最后都会以“远端”的身份出现在本机上。