这一篇讲什么
网络和 SSH 的问题有个共同点:你改的那个文件,往往不是真正生效的那个。 本篇在三台机器上逐个复现,贴原始输出。
| 机器 | 网络由谁管 | OpenSSH |
|---|---|---|
| CentOS 7.9(VMware) | network 脚本 + dhclient(这台 NetworkManager 是停着的) | 7.4p1 |
| Rocky 9.8(LXD 虚拟机) | NetworkManager | 9.9p1 |
| Ubuntu 24.04.5(LXD 虚拟机) | systemd-networkd + systemd-resolved(netplan 生成配置) | 9.6p1 |
先说一个会让很多老教程直接失效的事实 ------ 这些命令三台默认都没有:
scss
ifconfig (没有)
netstat (没有)
route (没有)
arp (没有)
ip /usr/sbin/ip
ss /usr/sbin/ss
dig (没有)
nslookup (没有)
host (没有)
telnet (没有)
nmap (没有)
traceroute (没有)
tcpdump (没有)
(Rocky 9 的输出;CentOS 7、Ubuntu 24.04 这几行一样。)nc 只有 Ubuntu 自带,wget Rocky 9 没有。ip 和 ss 是唯一能指望的 ;dig 在 RHEL 系要装 bind-utils,Ubuntu 装 dnsutils。
1. 改了 /etc/hosts,不同命令给出不同答案 ✅
往 /etc/hosts 追加一行 1.2.3.4 www.baidu.com,然后用五种方式解析:
CentOS 7 / Rocky 9(两台一样):
ini
== getent hosts:
2409:8c54:870:310:0:ff:b0ed:40ac www.a.shifen.com www.baidu.com
2409:8c54:870:187:0:ff:b0d9:bb1c www.a.shifen.com www.baidu.com
== getent ahosts:
1.2.3.4 STREAM www.baidu.com
== dig +short:
111.45.11.5
183.240.99.224
== ping:
PING www.baidu.com (1.2.3.4) 56(84) bytes of data.
Ubuntu 24.04:
ini
== getent hosts:
1.2.3.4 www.baidu.com
== getent ahosts:
1.2.3.4 STREAM www.baidu.com
== dig +short:
1.2.3.4
== ping:
PING www.baidu.com (1.2.3.4) 56(84) bytes of data.
两个反直觉的地方:
① 「用 getent hosts 验证 hosts 有没有生效」在 CentOS 7 / Rocky 9 上会骗你。 getent hosts 优先查 IPv6 地址,hosts 里写的是 IPv4,于是它去 DNS 拿了百度的 IPv6 回来。ping 实际用的就是 1.2.3.4。要验证,用 getent ahosts,三台都给出 1.2.3.4。
② 「dig 不读 /etc/hosts」在 Ubuntu 上不成立。 Ubuntu 的 /etc/resolv.conf 指向本机的 systemd-resolved(127.0.0.53),dig 问的是它,而 resolved 自己会读 /etc/hosts。CentOS 7 / Rocky 9 的 dig 直接问上游 DNS,确实不读 hosts。
2. 手改 /etc/resolv.conf,过一会儿就没了 ✅
Rocky 9:文件头写着是谁生成的。
ruby
# Generated by NetworkManager
手改后:nameserver 9.9.9.9
nmcli con up 之后:nameserver 10.115.180.1 nameserver fd42:7e9b:f1d3:c525::1 nameserver fe80::216:3eff:feca:e4d9%enp5s0
连接一重新激活(重启网络、重启机器都会),手改的内容就被覆盖。要改 DNS,改连接配置 :nmcli con mod <连接名> ipv4.dns ...。
Ubuntu 24.04 :/etc/resolv.conf 根本不是一个普通文件。
bash
lrwxrwxrwx 1 root root 39 Sep 20 07:46 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf
nameserver 127.0.0.53
手改写进的是 /run 下那份文件,重启 resolved 就回去了:
ruby
手改后:nameserver 9.9.9.9
restart systemd-resolved 之后:nameserver 127.0.0.53
真实的上游 DNS 要用 resolvectl status 看:
arduino
Current DNS Server: 10.115.180.1
CentOS 7 (这台用 dhclient):文件头是 ; generated by /usr/sbin/dhclient-script,DHCP 续租时同样会被重写。
🔑 动 resolv.conf 之前先 ls -l 看它是不是软链、head -1 看是谁生成的。
3. 用 /dev/tcp 测端口 ------ 但别在 sh 里用 ✅
没有 telnet、nc 的机器,bash 自带的 /dev/tcp 能顶上:
shell
# timeout 3 bash -c 'echo > /dev/tcp/127.0.0.1/22' && echo "22 端口 OK" || echo "22 端口 FAIL"
22 端口 OK
9 端口 FAIL
bash: connect: Connection refused
三台都行。但它是 bash 的功能,不是系统的 。脚本开头写 #!/bin/sh 时:
bash
lrwxrwxrwx. 1 root root 4 Apr 30 2024 /bin/sh -> bash (Rocky 9)
sh 下 /dev/tcp 可用
lrwxrwxrwx 1 root root 4 Mar 31 2024 /bin/sh -> dash (Ubuntu 24.04)
sh: 1: cannot create /dev/tcp/127.0.0.1/22: Directory nonexistent
CentOS 7 的 /bin/sh 也是 bash,能用。同一个脚本,搬到 Ubuntu 就坏,报错还是「目录不存在」,很难联想到原因。
顺带,两种 nc 的输出完全不一样,写脚本判断输出文字的要注意:
vbnet
Ncat: 0 bytes sent, 0 bytes received in 0.01 seconds. (CentOS 7 / Rocky 9 装的 nmap-ncat,端口通)
Ncat: Connection refused.
Connection to 127.0.0.1 22 port [tcp/ssh] succeeded! (Ubuntu 自带的 openbsd nc,端口通)
nc: connect to 127.0.0.1 port 9 (tcp) failed: Connection refused
4. CLOSE-WAIT 堆积 ------ 复现一次就知道该找谁 ✅
写一个 python 服务端:accept() 之后永远不 close(模拟连接池泄漏)。客户端连 5 次,每次连上就关:
ini
== 服务端(9100)一侧:
5 CLOSE-WAIT
1 LISTEN
== 客户端一侧:
5 FIN-WAIT-2
三台一样。CLOSE-WAIT 在哪一侧,就是哪一侧的程序没关连接 ------ 对方已经关了(发了 FIN),自己这边一直没调 close()。调内核参数没用,这是程序的 bug。
css
# ss -tan state close-wait | head -3
Recv-Q Send-Q Local Address:Port Peer Address:Port
1 0 127.0.0.1:9100 127.0.0.1:44834
1 0 127.0.0.1:9100 127.0.0.1:44842
Recv-Q 是 1:对方的 FIN 到了,程序没读。在 Rocky 9 / Ubuntu 24.04 上杀掉服务进程后:
objectivec
杀掉服务进程后 CLOSE-WAIT 剩:0
5. 连一个没人监听的端口,抓包看到的是 RST ✅
bash
IP 127.0.0.1.51416 > 127.0.0.1.9999: Flags [S], seq 1006525363, win 65495, ...
IP 127.0.0.1.9999 > 127.0.0.1.51416: Flags [R.], seq 0, ack 1006525364, win 0, length 0
(Rocky 9,三台一样。)SYN 过去,RST 回来,客户端报 Connection refused。报 Connection refused = 包到了对方机器、但那个端口没人监听 ;如果是防火墙丢包,看到的是超时,或者防火墙主动回拒绝(下面第 12 节的 No route to host 就是 firewalld 的拒绝)。
6. MTU:1472 能过,1473 不行 ✅
ini
# ping -c1 -M do -s 1472 <网关>
1480 bytes from 10.115.180.1: icmp_seq=1 ttl=64 time=0.140 ms
# ping -c1 -M do -s 1473 <网关>
ping: local error: message too long, mtu=1500
1472 + 8(ICMP 头)+ 20(IP 头)= 1500。-M do 禁止分片,用来探测路径 MTU。CentOS 7 的报错是大写开头的 Message too long,Rocky 9 / Ubuntu 24.04 是小写 ------ 用 grep 判断时加 -i。
7. sudo 之后代理没了 ✅
当前 shell:1
sudo 之后:0
sudo -E 之后:1
(Rocky 9 / Ubuntu 24.04 一样;数字是 env 里 http_proxy 的行数。)sudoers 默认 Defaults env_reset,sudo 会清掉环境变量,于是 sudo yum install、sudo curl 走不了你 export 的代理。用 sudo -E,或者在 sudoers 里 env_keep += "http_proxy https_proxy"。
8. Rocky 9:nmcli 改完不生效,还要 up 一次 ✅
rust
== mod 之后、up 之前:
lab0 UNKNOWN 10.99.0.1/24 fe80::74e0:5aff:febb:7296/64
10.99.0.2/24
== up 之后:
lab0 UNKNOWN 10.99.0.2/24 fe80::74e0:5aff:febb:7296/64
nmcli con mod 只改配置(nmcli -g 已经显示 .2),网卡上还是 .1,nmcli con up 之后才生效。新建的连接存成 keyfile 格式:
lab0.nmconnection
(在 /etc/NetworkManager/system-connections/ 下。)
「RHEL 9 不再读 ifcfg 文件」这个说法过头了。 Rocky 9 cloud 镜像自己的网卡配置就是 ifcfg-enp5s0;手写一个 ifcfg-lab1 再 nmcli con reload,NetworkManager 照样认:
javascript
lab1::/etc/sysconfig/network-scripts/ifcfg-lab1
准确的说法是:新建的连接默认存成 keyfile,旧的 ifcfg 仍然能读。
9. Ubuntu:netplan 的三个坑 ✅
① 权限太宽只给警告(退出码仍是 0):
kotlin
** (generate:1421): WARNING **: 17:37:10.219: Permissions for /etc/netplan/90-lab.yaml are too open. Netplan configuration should NOT be accessible by others.
② 缩进用了 Tab 直接失败:
bash
有 Tab 时 generate 退出码=1
/etc/netplan/90-lab.yaml:5:1: Invalid YAML: tabs are not allowed for indent:
③ 网上教程里的 gateway4 已经废弃(还能用,给警告):
go
** (generate:1583): WARNING **: 17:37:13.058: `gateway4` has been deprecated, use default routes instead.
netplan try 回滚的只是运行状态,不回滚文件。 把地址改成 10.99.0.9 后 netplan try --timeout 8,不按回车:
php
Press ENTER before the timeout to accept the new configuration
Reverting.
== try 结束后:
lab0 UNKNOWN fe80::ccc0:aeff:fedb:490d/64
addresses: [10.99.0.9/24]
网卡上已经没有 10.99.0.9,但 yaml 文件里还是它。下一次 netplan apply 或重启,这个你以为已经回滚了的配置就会生效。 远程改网络时这一点最要命:try 救了你一次,重启机器又断了。
10. SSH 默认配置,三台不一样 ✅
yaml
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017 (CentOS 7)
permitrootlogin yes
passwordauthentication yes
usedns yes
OpenSSH_9.9p1, OpenSSL 3.5.8 25 Aug 2026 (Rocky 9)
permitrootlogin without-password
passwordauthentication yes
usedns no
(sshd -T 输出节选。Ubuntu 24.04 的 OpenSSH_9.6p1 三项与 Rocky 9 相同。)
- root 能不能用密码登录 :CentOS 7 能;Rocky 9 / Ubuntu 24.04 默认
without-password,只能用密钥。 usedns yes:CentOS 7 默认开着,这是「SSH 登录要卡好几秒」的常见原因(服务端反查客户端 IP 的主机名)。- 配置文件 :Rocky 9 / Ubuntu 24.04 的
sshd_config里有Include /etc/ssh/sshd_config.d/*.conf,而且这行在文件开头附近 ------ sshd 对同一选项取第一次出现的值 ,所以sshd_config.d/里的设置会压过主文件后面写的。CentOS 7 没有这个目录。 - 日志 :CentOS 7 / Rocky 9 在
/var/log/secure,Ubuntu 在/var/log/auth.log。
改完配置先检查语法,别直接重启:
arduino
# echo 'PermitRootLogin maybe' > /etc/ssh/sshd_config.d/99-lab.conf; sshd -t
/etc/ssh/sshd_config.d/99-lab.conf line 1: unsupported option "maybe".
sshd -t 退出码=255
11. 密钥登录 Permission denied ------ 权限到底要多严 ✅
同一把密钥,只改权限,在 Rocky 9 和 Ubuntu 24.04 上逐项试:
| 改动 | Rocky 9 | Ubuntu 24.04 |
|---|---|---|
| 家目录 777 | ❌ 拒绝 | ❌ 拒绝 |
| 家目录 775(组可写) | ❌ 拒绝 | ✅ 能登录 |
家目录 755 + .ssh 755 |
✅ 能登录 | ✅ 能登录 |
.ssh 777 |
❌ 拒绝 | ❌ 拒绝 |
authorized_keys 644 |
✅ 能登录 | ✅ 能登录 |
authorized_keys 666 |
❌ 拒绝 | ❌ 拒绝 |
authorized_keys 属主改成 root |
❌ | ❌ 拒绝 |
结论:规则是「不能让别人写」,不是「必须 700 / 600」。 .ssh 755、authorized_keys 644 都能登录;真正致命的是组或其他人可写。Ubuntu 对「家目录组可写」放行(推测是 Debian 系对「每个用户一个私有组」做的补丁,未查证),Rocky 9 不放行。
客户端只会告诉你:
sql
labu@127.0.0.1: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).
原因只在服务端日志里:
bash
== /var/log/secure:
2 Authentication refused: bad ownership or modes for directory /home/labu
1 Authentication refused: bad ownership or modes for directory /home/labu/.ssh
1 Authentication refused: bad ownership or modes for file /home/labu/.ssh/authorized_keys
直接告诉你是哪一层目录的问题。排查密钥登录,第一步就是去服务端看这个日志,不是在客户端反复试。
🆕 Rocky 9 上「属主改成 root」那一次,客户端看到的不是 Permission denied,而是:
csharp
[authorized_keys 属主改成 root] kex_exchange_identification: read: Connection reset by peer
服务端日志:
arduino
drop connection #0 from [127.0.0.1]:54934 on [127.0.0.1]:22 penalty: failed authentication
persourcepenalties crash:90 authfail:5 noauth:1 grace-exceeded:10 refuseconnection:10 max:600 min:15 max-sources4:65536 max-sources6:65536 overflow:permissive overflow6:permissive
这是 OpenSSH 9.8 起新增的 PerSourcePenalties :同一个来源 IP 认证失败累积到一定程度,sshd 会直接断开 它一段时间。测试时连续失败了好几次,于是被「罚」了。排查时反复重试,可能把自己的 IP 罚进去 ,看到 Connection reset 别以为是网络问题。Ubuntu 24.04 的 9.6 没有这个机制。
12. Rocky 9 改 SSH 端口:重启成功,端口却没开 ✅
在 sshd_config.d/ 里写 Port 22 + Port 2222:
ini
sshd -t 通过
restart 退出码=0 is-active=active
0.0.0.0:22
[::]:22
语法检查通过、重启成功、服务 active ------ 但只监听了 22。 日志:
vbnet
error: Bind to port 2222 on 0.0.0.0 failed: Permission denied.
error: Bind to port 2222 on :: failed: Permission denied.
是 SELinux:sshd 只被允许绑定标记为 ssh_port_t 的端口。
markdown
# semanage port -a -t ssh_port_t -p tcp 2222; semanage port -l | grep '^ssh_port_t'
ssh_port_t tcp 2222, 22
0.0.0.0:2222
0.0.0.0:22
[::]:2222
[::]:22
⚠️ 如果只写 Port 2222、把 22 去掉,结果是这样的(换成 2224 测试,没加 SELinux 标记):
vbnet
sshd -t 通过
只留 Port 2224 时 restart 退出码=1 is-active=activating
0
Job for sshd.service failed because the control process exited with error code.
一个端口都没监听。 已连着的会话没断,所以你不会马上发现 ------ 退出后就再也连不上了。改端口一定先加新端口、保留 22,确认新端口能登录再删 22。 semanage 在 Rocky 9 cloud 镜像里默认没有,要装 policycoreutils-python-utils。
端口开了,外面还是连不上,那是 firewalld:
yaml
nc: connect to 10.115.180.175 port 2222 (tcp) failed: No route to host
firewall-cmd --add-port=2222/tcp 之后:
css
Connection to 10.115.180.175 2222 port [tcp/*] succeeded!
注意报错是 No route to host ,不是 Connection refused,很容易被误导去查路由。
13. Ubuntu 24.04 改 SSH 端口:restart ssh 不管用 ✅
同样写 Port 22 + Port 2222:
ini
sshd -t 通过
restart ssh 退出码=0
0.0.0.0:22 users:(("sshd",pid=1112,fd=3),("systemd",pid=1,fd=69))
[::]:22 users:(("sshd",pid=1112,fd=4),("systemd",pid=1,fd=70))
注意监听者里有 systemd:Ubuntu 24.04 的 SSH 是 socket 激活的 ,端口由 ssh.socket 占着,sshd 配置里的 Port 不直接决定监听哪个端口。
sshd active
ssh active
ssh.socket active
要这样才生效:
ini
# systemctl daemon-reload; systemctl restart ssh.socket
0.0.0.0:2222 users:(("sshd",pid=1177,fd=3),("systemd",pid=1,fd=60))
0.0.0.0:22 users:(("sshd",pid=1177,fd=5),("systemd",pid=1,fd=62))
[::]:2222 users:(("sshd",pid=1177,fd=4),("systemd",pid=1,fd=61))
[::]:22 users:(("sshd",pid=1177,fd=6),("systemd",pid=1,fd=63))
daemon-reload 这一步是关键:有个生成器会读 sshd_config 里的 Port,生成 socket 的监听配置:
ini
# /run/systemd/generator/ssh.socket.d/addresses.conf
ListenStream=
ListenStream=0.0.0.0:2222
网上大量「改 /etc/ssh/sshd_config 的 Port,然后 systemctl restart sshd」的教程,在 Ubuntu 24.04 上执行不报错、端口不变。
14. 服务器重装后连不上:REMOTE HOST IDENTIFICATION HAS CHANGED ✅
在 Rocky 9 上重新生成主机密钥(相当于重装系统),从 Ubuntu 再连:
python
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Offending ED25519 key in /root/.ssh/known_hosts:1
退出码=255
(节选。)确认是自己重装的,删掉旧记录:
bash
# ssh-keygen -R 10.115.180.175
# Host 10.115.180.175 found: line 1
/root/.ssh/known_hosts updated.
Original contents retained as /root/.ssh/known_hosts.old
两个细节:
- 别想着用
grep IP known_hosts找那一行 ------ Ubuntu 默认把主机名哈希了,文件里是|1|77ChuBKUWhZ2GkMu3fKEWd4gNbg=|...这样的内容,grep 不到。用ssh-keygen -R。 - 非 22 端口的记录名字是
[IP]:端口,要单独删:ssh-keygen -R '[10.115.180.175]:2222'。
15. 只监听 127.0.0.1 的服务,用 SSH 隧道访问 ✅
Rocky 9 上一个 HTTP 服务只监听本机:
makefile
127.0.0.1:8080
从外面连:
yaml
nc: connect to 10.115.180.175 port 8080 (tcp) failed: No route to host
本地转发:
arduino
# ssh -fNL 18080:127.0.0.1:8080 labu@10.115.180.175
ssh -fNL 退出码=0
经隧道访问 127.0.0.1:18080 → 200
-f 放后台,-N 不执行远程命令。用完记得关,它不会自己退出:
ini
1089 ssh -fNL 18080:127.0.0.1:8080 -i /root/.ssh/lab_ed -o BatchMode=yes labu@10.115.180.175
小结
| 现象 | 真正的原因 |
|---|---|
hosts 改了,getent hosts 不认 |
它优先查 IPv6;用 getent ahosts |
Ubuntu 上 dig 认 hosts |
dig 问的是 resolved,resolved 读 hosts |
| resolv.conf 手改被冲掉 | NetworkManager / resolved / dhclient 在管它 |
#!/bin/sh 脚本里 /dev/tcp 报目录不存在 |
Ubuntu 的 sh 是 dash |
| CLOSE-WAIT 堆积 | 这一侧的程序没 close |
| netplan try 回滚了,重启又断 | 文件没回滚 |
| 密钥登录 Permission denied | 看服务端日志 Authentication refused |
| 反复试后变成 Connection reset | OpenSSH 9.8+ 的 PerSourcePenalties |
| Rocky 改端口没生效 | SELinux,semanage port -a -t ssh_port_t |
| Ubuntu 24.04 改端口没生效 | daemon-reload + restart ssh.socket |
下一篇:日志与性能 ------ journal 重启就没了、OOM 杀进程、logrotate 的两种模式。