SSH 报 connect to host port 22: Connection refused:从 Mac 连出去,还是连进这台 Mac
Frank Zhang
- 11 分钟阅读 - 5368 字TL;DR
先分清这句报错是从 Mac 连出去,还是连进这台 Mac(含 localhost)。只读命令看实际主机、端口和有没有进程在听;改远程登录或重启 sshd 放在核对之后。
终端里如果是下面这一句,TCP 连接没有建成。账号、密钥、密码都还没开始。
ssh: connect to host <名字> port 22: Connection refused
<名字> 是你在命令里写的那台主机,或者 ~/.ssh/config 里 Host 匹配到之后实际要用的名字。先看冒号后面的三个词,再决定要查 Mac 自己的“远程登录”,还是查对面那台机器。下面这张表只给第一条只读命令。写这篇时没有在 Mac 或 VPS 上重跑这些命令;旗标含义来自手册。你机器上的 man 和这里不一致时,以你的手册为准。
| 冒号后面的原文 | 更可能是什么 | 第一条只读命令 |
|---|---|---|
Connection refused | 这一次 connect() 落到了 ECONNREFUSED。Linux 的 connect(2) 写的是:流式套接字发现远端地址上没有人在听。sshd 没在这个地址的这个端口上听,或听在别的端口、别的地址上,都会是这一句。主动拒绝(对 SYN 回 RST)的防火墙,在客户端上也是同一个 errno。这后半句是一般判断,那本手册的原句只写了“没有人在听”。 | ssh -v user@host |
Operation timed out(用这句对照 Mac 客户端);Linux 上 ETIMEDOUT 的 strerror 是 Connection timed out | 等不到应答。Linux connect(2) 对 ETIMEDOUT 的说明是连接尝试超时。错的地址、丢包的防火墙、云安全组的默认丢弃、Mac 防火墙的隐形模式(对探测不做应答),都落在这一类。隐形模式的依据见下面防火墙一节。 | ssh -v user@host |
No route to host,或 Network is unreachable | 另一句报错。Network is unreachable 对得上 connect(2) 里的 ENETUNREACH。No route to host 是 EHOSTUNREACH 的 strerror;这篇引用的那版 connect(2) 错误列表里没有单独写它。以你终端里的原文为准。 | ssh -v user@host |
OpenSSH 客户端把这句拼出来的方式,以这篇写作时读到的 sshconnect.c 为准:全部地址都失败之后,打印 ssh: connect to host %s port %s: %s,第三段是 strerror(errno)。写这篇时在本机 glibc 上核对过:ECONNREFUSED 是 Connection refused,ETIMEDOUT 是 Connection timed out,EHOSTUNREACH 是 No route to host,ENETUNREACH 是 Network is unreachable。这是这一份 C 库的文案,不是在 Mac 上跑出来的。Mac 上超时常对上的是 Operation timed out;这篇没有打开 Darwin 的 errno 头文件,你屏幕上的词优先。
-v 还会为每个试过的地址打一行 Connecting to <主机> [<IP>] port <端口>。先读那一行,再读最后的报错。报错是最后一次失败留下的 errno。
先分清你站在哪一侧
两件事会打出同一句 port 22: Connection refused。
从这台 Mac 连出去:目标是 VPS、另一台 Linux,或别的机器。这台 Mac 的“远程登录”开没开,不决定对面的 22 端口听不听。要看的是客户端真正连去的主机和端口,然后到对面的控制台上看有没有人在听。
连进这台 Mac:别人连你的 Mac,或者你在这台 Mac 上执行 ssh localhost。Apple 的远程登录说明写的是:打开 Remote Login,才能用 SSH 或 SFTP 从另一台电脑访问这台 Mac。开关关着时,本机 22 上没有 sshd 在听,ssh localhost 就会是 Connection refused。
本站另有一篇是连自己 Mac 的实录,里面还有 cpolar 隧道和端口变化:一次ssh排错经历。那次已经做过的启动命令留在那篇。这篇只排动手之前的只读顺序。
客户端上的只读命令
这三条不改 sshd,不改防火墙,也不改 known_hosts。
1. ssh -v:它到底在连哪
ssh(1) 写明 -v 会打印调试信息,用来查连接、认证和配置;最多可以叠到三个 -v。第一层就够看出主机、IP 和端口。
ssh -v user@host
在 Connecting to 那一行核对三件事:主机名是不是你要的那台,方括号里的 IP 是不是它,端口是不是 22。端口已经不是 22 的时候,后面的 nc 也要改成那个端口。
2. ssh -G:配置把名字改写成了什么
同一本手册写明 -G 会在展开 Host 和 Match 之后把配置打出来,然后退出。它不发起连接。
ssh_config(5) 里,Host 决定后面的设置套到哪些名字上;Hostname 是真正要登录的主机,可以是别名对应的真名或 IP;Port 默认 22;User 是登录用户。ProxyCommand 或 ProxyJump 有值时,拒绝可能出在跳板这一段。
ssh -G host | grep -iE '^(hostname|port|user|proxycommand|proxyjump) '
grep 只是滤这几行。什么都没滤出来,就直接看 ssh -G host 的全文,找这几个关键字。关键字在手册里不区分大小写。
3. nc -vz:只问这个端口接不接 TCP
Mac 上的 nc(1)(这份是手册转载,不是 developer.apple.com 上的一页)写明:-z 只探测有没有守护进程在听,不往里送数据;-v 输出更详细。手册自己的例子是 nc -z host 20-30,成功时会有 Connection to … port [tcp/ssh] succeeded!。
nc -vz host 22
把 host 换成上一节看到的 IP 或主机名。自定义端口就换成那个端口。本机再加两条,把 IPv4 和 IPv6 分开。同一本手册有 -4 和 -6:
nc -4 -vz 127.0.0.1 22
nc -6 -vz ::1 22
-4 通、-6 拒绝,或反过来,就和 ssh -v 里每一行 Connecting to 对一下:被拒绝的是哪一个地址族。这篇不把“改 AddressFamily”写成修复。那是客户端配置,会改掉以后连哪些地址。
nc -z 如果报非法选项,说明这台机器上的 nc 不是这份手册里的实现。停在 ssh -v 即可。为了排查去另装一个 netcat,不在这篇的步骤里。
连进这台 Mac:只读
人在机器跟前,或已经有别的办法看到这台 Mac 的屏幕时,再查下面两项。查的是“远程登录开没开”和“22 上有没有监听”。打开开关是后一节的事。
远程登录开没开
systemsetup(8) 的转载页写明:-getremotelogin 显示远程登录(SSH)是开还是关。Apple 的 Remote Desktop 说明 要求在 systemsetup 前面加 sudo。
sudo systemsetup -getremotelogin
这条只查询。手册把“需要完整磁盘访问权限”写在 -setremotelogin 上,没有写在 -getremotelogin 上。
图形界面的路径以 Apple 现行用户指南为准:苹果菜单 → 系统设置 → 边栏「通用」→「共享」(可能要往下滚),点 Remote Login 旁边的按钮,看 Remote Login 是开还是关。指南写明,开着的时候,指示下面会给出一条 SSH 命令。同一页还有一项「Allow full disk access for remote users」(允许远程用户完全访问磁盘)。那是远程用户的权限,要单独打开才生效。它不负责把 22 端口听起来,也不是下面那条命令所要求的“终端的完整磁盘访问权限”。
谁在听 22
sudo lsof -nP -iTCP:22 -sTCP:LISTEN
这条只列监听中的套接字。-n、-P 让地址和端口保持数字,-iTCP:22 只看 TCP 22,-sTCP:LISTEN 只留 LISTEN。旗标以你 Mac 上的 man lsof 为准。没有输出,就是现在没有进程在这个端口上听。这和 Connection refused 对得上。
防火墙:只查询
Mac 防火墙在另一处。Apple 的防火墙设置路径是:系统设置 → 边栏「网络」→「防火墙」。同一页对「Enable stealth mode」的说明是:避免 Mac 应答那些用来发现它是否存在的探测;已授权 App 的请求仍会应答,ICMP(ping)这类未授权请求则没有响应。
socketfilterfw(8) 的转载页有两个查询开关:
/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate
/usr/libexec/ApplicationFirewall/socketfilterfw --getstealthmode
现行用户指南举的“没有响应”例子是 ping。指南没有写“对关闭的 TCP 端口不回 RST”。因此:隐形模式开着时,更符合指南原文的现象是探测没有应答,客户端表现为超时那一句,而不是 Connection refused。把“隐形模式会造成 TCP 超时”说死,超出了这页原文,这里只把它当作一般判断。查询如果提示权限不够,再考虑加 sudo。这里的 sudo 仍然只是读状态。
从 Mac 连出去:到服务器上看,仍然先只读
对面那台机器的这些命令,从厂商提供的控制台、VNC 或你已经握着的另一条会话里跑。唯一的一条 SSH 已经连不上时,不要在那条会话里重启 sshd。
有没有人在听
ss(8):-t 只看 TCP,-l 只看监听(默认不显示监听),-n 不做服务名解析,-p 显示占用套接字的进程。看进程名通常要 root。
sudo ss -tlnp | grep -E 'ssh|:22'
有监听行,就看地址和端口。127.0.0.1:22 这种只绑在回环上的地址,从别的机器连过去,仍然会在 22 上被拒绝。没有监听行,就是这个端口上现在没有人接。
有效配置可以只打印、不启动服务。sshd(8) 的 -T 是扩展测试:检查配置是否有效,把生效配置打到标准输出,然后退出。它包含 -t 的额外检查。默认配置文件是 /etc/ssh/sshd_config。
sudo sshd -T | grep -E '^(port|listenaddress) '
port 或 listenaddress 和你客户端用的不一致,就用 ssh -p <端口> 连它正在听的那个,或先弄清它为什么只绑在某个地址上。先改客户端去连已经在听的端口,而不是先改服务器把端口改回去。
服务名:先问这台机器
Debian 的 sshd(8) 写它通常由 /etc/init.d/ssh 在开机时启动。Ubuntu 从 22.10 起默认用 systemd 套接字激活,单元名是 ssh.socket 和 ssh.service,依据是 Ubuntu 的这篇说明(Steve Langasek,2022-08-24)。那篇写明:在连接进来之前,sshd 进程可以不在跑。所以 ssh.service 显示 inactive,本身不能说明 22 没人听;要同时看 ssh.socket 和上面的 ss。
同一篇还写了端口可能不在你刚改的 sshd_config 里生效:22.10、23.04、23.10 会把已有的 Port / ListenAddress 迁到 /etc/systemd/system/ssh.socket.d/addresses.conf(多个 ListenAddress 时不迁移)。24.04 改为用 systemd generator 从 sshd 配置里取端口和地址。原文写的文件名是 sshd.conf;管理员实际编辑的文件仍是 /etc/ssh/sshd_config,以及这台机器上的 sshd_config.d。这里把“改了 Port 却仍拒绝 22”标成一般判断:先用 sshd -T 和 ss 看生效值,再读那篇说明。这篇不提供改监听端口或关掉套接字激活的步骤。那篇讨论里,人们会因此把自己锁在外面。
Red Hat 的远程登录说明使用的服务名是 sshd,并写明 SSH 默认端口是 22。
所以先按发行版问名字,问的是状态:
systemctl status ssh.socket
systemctl status ssh
systemctl status sshd
不存在的单元会直接告诉你。三条都是查询。
防火墙和 fail2ban:只查询
本机防火墙和云上的安全组是两层。一般判断:丢弃数据包时,客户端等到的是超时那一句;规则明确拒绝、回了 RST 时,客户端看到的是 Connection refused。你用的那一家云把默认拒绝做成丢弃还是拒绝,以那家的文档为准。这篇没有逐家核对控制台。安全组要改的话,在厂商控制台里对具体来源地址改,步骤不在这里。把 22 对全世界打开,也不在这里。
机器上已有的防火墙,只看状态:
sudo ufw status
sudo firewall-cmd --list-all
sudo nft list ruleset
只跑这台机器上实际有的那一条。命令不存在就换下一条,不要为了排查现装一套防火墙。nft list ruleset 可能很长,它仍然只是读规则。
fail2ban 的封禁动作由配置决定:拒绝和丢弃会分别变成上面两种报错。只列出监狱,不解除封禁:
sudo fail2ban-client status
命令不存在,就是当前路径上没有 fail2ban。监狱的名字以这条的输出为准。
只读对上了,才改状态
下面两步都会改变能不能从网络连进来。只读结果已经说明开关是关的、或配置确实要改,并且你接受它的影响,再做。
在 Mac 上打开远程登录
Apple 的指南就是上一节那条路径:系统设置 → 通用 → 共享 → Remote Login 旁边的按钮 → 打开 Remote Login。同一页写明:允许远程登录会让 Mac 没那么安全,并指向把 Mac 设得更安全的说明。这是一个网络监听,不只给 localhost 用。你只是要从这台 Mac 连出去时,不需要为了排查把它打开。
已经决定要开,也可以用命令。手册写 -setremotelogin on|off 会设置远程登录,并且需要完整磁盘访问权限。Apple 的已归档说明(页面写明 Catalina 10.15 及以后,Apple 不再更新)要求先给父进程完整磁盘访问权限。它写的点击路径仍是“系统偏好设置 → 安全性与隐私 → 隐私 → 完整磁盘访问权限”。现行系统设置里的文字对不上时,用设置里的搜索找“完整磁盘访问权限”,把终端加进去。这篇不另编一套点击路径。
sudo systemsetup -setremotelogin on
父进程没有那项权限时,命令会失败。失败就是权限,不是“22 端口的结论”。这项权限给的是终端,和共享面板里“允许远程用户完全访问磁盘”是两个开关。后一个开关会让远程用户得到整块磁盘的访问,修复 Connection refused 用不到它。
旧文章里有 launchctl load -w /System/Library/LaunchDaemons/ssh.plist。这篇把开关放在系统设置的 Remote Login 上。本站那次实录里用过 sudo launchctl kickstart -k system/com.openssh.sshd,那是那次已经发生的启动方式,记录在一次ssh排错经历。
打开之后,回到上面的 lsof 和 nc -vz 127.0.0.1 22,确认 22 上已经有人听。
在服务器上改配置之前
sshd(8) 对 -t 的说明是:只检查配置文件是否有效,以及密钥是否正常。它用于在配置项可能变化时仍能可靠地更新 sshd。它不启动监听。
sudo sshd -t
退出码是 0,再考虑重载或重启。重启会打断正在用的会话。留着厂商控制台,或留着一条已经登录、并且你不准备把它关掉的会话。发行版上的单元名用上一节 systemctl status 已经确认的那个:Debian/Ubuntu 上常见的是 ssh,Red Hat 系列是 sshd。名字没确认之前,不重启。
改完再跑一次 ss -tlnp 和从客户端来的 nc -vz。监听地址和客户端实际连的地址要一致。
要不要把一台新机器的 22 端口露到公网,是部署前就要决定的事,写在部署一个 GitHub 项目前,最应该先确认的 10 件事。那篇不负责修这一句报错。
端口通了,下一句可能是 host key
frp、cpolar 或端口转发把主机或端口变了之后,TCP 可以已经成功,然后客户端改报 host key 对不上。那是另一句。Connection refused 还停在 TCP 时,不去删 known_hosts。
下一句如果是 Host key verification failed 或 REMOTE HOST IDENTIFICATION HAS CHANGED,用known_hosts 修复命令生成器 生成只删除这一台主机旧记录的命令,先核对指纹再删。那次隧道端口变化的经过在一次ssh排错经历。
这篇停在这里
这些情况会在 TCP 建成之后,或根本是另一条路径,这篇没有往下写:
Permission denied (publickey)或密码被拒绝。能看到认证失败,说明端口已经接上了。- 公司 VPN、HTTP 代理、设备管理配置文件。
ssh -G里如果已经有proxycommand或proxyjump,先把那一行读完。怎么配 VPN,不在这里。 - 只剩 IPv6、或只有 IPv6 路由的网络怎么设计。上面的
-4/-6只用来看这次拒绝落在哪一个地址族。 - Windows 上的 OpenSSH 客户端。命令和文案要换;本站那篇实录里也写了 Windows 需要换一部分命令。
- 搜索量。这篇没有另做外部数据。
其他阅读路线见自部署阅读路线。英文稿是同一题的另一份写法:ssh: connect to host port 22: Connection refused, from a Mac or into a Mac。
这篇如果有用,欢迎自愿赞助,用来继续更新博客和自部署笔记。这不是慈善捐赠。