
群晖 DSM 里明明已经开启 SSH,端口也改了,外面还是打不开。这个问题第一眼很像路由器端口转发错了,或者账号密码不对。我们这次遇到的不是这两类。
真正的坑在群晖自己的前端状态和 OpenSSH 配置文件之间。DSM 面板里显示的是新端口,但 /etc/ssh/sshd_config 里还残留旧端口。结果同一个 sshd 进程同时监听两个端口:一个是 DSM 认可的新端口,一个是手工配置留下来的旧端口。
现场是什么样
为避免把私人环境写到公开页面,下面把真实主机、账号和端口都泛化处理。场景是这样的:
- DSM 的“终端机”页面里开了 SSH。
- 从本机连
<nas-host>:<old-port>,一开始是Connection refused。 - 通过临时入口进到系统后,发现 OpenSSH 配置文件不能通过语法检查。
当时 /etc/ssh/sshd_config 头部混进了一段不属于 OpenSSH 的 YAML 配置。内容不重要,重要的是它让 sshd 把普通 YAML 当成 SSH 配置解析,于是启动失败。
Bad configuration option: version:
Bad configuration option: services:
这会让 /bin/sshd -t -f /etc/ssh/sshd_config 报一串 Bad configuration option。所以第一层根因很清楚:OpenSSH 配置文件坏了,sshd 根本起不来。
我们从 /etc.defaults/ssh/sshd_config 恢复默认配置,再把 SSH 开起来。这一步里我犯了一个小错:第一次把 Port <old-port> 追加到了文件末尾,而默认配置末尾已经进入 Match 块,OpenSSH 报:
Directive 'Port' is not allowed within a Match block
这个错误不复杂,修复方式是把 Port 放到全局配置区,也就是 Match 之前。修完后 sshd -t 通过,旧端口开始监听。
第二个坑:前端改了,旧配置没消失
后来我们在 DSM 前端里把 SSH 改到新端口。这时出现了一个更反直觉的状态:
tcp 0 0 0.0.0.0:<old-port> LISTEN sshd
tcp 0 0 0.0.0.0:<new-port> LISTEN sshd
同一个 sshd 进程同时听两个端口。sshd -T 也直接确认:
port <old-port>
port <new-port>
为什么前端已经是新端口,系统还保留旧端口?答案在两个文件:
/etc/synoinfo.conf: ssh_port="<new-port>"
/etc/ssh/sshd_config: Port <old-port>
DSM 自己的端口状态存在 /etc/synoinfo.conf。我们手工修复时留下的 Port <old-port> 是 OpenSSH 原生配置。群晖启动 sshd 时把两边叠在一起,于是变成双端口。
这也解释了为什么前端看起来“已经成功改成新端口”,但实际网络行为还是不干净。前端没有撒谎,它只展示 DSM 自己记录的端口。问题是我们另外手写了一条 OpenSSH 端口配置,DSM 不会替我们清理。
为什么旧端口认证后还会被踢
这一点没有看到群晖源码,只能按现场证据判断。
旧端口不是完全拒绝连接,它能走到认证成功。我们开过一次 sshd -ddd 调试,日志里能看到 public key 通过、PAM account/session 也通过,然后子进程往 stderr 写了一小段错误,客户端看到的是:
Permission denied, please try again.
同一台机器、同一个管理员账号、同一把密钥,换成新端口就能进。这个差异不像账号权限问题,更像群晖在会话阶段只认 /etc/synoinfo.conf 里的 SSH 端口。旧端口是被 OpenSSH 配置额外带出来的入口,不再是 DSM 认可的入口。
这个判断不需要过度展开。对运维来说,足够的结论是:不要让 DSM 的 SSH 端口和 sshd_config 的 Port 同时说话。
收口动作
修复只做了一件事:保留 DSM 的端口来源,去掉手写残留。
备份原配置:
cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.before-remove-old-port
把这一行:
Port <old-port>
改成注释:
#Port <old-port>
# Removed local stale override; DSM SSH port is stored in /etc/synoinfo.conf ssh_port.
然后检查并 reload:
/bin/sshd -t -f /etc/ssh/sshd_config
/bin/systemctl reload sshd
修完后的有效配置只剩一个端口:
port <new-port>
从另一台机器验证:
ssh -p <new-port> <admin-user>@<nas-host>
# ok
nc -vz -G 3 <nas-host> <old-port>
# Connection refused
nc -vz -G 3 <nas-host> 23
# Connection refused
这才算收口:SSH 只留新端口,旧端口没有监听,Telnet 的 23 也关了。
这次我哪里判断慢了
一开始我把注意力放在账号、密码、PAM、shell、authorized_keys 权限上。这些检查不是错的,但它们解释不了“同一账号在新端口能进、在旧端口认证后被踢”。
更早应该做这三条:
/bin/sshd -T -f /etc/ssh/sshd_config | grep '^port '
grep -n 'ssh_port' /etc/synoinfo.conf
netstat -ltnp | grep -E '(:<new-port>|:<old-port>|:23)'
这三条能直接把“前端状态”“OpenSSH 有效配置”“真实监听端口”摆在一起。只要三者不一致,就先别猜密码和 PAM。
另一个教训是,恢复厂商默认配置时不要只看语法通过。DSM 这种系统不是纯 Linux 服务器,它有自己的配置数据库、前端状态和服务脚本。手工写进 /etc/ssh/sshd_config 的东西,可能和 DSM 管理面板叠加,而不是覆盖。
下次按这个顺序查
以后遇到 DSM SSH 端口问题,我会按这个顺序走:
sshd -t先确认配置文件能不能被 OpenSSH 解析。sshd -T | grep '^port '看 OpenSSH 最终认了哪些端口。grep ssh_port /etc/synoinfo.conf看 DSM 前端认哪个端口。netstat -ltnp看真实监听。- 新开一个 SSH 会话验证,确认以后再关 Telnet。
这次问题修完后,稳定入口只保留一个:
ssh -p <new-port> <admin-user>@<nas-host>
少猜,多看最终态。NAS 这种半 appliance、半 Linux 的系统,前端状态和底层配置同时存在,二者不一致时,真实行为只认启动出来的进程。

微信
支付宝
评论
评论发布后会立即公开,如触发规则可能被审核下架。