郭立 (leeguoo)

# 群晖 DSM 开了 SSH 端口还是打不开:一次端口漂移复盘

群晖 DSM 面板里开启 SSH 或修改端口后,端口仍然打不开,问题不一定在密码、账号或路由器端口转发,也可能是 DSM 前端状态和 sshd_config 里的旧端口配置叠在了一起。

2026年6月27日 · 文章 · 公开

本页目录

群晖 DSM 前端端口和 sshd 配置分裂示意

群晖 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 配置解析,于是启动失败。

$ text
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 报:

$ text
Directive 'Port' is not allowed within a Match block

这个错误不复杂,修复方式是把 Port 放到全局配置区,也就是 Match 之前。修完后 sshd -t 通过,旧端口开始监听。

第二个坑:前端改了,旧配置没消失

后来我们在 DSM 前端里把 SSH 改到新端口。这时出现了一个更反直觉的状态:

$ text
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 也直接确认:

$ text
port <old-port>
port <new-port>

为什么前端已经是新端口,系统还保留旧端口?答案在两个文件:

$ text
/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 写了一小段错误,客户端看到的是:

$ text
Permission denied, please try again.

同一台机器、同一个管理员账号、同一把密钥,换成新端口就能进。这个差异不像账号权限问题,更像群晖在会话阶段只认 /etc/synoinfo.conf 里的 SSH 端口。旧端口是被 OpenSSH 配置额外带出来的入口,不再是 DSM 认可的入口。

这个判断不需要过度展开。对运维来说,足够的结论是:不要让 DSM 的 SSH 端口和 sshd_configPort 同时说话。

收口动作

修复只做了一件事:保留 DSM 的端口来源,去掉手写残留。

备份原配置:

$ bash
cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.before-remove-old-port

把这一行:

$ text
Port <old-port>

改成注释:

$ text
#Port <old-port>
# Removed local stale override; DSM SSH port is stored in /etc/synoinfo.conf ssh_port.

然后检查并 reload:

$ bash
/bin/sshd -t -f /etc/ssh/sshd_config
/bin/systemctl reload sshd

修完后的有效配置只剩一个端口:

$ text
port <new-port>

从另一台机器验证:

$ bash
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 权限上。这些检查不是错的,但它们解释不了“同一账号在新端口能进、在旧端口认证后被踢”。

更早应该做这三条:

$ bash
/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 端口问题,我会按这个顺序走:

  1. sshd -t 先确认配置文件能不能被 OpenSSH 解析。
  2. sshd -T | grep '^port ' 看 OpenSSH 最终认了哪些端口。
  3. grep ssh_port /etc/synoinfo.conf 看 DSM 前端认哪个端口。
  4. netstat -ltnp 看真实监听。
  5. 新开一个 SSH 会话验证,确认以后再关 Telnet。

这次问题修完后,稳定入口只保留一个:

$ bash
ssh -p <new-port> <admin-user>@<nas-host>

少猜,多看最终态。NAS 这种半 appliance、半 Linux 的系统,前端状态和底层配置同时存在,二者不一致时,真实行为只认启动出来的进程。

下一篇 →
Claude Code 把自己吓到了:一次“提示词注入”乌龙

评论

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

最多 1000 字。