一次 WSL SSH 导致远程 Linux TUI 显示异常的排查记录
WSL 里 SSH 连服务器后 top 黑屏 / 卡死?真正的元凶不是服务器,也不是 ncurses
一次把「服务器是不是坏了」的怀疑,一路排查到「其实是 WSL 原生
ssh的 TUI 显示链路」的完整记录。
两个要纠正的常见误解:① 重装openssh-client/ncurses修不了这个问题;②wsl --shutdown只能碰运气、不保证稳。真正一劳永逸的做法是改用 Windows 的ssh.exe。
TL;DR(太长不看版)
- 现象:在 WSL 里用
ssh连远程 Linux 服务器,登录、普通命令都正常,但一执行top(或任何全屏 TUI:htop、vim、方向键历史搜索等)就黑屏 / 卡死。 - 真正根因:不是服务器、不是
TERM、不是终端尺寸、不是 terminfo、不是本地软件包——而是 WSL 原生/usr/bin/ssh这条「交互式全屏 TUI」显示链路本身会偶发抽风(intermittent),跟其它一切都无关。 - 一劳永逸的解法:把连接命令换成 Windows 自带的
ssh.exe(C:\Windows\System32\OpenSSH\ssh.exe)。它是原生 Windows 程序、走 ConPTY,完全绕开 WSL 这段桥接,对该问题天生免疫。(记得给它单独配免密,见第六节。) - 临时手段(不保证):
wsl --shutdown重启 WSL 有时能恢复,但实测重开后仍可能再次卡死,只能当作应急,别指望它稳。 - 别再做的事:重装
openssh-client/ncurses-*/util-linux/procps。dpkg -V校验证明这些包完好无损,重装纯属无效操作。
一、问题现象
连接服务器本身没问题,登录横幅、ls、cat 等都正常。但只要执行需要「全屏刷新」的交互式命令,就出问题:
1. top 执行后终端变黑 / 空白,像整个会话卡死。
2. 按方向键 ↑ 调历史命令时黑屏或界面错乱。
3. vim / htop / less 等全屏程序同样异常。
4. 但 ps aux | head、top -b -n 1 > file 这类"非全屏 / 写文件"的用法其实是正常的。
一开始很容易误判成「服务器卡死了」,甚至开始考虑重装系统。先别急,服务器大概率是好的。
二、我走过的弯路(误判合集)
这些都不是根因,但排查过程中很容易被带偏,记录下来供避坑:
1)tcpdump 一直不返回 ≠ 卡死
tcpdump -i any host <TARGET_IP> and port 443 -nn
tcpdump 默认持续抓包、不会自己退出,这是正常行为。以后加限制即可:
timeout 30s tcpdump -i any 'host <TARGET_IP> and port 443' -nn # 限时
tcpdump -i any 'host <TARGET_IP> and port 443' -nn -c 20 # 限包数
2)Atuin(命令历史 TUI)只是干扰项
服务器上装了 atuin,它会接管方向键、打开历史搜索 TUI。终端渲染异常时它自然也黑屏。但卸载 Atuin 后 top 依旧异常,说明它不是根因。
3)重装服务器的 procps —— 没用
top / ps 来自 procps,于是尝试重装:
apt install --reinstall -y procps
结果卡在 Scanning processes...(其实是 apt 后置钩子 needrestart 在扫描进程),而 procps、top -v、ps --version 检查全部正常。服务器的 procps 没坏。
4)重装本地 openssh-client / ncurses —— 这就是那个「看似管用、实则无效」的操作
曾经以为「重装本地 openssh-client、ncurses、util-linux 就好了」。这是个误解。 后文会用 dpkg -V 证明这些包根本没坏;之所以有时「看起来修好了」,只是那一串 apt / 重启操作顺带重启了 WSL——而重启 WSL 本身也只是「碰运气」,并不稳定(见第五节)。
三、系统性排查:把嫌疑一个个排除
排查的核心思路:先证明「命令本身和服务器是好的」,再把问题逼到「显示链路」上。
1)服务器资源正常
uptime; free -h; df -h
CPU 负载低、内存充足、磁盘富余、无 swap 压力 —— 排除「资源耗尽导致卡死」。
2)top 是真卡死,还是只是「显示」异常?——写文件法
把 top 的输出重定向到文件,绕开终端显示:
timeout -k 2s 8s top -b -n 1 >/tmp/top.out 2>/tmp/top.err
echo "exit=$?"; wc -l /tmp/top.out
结果:exit=0、top.err 为空、top.out 有上百行内容。
结论:top 本身执行成功、有正常输出。问题在「终端显示层」,不是 top 或服务器卡死。
3)换 SSH 客户端对比 —— 最关键的一步
同一台服务器,用三种方式连上去再跑 top:
| 连接方式 | top 是否正常 |
|---|---|
Windows PowerShell 里 ssh(Windows OpenSSH) |
✅ 正常 |
WSL 里调用 Windows 的 ssh.exe |
✅ 正常 |
WSL 里的原生 /usr/bin/ssh |
❌ 黑屏 / 卡死 |
这一步把问题彻底定位到了「WSL 原生 ssh 的显示链路」,而不是服务器,也不是终端软件本身(因为同一个终端里,ssh.exe 完全正常)。
4)本地软件包完整性 —— 用 dpkg -V 打假
很多人第一反应是「本地 ncurses / ssh 坏了,重装」。先用 dpkg -V 校验(有输出才代表文件被改动 / 损坏):
dpkg -V openssh-client ncurses-base ncurses-bin ncurses-term util-linux
# 空输出 = 全部完好
dpkg-query -W -f='${Package} ${Version} ${Status}\n' \
openssh-client ncurses-base ncurses-bin ncurses-term util-linux
结果:零输出、全部 install ok installed。包完好无损。
顺带一提:远程
top用的是服务器上的 terminfo,不是你 WSL 本地的 terminfo。所以本地重装ncurses-term在原理上就不可能影响远程top的渲染。「重装本地包能修」这个结论从逻辑上就站不住。
5)TERM 与终端尺寸是否正确传到了服务器?
全屏程序依赖两样东西:正确的 TERM、正确的窗口尺寸(rows × cols)。分别验证:
# 本地
echo "$TERM" # xterm-256color
stty size # 例如 14 48
# 连上服务器后
echo "$TERM" # xterm-256color —— 正确传过去了
stty size # 14 48 —— 尺寸也正确传过去了
infocmp xterm-256color >/dev/null && echo "terminfo OK" # 服务器有对应 terminfo
结论:TERM、尺寸、terminfo 全部正确。既不是「尺寸变成 0×0」,也不是「terminfo 缺失」。
到这里,所有常见嫌疑(服务器 / 命令 / TERM / 尺寸 / terminfo / 本地包)全部排除。
四、真正的根因
把所有「没坏」的都划掉之后,问题被锁死在一个地方:WSL 原生 /usr/bin/ssh 把远程交互式全屏 TUI 的输出渲染回本地这条链路,本身会偶发抽风。
- 它与服务器、
TERM、尺寸、terminfo、本地软件包统统无关(上面逐一验证过都正常)。 - 它是偶发、有状态的:同样的环境、同样的命令,有时正常、有时黑屏,重启 WSL 后也可能再次复现。
- 关键对照:同一个终端窗口里,用 Windows
ssh.exe连同一台服务器跑top永远正常;只有走 WSL 原生/usr/bin/ssh才会翻车。因为ssh.exe是原生 Windows 程序、直接走 ConPTY,绕开了 WSL 这段有问题的桥接。
换句话说:这是 WSL 自身在「Linux 进程的伪终端 ↔ Windows 控制台」桥接上的一个不稳定点,恰好被全屏 TUI(top 等)触发。 与其去猜它什么时候犯病,不如直接绕开它。
五、解法
方案 A:改用 Windows 的 ssh.exe(★ 推荐,一劳永逸)
直接让连接命令走 Windows OpenSSH,从根上绕开 WSL 那段有问题的链路:
alias myserver='/mnt/c/Windows/System32/OpenSSH/ssh.exe -tt root@<SERVER_IP>'
之后 myserver → top 稳定正常,不再受那个偶发问题影响。注意:ssh.exe 用的是 Windows 侧的密钥 / 配置,免密要单独配(见第六节)。 这也是本次最终采用、验证有效的方案。
方案 B:wsl --shutdown 重启 WSL(临时应急,不保证)
在 PowerShell / CMD 里执行,再重新打开 WSL:
wsl --shutdown
有时能让原生 ssh 的 top 暂时恢复,但实测重开后仍可能再次卡死——它只是重置了一下状态、碰运气,不是可靠解法。想保留原生 ssh 时可以一试,但别指望它稳。
附带小建议:别用又窄又矮的内嵌终端面板(比如 14×48 那种),全屏程序空间太小;用 Windows Terminal 开大窗口体验更好。
方案 C:临时救急(终端已经花屏时)
先把当前终端硬复位,恢复可用:
stty sane 2>/dev/null
printf '\033[0m\033[?25h\033[?1049l\033c' # 重置属性/显示光标/退出备用屏/终端 reset
export TERM=xterm-256color
clear
(这只是临时恢复显示,治标不治本,根治靠方案 A。)
六、免密登录:一个「两套钥匙」的大坑
排查中顺带修了免密登录,这里有一个极易踩的坑:
WSL 原生
ssh和 Windowsssh.exe用的是两套完全不同的密钥 / 配置。
| 命令 | 用哪套密钥 / 配置 |
|---|---|
WSL 原生 ssh root@... |
WSL 侧:~/.ssh/(即 \\wsl$\...\home\<user>\.ssh\) |
Windows ssh.exe root@... |
Windows 侧:C:\Users\<你>\.ssh\ |
所以既然最终用 ssh.exe(方案 A),就必须给 Windows 侧那把钥匙配免密——「在 WSL 里配的免密」对 ssh.exe 无效。典型现象:WSL 原生 ssh 免密成功,但 myserver(=ssh.exe)仍要密码,正是因为 Windows 侧的 id_rsa 没授权到服务器。
第 1 步:让 WSL 原生 ssh 先能免密(用它当「跳板」装钥匙)
确认本地已有密钥(默认名 ~/.ssh/id_rsa),写一条 host 配置强制使用它:
# 单行写入配置(单行不易被终端粘贴截断)
printf 'Host <SERVER_IP>\n HostName <SERVER_IP>\n User root\n IdentityFile ~/.ssh/id_rsa\n IdentitiesOnly yes\n' > ~/.ssh/config
chmod 700 ~/.ssh && chmod 600 ~/.ssh/config ~/.ssh/id_rsa
# 测试:应输出 免密成功_root,不再要密码
ssh root@<SERVER_IP> 'echo 免密成功_$(whoami)'
IdentitiesOnly yes 很关键:让 ssh 只用指定的这把钥匙,避免 agent 里一堆无关 key 被轮流试到「次数用尽」、结果退回密码。
(若公钥还没装到服务器,先 ssh-copy-id -i ~/.ssh/id_rsa.pub root@<SERVER_IP> 装一次,会让你输一次密码。)
第 2 步:把 Windows 侧的公钥也装到服务器
借上一步已经免密的 WSL ssh,把 Windows 的公钥追加到服务器(无需再输密码):
cat /mnt/c/Users/<你>/.ssh/id_rsa.pub | ssh root@<SERVER_IP> \
'umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys; chmod 600 ~/.ssh/authorized_keys'
# 验证 ssh.exe 免密(应输出 root)
/mnt/c/Windows/System32/OpenSSH/ssh.exe -o BatchMode=yes root@<SERVER_IP> whoami
第 3 步:把 myserver 指向 ssh.exe
sed -i "s#alias myserver=.*#alias myserver='/mnt/c/Windows/System32/OpenSSH/ssh.exe -tt root@<SERVER_IP>'#" ~/.bashrc && source ~/.bashrc
myserver # 免密进入 + top 正常
小技巧:多行 heredoc 在某些终端里粘贴容易被截断,导致配置只写了一半。写配置优先用单行
printf,或干脆用编辑器保存。
七、经验总结 / 排查清单
遇到「top 黑屏、tcpdump 不返回」这类现象,别急着判定「服务器坏了」。按下面顺序排查:
1. 服务器资源是否异常? uptime / free -h / df -h
2. 命令是否真的卡死? timeout ... top -b -n 1 >file,看有没有输出
3. 非交互 / 写文件是否正常? 正常 → 问题在"显示层"
4. 换 SSH 客户端对比 PowerShell ssh / ssh.exe / WSL 原生 ssh
5. 本地包是否真损坏? dpkg -V(空输出=没坏,别瞎重装)
6. TERM / stty size 是否正确? 确认没变 0×0、terminfo 存在
7. 定位到 WSL 原生 ssh 链路 → 直接改用 Windows ssh.exe(+ 给它配免密)
8. 想临时救一下 wsl --shutdown(碰运气,不保证)
9. 最后才考虑重装系统(几乎用不到)
一句话本质:这不是「Linux 服务器坏了」,而是一次典型的 WSL 本地终端链路问题:
Windows 终端 → WSL → WSL 原生 /usr/bin/ssh → 远程 Ubuntu → top / TUI
↑
这一段偶发抽风,导致远程全屏 TUI 显示异常
可靠解法是绕开这段——改用 Windows ssh.exe(并给它单独配好免密)。 wsl --shutdown 只能临时碰运气;而重装 openssh / ncurses / procps 是无效操作,别再花时间在上面了。
更多推荐



所有评论(0)