跳转至内容

OpenSSH

来自 ArchWiki

OpenSSH (OpenBSD Secure Shell) 是一组计算机程序,通过使用 Secure Shell (SSH) 协议的计算机网络提供加密通信会话。它作为 SSH Communications Security 提供的专有 Secure Shell 软件套件的开源替代品而创建。OpenSSH 是作为 Theo de Raadt 领导的 OpenBSD 项目的一部分开发的。

OpenSSH 有时会与名称相似的 OpenSSL 混淆;然而,这两个项目具有不同的目的,并且由不同的团队开发,相似的名称仅来源于相似的目标。

安装

安装 openssh 包。

客户端用法

要连接到服务器,运行

$ ssh -p port user@server-address

如果服务器仅允许公钥认证,请遵循 SSH 密钥

配置

本文章或章节需要扩充。

原因: openssh 9.4p1-2 在 /etc/ssh/ssh_config 中添加了 Include /etc/ssh/ssh_config.d/*.conf。现在可以使指令使用下拉文件。 (在 Talk:OpenSSH 讨论)

客户端可以配置为存储常用选项和主机。所有选项都可以全局声明或限制在特定主机。例如

~/.ssh/config
# global options
User user

# host-specific options
Host myserver
    Hostname server-address
    Port     port

有了这样的配置,以下命令是等效的

$ ssh -p port user@server-address
$ ssh myserver

有关更多信息,请参阅 ssh_config(5)

有些选项没有命令行开关的等效项,但您可以使用 -o 在命令行上指定配置选项。例如 -oKexAlgorithms=+diffie-hellman-group1-sha1

服务器用法

本文章或章节需要扩充。

原因: openssh 9.4p1-2 在 /etc/ssh/sshd_config 中添加了 Include /etc/ssh/sshd_config.d/*.conf。现在可以使指令使用下拉文件。 (在 Talk:OpenSSH 讨论)

sshd 是 OpenSSH 服务器守护进程,由 /etc/ssh/sshd_config 配置并由 sshd.service 管理。在更改配置时,请在重启服务之前使用 sshd 测试模式,以确保它能够干净地启动。有效配置不会产生任何输出。

# sshd -t

配置

要仅允许某些用户访问,请添加此行

AllowUsers    user1 user2

要仅允许某些组访问

AllowGroups   group1 group2

要添加欢迎消息(例如,来自 /etc/issue 文件),请配置 Banner 选项

Banner /etc/issue

公共和私有主机密钥由 sshdgenkeys 服务/etc/ssh 中自动生成,如果丢失(即使 sshd_config 中的 HostKeyAlgorithms 选项只允许某些算法),它们也会被重新生成。基于 ed25519、ecdsa 和 rsa 算法提供三个密钥对。要让 sshd 使用特定的密钥,请指定以下选项

HostKey /etc/ssh/ssh_host_ed25519_key

如果服务器要暴露给 WAN,建议将默认端口 22 更改为随机的高端口,如下所示

Port 39901
提示
  • 为了帮助选择一个尚未分配给常用服务的替代端口,请查阅 TCP 和 UDP 端口号列表。您也可以在本地 /etc/services 中找到端口信息。将端口从默认端口 22 更改将减少因自动身份验证尝试而产生的日志条目数量,但不会消除它们。有关相关信息,请参阅 端口敲门
  • 建议完全禁用密码登录。这将大大提高安全性,有关更多信息,请参阅 #强制公钥认证。有关更多推荐的安全方法,请参阅 #保护
  • OpenSSH 可以通过在配置文件中有多个 Port port_number 行来监听多个端口。
  • 可以通过从 /etc/ssh 中删除要替换的密钥对,然后以 root 身份运行 ssh-keygen -A 来生成新的(或丢失的)主机密钥对。

守护进程管理

启动/启用 sshd.service。它将使 SSH 守护进程永久激活,并为每个传入连接创建一个进程。

套接字激活 (Socket activation)

openssh 8.0p1-3 移除了使用 systemd 的套接字激活的 sshd.socket,因为它容易受到拒绝服务攻击。有关详细信息,请参阅 FS#62248。在更新到 openssh 8.0p1-3 时,如果启用了 sshd.socket,则 sshd.socketsshd@.service 单元将被复制到 /etc/systemd/system/重新启用。这仅是为了不破坏现有设置;仍然建议用户迁移到 sshd.service

警告 如果您继续使用 sshd.socket,请注意其问题
  • sshd.socket 单元可能会失败(例如,由于内存不足的情况),并且不能在套接字单元上指定 Restart=always。请参阅 systemd issue 11553
  • 使用套接字激活可能导致拒绝服务,因为过多的连接可能导致拒绝进一步激活服务。请参阅 FS#62248

使用 sshd.socket 会忽略 ListenAddress 设置,因此它允许通过任何地址进行连接。要实现设置 ListenAddress 的效果,您必须通过 编辑 sshd.socket 来为 ListenStream 指定端口 IP(例如,ListenStream=192.168.1.100:22)。您还必须在 [Socket] 下添加 FreeBind=true,否则设置 IP 地址将具有与设置 ListenAddress 相同的缺点:如果网络未及时启动,套接字将无法启动。

使用套接字激活时,将为每个连接启动一个瞬态的 sshd@.service 实例(带有不同的实例名称)。因此,sshd.socket 和守护进程的常规 sshd.service 都不能在日志中监视连接尝试。可以通过以 root 身份运行 journalctl -u "sshd@*" 或以 root 身份运行 journalctl /usr/bin/sshd 来查看 SSH 的套接字激活实例的日志。

保护

通过 SSH 允许远程登录对管理目的很有用,但可能对您的服务器安全构成威胁。SSH 访问通常是暴力破解攻击的目标,需要正确限制 SSH 访问以防止第三方获得对您服务器的访问权。

ssh-audit 提供对服务器和客户端配置的自动分析。有关该主题,还有许多其他好的指南和工具,例如

强制公钥认证

如果客户端无法通过公钥进行身份验证,默认情况下,SSH 服务器将回退到密码身份验证,从而允许恶意用户通过 暴力破解密码来尝试获得访问权限。一种防止此攻击的最有效方法是完全禁用密码登录,并强制使用 SSH 密钥。这可以通过在守护进程配置文件中设置以下选项来完成

/etc/ssh/sshd_config.d/20-force_publickey_auth.conf
PasswordAuthentication no
AuthenticationMethods publickey
警告 在将此添加到您的配置之前,请确保所有需要 SSH 访问的帐户都已在相应的 authorized_keys 文件中设置了公钥认证。有关更多信息,请参阅 SSH 密钥#将公钥复制到远程服务器

双因素认证和公钥

SSH 可以设置为需要多种身份验证方式;您可以使用 AuthenticationMethods 选项来指定需要哪些身份验证方法。这使您可以使用公钥以及双因素授权。

认证提供者

请参阅 Google Authenticator 来设置 Google Authenticator。

对于 Duo,安装 duo_unixAUR,它将提供 pam_duo.so 模块。请阅读 Duo Unix 文档 以获取有关如何设置必要的 Duo 凭据(集成密钥、秘密密钥、API 主机名)的说明。

PAM 设置

本文或本章节的准确性存在争议。

原因:[1] 以来,发行版默认设置为 KbdInteractiveAuthentication no。之后,由于 FS#79285,默认值的词法顺序进行了调整,以允许优先级更高的用户片段,这些片段会匹配 20-pam.conf 以下。然而,一个 BBS 帖子似乎通过仅将自定义项按顺序排列在 99-archlinux.conf 默认值之后来解决。(在 Talk:OpenSSH 讨论)

要将 PAM 与 OpenSSH 一起使用,请编辑以下文件

/etc/ssh/sshd_config.d/20-pam.conf
KbdInteractiveAuthentication yes
AuthenticationMethods publickey keyboard-interactive:pam

然后,您可以根据您的 PAM 设置,使用 publickey 用户身份验证进行登录。

另一方面,如果您想同时使用 publickey 用户身份验证(如您的 PAM 设置所要求)来验证用户,请使用逗号而不是空格来分隔 AuthenticationMethods

/etc/ssh/sshd_config.d/20-pam.conf
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive:pam

在需要 pubkey pam 身份验证的情况下,您可能希望禁用密码要求

/etc/pam.d/sshd
auth      required  pam_securetty.so     #disable remote root
#Require google authenticator
auth      required  pam_google_authenticator.so
#But not password
#auth      include   system-remote-login
account   include   system-remote-login
password  include   system-remote-login
session   include   system-remote-login

防止暴力破解攻击

暴力破解是一个简单的概念:连续尝试使用大量的随机用户名和密码组合登录到一个网页或服务器登录提示(如 SSH)。

有关 iptables,请参阅 ufw#使用 ufw 进行速率限制Simple stateful firewall#暴力破解攻击

自 9.8 起,实现了一个类似 fail2ban 的基本保护:PerSourcePenalties 选项使用合理的默认值进行设置。针对客户端的各种情况下的处罚是根据其源地址执行的,导致在一段时间内连接被拒绝。

另外,您可以使用一个自动阻止试图暴力破解的人的脚本来保护自己免受暴力破解攻击。

  • 仅允许来自受信任位置的传入 SSH 连接
  • 使用 fail2bansshguard 自动阻止多次密码身份验证失败的 IP 地址。
  • 使用 pam_shield 来阻止在一定时间内进行过多登录尝试的 IP 地址。与 fail2bansshguard 不同,此程序不考虑登录成功或失败。

限制 root 登录

本文或本章节已过时。

原因: 在当前版本中,root 登录已被默认禁用。我也不清楚本节及子节的哪些部分是多余的。(在 Talk:OpenSSH 讨论)

通常认为不允许 root 用户通过 SSH 无限制地登录是不好的做法。有两种方法可以限制 SSH root 访问以提高安全性。

拒绝

Sudo 选择性地为需要这些权限的操作提供 root 权限,而无需针对 root 帐户进行身份验证。这允许锁定 root 帐户以防止通过 SSH 访问,并可能作为防止暴力破解攻击的安全措施,因为现在攻击者除了密码外还必须猜对帐户名。

可以通过编辑守护进程配置文件中的“身份验证”部分来配置 SSH 以拒绝 root 用户的远程登录。只需将 PermitRootLogin 设置为 no

/etc/ssh/sshd_config.d/20-deny_root.conf
PermitRootLogin no

接下来,重启 SSH 守护进程。

您现在将无法通过 SSH 以 root 用户身份登录,但仍然可以登录到您的普通用户并使用 susudo 进行系统管理。

限制

一些自动化任务,如远程全系统备份,需要完整的 root 权限。为了安全地允许这些操作,而不是通过 SSH 禁用 root 登录,可以只允许 root 用户登录执行选定的命令。这可以通过编辑 ~root/.ssh/authorized_keys 来实现,方法是在所需密钥前加上前缀,例如如下:

command="rrsync -ro /" ssh-ed25519 ...

这将允许使用此特定密钥的任何登录仅执行引号之间的指定命令。

通过将以下内容添加到 sshd_config 中,可以补偿暴露 root 用户名登录所带来的增加的攻击面

PermitRootLogin forced-commands-only

此设置不仅会限制 root 用户可以通过 SSH 执行的命令,还会禁用密码的使用,强制为 root 帐户使用公钥认证。

一个限制性稍小的替代方案是允许 root 执行任何命令,但通过强制使用公钥认证来使暴力破解攻击变得不可行。对于此选项,请设置

PermitRootLogin prohibit-password

锁定 authorized_keys 文件

警告 锁定此文件仅能防止用户错误和某些幼稚的面对面攻击。它**不会**提供任何防止恶意程序或泄露的保护。使用多因素认证、防火墙和纵深防御策略来防止泄露。

如果出于任何原因,您认为相关用户不应能够添加或更改现有密钥,则可以阻止他们操作该文件。

在服务器上,使 authorized_keys 文件对用户只读,并拒绝所有其他权限

$ chmod 400 ~/.ssh/authorized_keys

为了防止用户简单地更改权限,请对 authorized_keys 文件设置不可变位。为了防止用户重命名 ~/.ssh 目录并创建新的 ~/.ssh 目录和 authorized_keys 文件,请也对 ~/.ssh 目录设置不可变位。要添加或删除密钥,您将需要暂时删除 authorized_keys 的不可变位并使其可写。

提示 建议通过例如 auditd 来记录对任何 authorized_keys 文件的更改。

SSH 证书

虽然常见的 SSH 密钥和手动指纹验证对于由单个管理员管理的一小组主机来说可能易于使用,但这种身份验证方法根本无法扩展。当需要多个用户通过 SSH 访问许多服务器时,手动验证每个主机的 ssh 公钥指纹几乎不可能安全可靠地完成。

解决方案是使用 SSH 证书,它通过信任链提供公钥身份的自动验证,该信任链比 SSH 默认的“首次使用时信任”方法具有更好的可扩展性。SSH 证书基本上就是普通的公钥 SSH 密钥,但带有来自受信任证书颁发机构的额外签名,该签名验证密钥身份。

为您的基础设施创建主机证书颁发机构密钥
$ ssh-keygen -t ed25519 -f ~/.ssh/ca_host_key -C 'Host certificate authority for *.example.com'

私有证书颁发机构密钥应安全存储,最好存储在智能卡或硬件令牌上,以防止密钥被提取,例如 NitrokeyYubiKey

签名服务器的公用 SSH 主机密钥

将公用服务器密钥复制到包含私有证书颁发机构密钥的本地系统以对其进行签名

$ ssh-keygen -h -s ~/.ssh/ca_key -I certLabel -n server01.example.com ./ssh_host_ed25519_key.pub
移动新证书并配置 sshd 使用它

生成的证书 ssh_host_ed25519_key-cert.pub 应复制到服务器的 /etc/ssh/

/etc/ssh/sshd_config.d/20-ed25519_key.conf
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
配置所有客户端信任证书颁发机构
~/.ssh/known_hosts
@cert-authority  *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKL8gB/pjuff005YNazwMCqJpgsXAbQ3r4VStd/CRKwU Host certificate authority for *.example.com
警告 当服务器未提供用于识别的证书时,默认情况下将使用公钥认证作为回退。
SSH 用户证书

根据用户数量和部署方法,SSH 用户密钥也可以与证书一起使用。对于拥有众多 SSH 用户的组织,强烈建议安全地管理用户密钥部署。

用户证书的部署与服务器身份的部署基本相同。您可以在 Wikibooks:OpenSSH/Cookbook/Certificate-based Authentication 中找到更多详细信息和说明。

证书部署自动化

许多开源工具可以提供 SSH 证书的自动化部署。流行的例子是

SSHFP 记录

Secure Shell fingerprint record (SSHFP) 是域名系统中的一个可选资源记录,它将 SSH 密钥与主机名关联起来。它可用于通过 DNSSEC 验证公共服务器上的 SSH 指纹,而不是部署受信任的 CA 证书,这使得即使是未管理的客户端也能验证密钥指纹的有效性。

生成记录条目

要生成存储在 DNS 记录中的所需十六进制密钥指纹,请在目标服务器上创建哈希。

$ ssh-keygen -r host.example.com

这将读取指定域的所有可用 SSH 密钥,并输出有效的 SSHFP 记录,然后可以将其存储在受影响域的 DNS 条目中。

客户端配置

为了自动检索并信任存储为 SSHFP 记录的 SSH 密钥指纹,请将以下内容添加到您的 ssh 客户端配置文件中

~/.ssh/config
# global options
Match all
    VerifyHostKeyDNS yes

如果目标主机具有有效的 SSHFP 记录,并且该记录已通过有效的 DNSSEC 签名进行了验证,那么该指纹将自动接受,而不会提示用户验证主机身份。如果 DNS 记录未通过 DNSSEC 验证,则将像往常一样提示用户验证指纹。

注意 要使 SSHFP 记录正常工作,请确保在 /etc/resolv.conf 中设置了 trust-ad 选项。
生成 SSHFP 记录

要确定特定域的 SSH 指纹,请使用 ssh-keyscan 以有效的 DNS 记录格式检索 ssh 指纹。(请注意,默认情况下,每个可用密钥类型的指纹都以 SHA1 和 SHA256 提供。)

$ ssh-keyscan -D github.com
; github.com:22 SSH-2.0-babeld-57ca1323
; github.com:22 SSH-2.0-babeld-57ca1323
github.com IN SSHFP 1 1 6f4c60375018bae0918e37d9162bc15ba40e6365
github.com IN SSHFP 1 2 b8d895ced92c0ac0e171cd2ef5ef01ba3417554a4a6480d331ccc2be3ded0f6b
; github.com:22 SSH-2.0-babeld-57ca1323
github.com IN SSHFP 3 1 3358ab5dd3e306c461c840f7487e93b697e30600
github.com IN SSHFP 3 2 a764003173480b54c96167883adb6b55cf7cfd1d415055aedff2e2c8a8147d03
; github.com:22 SSH-2.0-babeld-57ca1323
github.com IN SSHFP 4 1 e9619e2ed56c2f2a71729db80bacc2ce9ccce8d4
github.com IN SSHFP 4 2 f83898df0bef57a4ee24985ba598ac17fccb0c0d333cc4af1dd92be14bc23aa5
; github.com:22 SSH-2.0-babeld-57ca1323

由于 SSHFP 记录将密钥指纹存储为十六进制值,而 SSH 指纹的常见输出是基于 SHA256 哈希的 base64 编码,因此有必要将记录转换回 base64 格式,以便与 known_hosts 文件或其他通常将指纹存储为 SHA256 的文档中的值进行比较。

$ echo "SSHFP-fingerprint" | xxd -r -p | base64

例如,使用 ed25519 密钥类型的 sha256 指纹的十六进制值用于 github.com

$ echo "f83898df0bef57a4ee24985ba598ac17fccb0c0d333cc4af1dd92be14bc23aa5" | xxd -r -p | base64
+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU=

与 known_hosts 条目进行比较

$ ssh-keygen -l -f ~/.ssh/known_hosts
手动从 DNS 检索 SSHFP 记录
$ dig SSHFP targetdomain.tld +short

技巧与提示

加密 SOCKS 隧道

这对于连接到不安全无线连接的笔记本电脑用户很有用。唯一的要求是 SSH 服务器运行在一个相对安全的位置,例如您的家中或工作场所。使用动态 DNS 服务(如 DynDNS)可能很有用,这样您就不必记住您的 IP 地址。

启动连接

$ ssh -TND 4711 user@host

N 标志禁用交互式提示,D 标志指定本地监听端口(您可以选择任何端口号)。T 标志禁用伪终端分配。

也许可以添加 verbose (-v) 标志,以验证连接。

配置您的浏览器(或其他程序)

上述步骤仅与网络浏览器或其他程序结合使用才有用。由于 SSH 支持 SOCKS v4 和 SOCKS v5,因此您可以使用任一版本。

  • 对于 Firefox
  1. 转到首选项 > 常规 > 网络设置,然后单击设置...
  2. 在新窗口中,选中手动代理配置选项,并在SOCKS 主机文本字段中输入 localhost,在端口文本字段中输入端口号(上面示例中为 4711)。
  3. 重新启动 Firefox。
注意 Firefox 不会自动通过 SOCKS 隧道进行 DNS 请求。可以通过选中使用 SOCKS vN (v4 或 v5) 时代理 DNS 来缓解此问题。
  • 对于 Chromium
  1. 您可以将 SOCKS 设置为环境变量或命令行选项。例如,将以下函数之一添加到您的 .bashrc
    secure_chromium() {
        local port=4711
        export SOCKS_SERVER=localhost:$port
        export SOCKS_VERSION=5
        (chromium > /dev/null 2>&1 &)
    }
    
    secure_chromium() {
        local port=4711
        (chromium --proxy-server="socks://:$port" > /dev/null 2>&1 &)
    }
    
  2. 打开一个终端并运行
    $ secure_chromium

设置本地 TUN 接口

这最初更复杂,但结果是您不必手动配置每个应用程序来使用 SOCKS 代理。这需要设置一个本地 TUN 接口并通过它路由流量。

请参阅 VPN over SSH#设置 badvpn 和隧道接口

X11 转发

X11 转发是一种机制,允许在远程系统上运行的 X11 程序的图形界面显示在本地客户端机器上。对于 X11 转发,远程主机不需要安装完整的 X11 系统;但是,它至少需要安装 xauthxauth 是一个实用程序,它维护服务器和客户端用于 X11 会话身份验证的 Xauthority 配置(来源)。

警告 X11 转发具有重要的安全含义,应至少通过阅读 ssh(1)sshd_config(5)ssh_config(5) 手册页的相关部分来认识到这些含义。另请参阅 此 StackExchange 问题

设置

远程
  • 安装 xorg-xauth
  • /etc/ssh/sshd_config
    • X11Forwarding 设置为 yes
    • 验证 AllowTcpForwardingX11UseLocalhost 选项是否设置为 yes,并且 X11DisplayOffset 是否设置为 10(如果没有更改,这些是默认值,请参阅 sshd_config(5)
  • 然后重启sshd 守护进程
客户端
  • 安装 xorg-xauth
  • 通过在命令行上指定 -X 开关进行机会性连接,或在 客户端配置 中将 ForwardX11 设置为 yes 来启用 ForwardX11 选项。
提示 如果 GUI 绘制效果不佳或收到错误,您可以启用 ForwardX11Trusted 选项(命令行上的 -Y 开关);这将阻止 X11 转发受到 X11 SECURITY 扩展 的控制。如果您这样做,请确保您已阅读本节开头的 警告

用法

正常登录到远程机器,如果客户端配置文件中未启用 ForwardX11,则指定 -X 开关

$ ssh -X user@host

如果运行图形应用程序时收到错误,请尝试使用 ForwardX11Trusted 代替

$ ssh -Y user@host

鉴于输出 X11 forwarding request failed,请重新设置您的远程机器。一旦 X11 转发请求成功,您就可以在远程服务器上启动任何 X 程序,它将被转发到您的本地会话

$ xclock

包含 Can't open display 的错误输出表示 DISPLAY 设置不正确。

请小心使用某些应用程序,因为它们会检查本地机器上是否正在运行实例。 Firefox 是一个例子:关闭正在运行的 Firefox 实例,或者使用以下启动参数在本地机器上启动远程实例

$ firefox --no-remote

当您连接时收到 "X11 forwarding request failed on channel 0"(并且服务器 /var/log/errors.log 显示 "Failed to allocate internet-domain X11 display socket"),请确保已安装 xorg-xauth 包。如果安装不起作用,请尝试

  • 服务器sshd_config 中启用 AddressFamily any 选项,或者
  • 服务器sshd_config 中将 AddressFamily 选项设置为 inet。

将其设置为 inet 可能会解决 IPv4 上 Ubuntu 客户端的问题。

要在 SSH 服务器上以另一个用户的身份运行 X 应用程序,您需要将来自 SSH 登录用户的 xauth list 的身份验证行 xauth add

提示 此处一些 有用的 链接 用于排查 X11 Forwarding 问题。

转发其他端口

除了 SSH 内置的 X11 支持外,它还可以通过本地转发或远程转发安全地隧道传输任何 TCP 连接。

本地转发在本地机器上打开一个端口,连接到该端口的请求将被转发到远程主机,然后从那里转发到指定的目标。通常,转发目标与远程主机相同,从而提供安全的 shell 和例如安全的 VNC 连接到同一台机器。本地转发通过 -L 开关完成,并附带形式为 <隧道端口>:<目标地址>:<目标端口> 的转发规范。

因此

$ ssh -L 1000:mail.google.com:25 192.168.0.100

将使用 SSH 登录到 192.168.0.100 并在此打开一个 shell,还将创建一个从本地机器的 TCP 端口 1000 到 mail.google.com 的 25 端口的隧道。建立连接后,连接到 localhost:1000 将连接到 Gmail SMTP 端口。对 Google 而言,任何此类连接(但不是连接上传输的数据)都将显示为源自 192.168.0.100,并且数据将在本地机器和 192.168.0.100 之间是安全的,但在 192.168.0.100 和 Google 之间不是,除非采取其他措施。

类似地

$ ssh -L 2000:192.168.0.100:6001 192.168.0.100

将允许连接到 localhost:2000,这些连接将被透明地发送到远程主机上的 6001 端口。前面的示例对于使用 vncserver 实用程序的 VNC 连接很有用——该实用程序是 tightvnc 包的一部分——虽然非常有用,但它明确表示缺乏安全性。

远程转发允许远程主机通过 SSH 隧道和本地机器连接到任意主机,提供了本地转发的功能性反转,并且对于例如远程主机由于防火墙限制而连接受限的情况很有用。它通过 -R 开关启用,并附带形式为 <隧道端口>:<目标地址>:<目标端口> 的转发规范。

因此

$ ssh -R 3000:irc.libera.chat:6667 192.168.0.200

将启动 192.168.0.200 上的 shell,并且从 192.168.0.200 连接到自身的 3000 端口(远程主机的 localhost:3000)将通过隧道发送到本地机器,然后发送到 irc.libera.chat 的 6667 端口,因此,在此示例中,即使端口 6667 通常对其被阻止,也可以使用远程主机上的 IRC 程序。

本地和远程转发都可以用来提供一个安全的“网关”,允许其他计算机利用 SSH 隧道,而无需实际运行 SSH 或 SSH 守护进程,方法是在转发规范的一部分中提供隧道开始的绑定地址,例如 <隧道地址>:<隧道端口>:<目标地址>:<目标端口><隧道地址> 可以是隧道开始处的机器上的任何地址。localhost 地址允许通过 localhost 或回环接口进行连接,而空地址或 * 允许通过任何接口进行连接。默认情况下,转发仅限于从隧道“开始”处的机器进行的连接,即 <隧道地址> 设置为 localhost。本地转发不需要额外的配置;但是,远程转发受到远程服务器的 SSH 守护进程配置的限制。有关远程转发和本地转发的更多信息,请参阅 sshd_config(5) 中的 GatewayPorts 选项和 ssh(1) 中的 -L address 选项。

跳转主机

在某些场景下,可能无法直接连接到目标 SSH 守护进程,并且需要使用跳转服务器(或 堡垒服务器)。因此,我们尝试连接两个或多个 SSH 隧道,并假设您的本地密钥已获得链中每台服务器的授权。这可以通过 SSH 代理转发 (-A) 和伪终端分配 (-t) 来实现,它会以以下语法转发您的本地密钥

$ ssh -A -t -l user1 bastion1 \
  ssh -A -t -l user2 intermediate2 \
  ssh -A -t -l user3 target

这可以使用 ProxyCommand 选项进行自动化

$ ssh -o ProxyCommand="ssh -W %h:%p bastion.example.org" targetserver.example.org

一种更简单、更安全的方法是使用 ProxyJump 选项和 -J 标志

$ ssh -J user1@bastion1,user2@intermediate2 user3@target

-J 指令中的多个主机可以用逗号分隔;它们将按列出的顺序连接。user...@ 部分不是必需的,但可以使用。-J 的主机规范使用 ssh 配置文件,因此可以根据需要在此处设置特定于主机的选项。

ProxyCommand 和 ProxyJump 选项之间的主要区别在于后者不需要在 jumphost 上运行 shell。因此,这也意味着 jumpserver 不需要访问用户的登录凭据或 SSH 代理转发。使用 ProxyJump 选项,ssh 客户端直接通过 jumpserver 连接到目标服务器,从而在客户端和目标服务器之间建立端到端加密通道。

配置文件中 -J 标志的等效项是 ProxyJump 选项;有关详细信息,请参阅 ssh_config(5)

通过中继器反向 SSH

本文或本节需要在语言、wiki 语法或风格方面进行改进。请参阅 Help:Style 获取参考。

原因: SSH 隧道化的想法很经典,所以需要一些参考资料进行详细解释。例如 [2],其中包含其他场景。(在 Talk:OpenSSH 中讨论)

其思想是客户端通过另一个中继器连接到服务器,而服务器使用反向 SSH 隧道连接到同一个中继器。当服务器位于 NAT 后面且中继器是用户可以访问的、可公开访问的 SSH 服务器并用作代理时,这很有用。因此,前提条件是客户端的密钥已获得中继器和服务器的授权,并且服务器也需要对中继器进行授权才能进行反向 SSH 连接。

以下配置示例假定 user1 是客户端上使用的用户帐户,user2 是中继器上使用的用户,user3 是服务器上使用的用户。首先,假设我们将使用端口 2222,服务器需要使用以下命令建立反向隧道

$ ssh -R 2222:localhost:22 -N user2@relay

这也可以通过启动脚本、systemd 服务、autosshsidedoorAUR 进行自动化。

在客户端,连接使用以下命令建立

$ ssh -t user2@relay ssh user3@localhost -p 2222
注意 ssh user3@relay -p 2222 需要您在转发服务器的防火墙中打开此端口,并允许来自其他地址的连接到此端口。

建立到反向隧道的连接的远程命令也可以在转发器的 authorized_keys 文件中定义,方法是包含 command 字段,如下所示

~/.ssh/authorized_keys
command="ssh user3@localhost -p 2222" ssh-ed25519 KEY2 user1@client

在这种情况下,连接使用以下命令建立

$ ssh user2@relay

或者,您可以将一个条目添加到您的 ssh 配置文件中,该条目指定 RemoteCommandRequestTTY

~/.ssh/config
Host jump-destination
    Hostname relay
    User user2
    RemoteCommand ssh user3@localhost -p 2222
    RequestTTY yes

这将简化连接为

$ ssh jump-destination
注意 客户端终端中的 SCP 自动补全功能将无法正常工作,甚至 SCP 传输本身在某些配置下也无法正常工作。

多路复用

SSH 守护进程通常监听端口 22。然而,许多公共互联网热点阻止所有非标准 HTTP/S 端口(分别为 80 和 443)的流量,从而有效地阻止 SSH 连接,这是一种常见的做法。最直接的解决方案是让 sshd 额外监听其中一个白名单端口

/etc/ssh/sshd_config
Port 22
Port 443

但是,端口 443 很可能已被 Web 服务器用于提供 HTTPS 内容,在这种情况下,可以使用多路复用器,如 sslh,它监听多路复用端口并可以智能地将数据包转发到多个服务。

加速 SSH

有几个 客户端配置 选项可以全局或针对特定主机加速连接。有关这些选项的完整说明,请参阅 ssh_config(5)

  • 使用更快的加密算法:在具有 AESNI 指令的现代 CPU 上,aes128-gcm@openssh.comaes256-gcm@openssh.com 应该比 openssh 的默认首选加密算法(通常是 chacha20-poly1305@openssh.com)提供更好的性能。可以使用 -c 标志选择加密算法。为了获得永久效果,请将 Ciphers 选项放在您的 ~/.ssh/config 文件中,并按新的首选顺序排列加密算法,例如
    Ciphers aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
  • 启用或禁用压缩:压缩可以提高慢速连接的速度;可以使用 Compression yes 选项或 -C 标志启用它。然而,使用的压缩算法是相对较慢的 gzip(1),它在快速网络上会成为瓶颈。为了加快连接速度,应该在本地或快速网络上使用 Compression no 选项。
  • 连接共享:您可以使用这些选项使所有到同一主机的会话共享一个连接
    ControlMaster auto
    ControlPersist yes
    ControlPath ~/.ssh/sockets/socket-%r@%h:%p
    
其中 ~/.ssh/sockets 可以是任何非其他用户可写目录。
  • ControlPersist 指定在初始客户端连接关闭后,主进程将在后台等待新客户端多长时间。可能的值是
    • no,在最后一个客户端断开连接后立即关闭连接;
    • 以秒为单位的时间;
    • yes,永远等待,连接将永远不会自动关闭。
  • 通过绕过 IPv6 查找,可以使用 AddressFamily inet 选项或 -4 标志缩短登录时间。
  • 最后,如果您打算使用 SSH 进行 SFTP 或 SCP,高性能 SSH/SCP 可以通过动态增加 SSH 缓冲区大小来显著提高吞吐量。安装 openssh-hpnAUR 包可以使用此增强功能的 OpenSSH 修补版本。

使用 SSHFS 挂载远程文件系统

请参阅 SSHFS 文章将 SSH 可访问的远程系统挂载到本地目录,这样您就可以使用任何工具对挂载的文件执行任何操作(复制、重命名、使用 vim 编辑等)。sshfs 通常比 shfs 更受青睐,后者自 2004 年以来未更新。

保活

默认情况下,如果 SSH 会话空闲一段时间,它会自动注销。为了保持会话在线,如果一段时间内未收到数据,客户端可以向服务器发送保活信号,或者服务器可以在一段时间内未收到客户端消息时对称地发送消息。

  • 在 **服务器** 端,ClientAliveInterval 设置超时(以秒为单位),在此之后,如果未收到来自客户端的数据,sshd 将发送一个响应请求。默认值为 0,不发送消息。例如,要每 60 秒从客户端请求一次响应,请在您的 服务器配置 中设置 ClientAliveInterval 60 选项。另请参阅 ClientAliveCountMaxTCPKeepAlive 选项。
  • 在 **客户端** 端,ServerAliveInterval 控制从客户端向服务器发送响应请求的间隔。例如,要每 120 秒从服务器请求一次响应,请将 ServerAliveInterval 120 选项添加到您的 客户端配置。另请参阅 ServerAliveCountMaxTCPKeepAlive 选项。
注意 为确保会话保持活动状态,只需客户端或服务器发送其中之一的保活请求即可。如果您同时控制服务器和客户端,一个合理的选择是仅为需要持久会话的客户端配置正值的 ServerAliveInterval,并将其他客户端和服务器保留为默认配置。

使用 systemd 自动重启 SSH 隧道

systemd 可以在启动/登录时自动启动 SSH 连接,并在它们失败时 **和** 重启它们。这使其成为维护 SSH 隧道的有用工具。

以下服务可以在登录时使用您的 ssh 配置文件 中的连接设置启动 SSH 隧道。如果连接因任何原因关闭,它将等待 10 秒钟后再重新启动

~/.config/systemd/user/tunnel.service
[Unit]
Description=SSH tunnel to myserver

[Service]
Type=simple
Restart=always
RestartSec=10
ExecStart=/usr/bin/ssh -F %h/.ssh/config -N myserver

然后 启用启动 用户单元。有关如何防止隧道超时,请参阅 #Keep alive。如果您希望在启动时启动隧道,您可能需要将 单元 重写为系统服务。

Autossh - 自动重启 SSH 会话和隧道

当会话或隧道无法保持活动时,例如由于网络状况不佳导致客户端断开连接,您可以使用 autossh 自动重启它们。

使用示例

$ autossh -M 0 -o "ServerAliveInterval 45" -o "ServerAliveCountMax 2" username@example.com

与 SSHFS 结合使用

$ sshfs -o reconnect,compression=yes,transform_symlinks,ServerAliveInterval=45,ServerAliveCountMax=2,ssh_command='autossh -M 0' username@example.com: /mnt/example 

通过 代理设置 设置的 SOCKS 代理连接

$ autossh -M 0 -o "ServerAliveInterval 45" -o "ServerAliveCountMax 2" -NCD 8080 username@example.com 

使用 -f 选项,autossh 可以作为后台进程运行。然而,以这种方式运行意味着密码无法交互输入。

会话将在您在会话中键入 exit 时结束,或者 autossh 进程收到 SIGTERM、SIGINT 或 SIGKILL 信号时结束。

通过 systemd 自动运行 autossh 以启动

如果您想自动启动 autossh,可以创建一个 systemd 单元文件

/etc/systemd/system/autossh.service
[Unit]
Description=AutoSSH service for port 2222
After=network.target

[Service]
Environment="AUTOSSH_GATETIME=0"
ExecStart=/usr/bin/autossh -M 0 -NL 2222:localhost:2222 -o TCPKeepAlive=yes foo@bar.com

[Install]
WantedBy=multi-user.target

这里的 AUTOSSH_GATETIME=0 是一个环境变量,指定在 autossh 认为连接成功之前 ssh 必须运行多长时间,将其设置为 0 也会使 autossh 忽略 ssh 的第一次运行失败。这在使用 autossh 启动时可能很有用。其他环境变量可以在 autossh(1) 中找到。当然,您可以根据需要使此单元更复杂(有关详细信息,请参阅 systemd 文档),并且显然您可以使用自己的 autossh 选项,但请注意 -f 暗示 AUTOSSH_GATETIME=0 在 systemd 中不起作用。

记得之后 启动 和/或 启用 服务。

您可能还需要禁用 ControlMaster

ExecStart=/usr/bin/autossh -M 0 -o ControlMaster=no -NL 2222:localhost:2222 -o TCPKeepAlive=yes foo@bar.com
提示 维护多个 autossh 进程以保持多个隧道处于活动状态也很容易。只需创建具有不同名称的多个服务文件。

SSH 守护进程失败时的备用服务

对于完全依赖 SSH 的远程或无头服务器,SSH 守护进程启动失败(例如,在系统升级后)可能会阻止管理访问。 systemd 通过 OnFailure 选项提供了一个简单的解决方案。

假设服务器运行 sshd 并且 telnet 是首选的故障安全替代方案。创建一个如下文件。 **不要** 启用 telnet.socket

/etc/systemd/system/sshd.service.d/override.conf
[Unit]
OnFailure=telnet.socket

就是这样。当 sshd 运行时,telnet 不可用。如果 sshd 启动失败,则可以打开 telnet 会话进行恢复。

基于主机的终端背景色

为了更好地区分您在不同主机上时,您可以设置 基于主机类型的不同背景色

此解决方案有效,但并非普遍(仅限 ZSH)。

网络特定配置

您可以使用 Match exec 的配置来根据您连接的网络使用特定的主机配置。

例如,在使用 nmcli(1) 时,并且连接(手动或通过 DHCP)配置为使用搜索域

~/.ssh/config
Match exec "nmcli | grep domains: | grep example.com"
  CanonicalDomains example.com
  # Should you use a different username on this network
  #User username
  # Use a different known_hosts file (for private network or synchronisation)
  #UserKnownHostsFile <network>_known_hosts

另一个 Match host ... exec "..." 的例子:考虑连接到 internal.example.com 需要一个堡垒/代理(通过 ProxyJump),除非您已经通过 VPN 连接。片段 !exec "host internal.example.com" 仅在 internal.example.com 无法通过 DNS 解析时适用。各种替代方案在 [3] 中讨论。

~/.ssh/config
Match host internal.example.com !exec "host internal.example.com"
  ProxyJump bastion.example.com
Host internal.example.com
  User foobar

私有网络主机密钥验证

由于不同网络上的不同服务器可能共享相同的私有 IP 地址,您可能希望以不同的方式处理它们。

本文或本章节的准确性存在争议。

原因: 最佳解决方案在实践中不需要使用其他东西的警告。(在 Talk:OpenSSH 中讨论)

最佳解决方案是使用 #Network specific configuration 根据您所在的网络使用不同的 UserKnownHostsFile。第二种解决方案,当您在新/原型网络上工作时作为默认值使用最佳,是简单地忽略私有网络的密钥

~/.ssh/config
Host 10.* 192.168.*.* 172.31.* 172.30.* 172.2?.* 172.1?.*
    # Disable HostKey verification
    # Trust HostKey automatically
    StrictHostKeyChecking no
    # Do not save the HostKey
    UserKnownHostsFile=/dev/null
    # Do not display: "Warning: Permanently Added ..."
    LogLevel Error

本文或本章节的准确性存在争议。

原因: 即使您使用主机名访问服务器,known_hosts 文件也会记录 IP 地址。(在 Talk:OpenSSH 中讨论)
警告 在生产环境中,请确保使用主机名访问主机和/或使用特定于网络的 known_hosts 文件。

登录时运行命令

如果您使用的是交互式会话,有多种方法可以在登录时执行命令

  • 使用远程主机上的 authorized_keys 文件(参见 sshd(8) § AUTHORIZED_KEYS FILE FORMAT
  • 如果服务器启用了 PermitUserRC 选项,则使用远程主机上的 ~/.ssh/rc
  • 使用远程主机上的 shell 配置文件,例如 .bashrc

代理转发

SSH 代理转发允许您在使用服务器连接时使用本地密钥。 建议 仅为选定的主机启用代理转发。

~/.ssh/config
Host myserver.com
    ForwardAgent yes

接下来,配置一个 SSH 代理 并使用 ssh-add 添加您的本地密钥。

如果您现在连接到远程服务器,您将能够使用本地密钥连接到其他服务。

生成新密钥

新的服务器私钥可以通过以下方式生成

  1. 删除所有密钥,例如
    # rm /etc/ssh/ssh_host_*_key*
  2. 重启 sshdgenkeys.service 或以 root 身份运行 ssh-keygen -A

一个更优雅的替代方法是过渡。首先,生成新的主机密钥

# ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key.new
# ssh-keygen -t ecdsa -b 521 -f /etc/ssh/ssh_host_ecdsa_key.new
# ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key.new

将旧密钥和新密钥都添加到 /etc/ssh/sshd_config.d/10-hostkeys.conf

HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key.new
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_ed25519_key.new
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_rsa_key.new

重启 sshd.service

客户端将在下次连接到服务器时学习到主机的新密钥指纹,并将它们添加到 ~/.ssh/known-hosts

一段时间后,可以从 /etc/ssh/sshd_config.d/10-hostkeys.conf 中删除旧密钥,只留下新密钥

HostKey /etc/ssh/ssh_host_ecdsa_key.new
HostKey /etc/ssh/ssh_host_ed25519_key.new
HostKey /etc/ssh/ssh_host_rsa_key.new

重启 sshd.service,然后物理删除旧密钥

# rm /etc/ssh/ssh_host_{ecdsa,ed25519,rsa}_key

同样,客户端将自动调整 ~/.ssh/known-hosts 以删除对应于旧密钥的指纹。

以非特权用户运行 sshd

您可能希望在容器中或用于测试等目的以非特权用户运行 sshd

由于非特权用户无法读取 /etc/ssh 中的主机密钥,因此必须生成新的主机密钥

$ ssh-keygen -q -N "" -t rsa -b 4096 -f /path/to/host/keys/ssh_host_rsa_key
$ ssh-keygen -q -N "" -t ecdsa -f /path/to/host/keys/ssh_host_ecdsa_key
$ ssh-keygen -q -N "" -t ed25519 -f /path/to/host/keys/ssh_host_ed25519_key

创建一个 sshd_config 文件。下面的示例使用高于 1024 的端口,提供新的主机密钥路径并禁用 PAM

/path/to/sshd_config
Port 2022
HostKey /path/to/host/keys/ssh_host_rsa_key
HostKey /path/to/host/keys/ssh_host_ecdsa_key
HostKey /path/to/host/keys/ssh/ssh_host_ed25519_key
UsePAM no

使用创建的配置运行 sshd-D 标志禁用守护进程模式,-e 将输出重定向到 stderr 以方便监控。

$ sshd -f /path/to/sshd_config -D -e

故障排除

核对表

在进一步排查之前,请检查这些简单问题。

  1. 配置目录 ~/.ssh,其内容应仅由用户访问(在客户端和服务器上均检查此项),并且用户的主目录应仅由用户写入
    $ chmod go-w ~
    $ chmod 700 ~/.ssh
    $ chmod 600 ~/.ssh/*
    $ chown -R $USER ~/.ssh
    
  2. 检查客户端的公钥(例如 id_ed25519.pub)是否在服务器的 ~/.ssh/authorized_keys 中。
  3. 检查您是否没有通过 服务器配置 中的 AllowUsersAllowGroups 限制 SSH 访问。
  4. 检查用户是否已设置密码。有时新用户尚未登录到服务器的用户没有密码。
  5. LogLevel DEBUG 附加/etc/ssh/sshd_config
  6. 以 root 身份运行 journalctl -xe 以获取可能的(错误)消息。
  7. 重启 sshd 并从客户端和服务器注销/登录。

连接被拒绝或超时问题

端口转发

如果您在 NAT 模式/路由器后面(除非您在使用 VPS 或公有 IP 地址的主机上,否则很可能是这样),请确保您的路由器正在将传入的 ssh 连接转发到您的机器。使用 ip addr 找到服务器的内部 IP 地址,并设置您的路由器将 SSH 端口上的 TCP 转发到该 IP。 portforward.com 可以帮助您。

SSH 是否正在运行并监听?

ss 工具会显示监听 TCP 端口的所有进程,命令如下

$ ss --tcp --listening

如果上述命令没有显示系统正在监听 ssh 端口,那么 SSH 没有运行:检查 日志 中的错误等。

是否有防火墙规则阻止连接?

Iptables 可能会阻止端口 22 上的连接。使用以下命令检查

# iptables -nvL

并查找可能在 INPUT 链中丢弃数据包的规则。然后,如果需要,使用类似以下的命令解除阻塞端口

# iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT

有关配置防火墙的更多帮助,请参阅 firewalls

流量甚至到达您的计算机了吗?

在遇到问题的计算机上启动流量转储

# tcpdump -lnn -i any port ssh and tcp-syn

这将显示一些基本信息,然后等待任何匹配的流量发生后再显示。现在尝试您的连接。如果您在尝试连接时没有看到任何输出,那么您的计算机之外的某个东西正在阻止流量(例如,硬件防火墙、NAT 路由器等)。

您的 ISP 或第三方阻止了默认端口?

注意 如果您 **知道** 您没有运行任何防火墙,并且知道您已将路由器配置为 DMZ 或已将端口转发到您的计算机但仍然不起作用,请尝试此步骤。在这里,您将找到诊断步骤和可能的解决方案。

在某些情况下,您的 ISP 可能会阻止默认端口(SSH 端口 22),因此您尝试的任何操作(打开端口、加固堆栈、防御洪水攻击等)都将无济于事。为了确认这一点,请在所有接口(0.0.0.0)上创建一个服务器并远程连接。

如果您收到类似以下的错误消息

ssh: connect to host www.inet.hr port 22: Connection refused

这意味着端口 **没有** 被 ISP 阻止,但服务器在该端口上未运行 SSH(参见 安全即是 the obscurity)。

但是,如果您收到类似以下的错误消息

ssh: connect to host 111.222.333.444 port 22: Operation timed out 

这意味着某些东西正在拒绝您在端口 22 上的 TCP 流量。基本上,该端口是隐蔽的,无论是通过您的防火墙还是第三方干预(如 ISP 阻止和/或拒绝端口 22 上的入站流量)。如果您知道您的计算机上没有运行任何防火墙,并且您知道您的路由器和交换机中没有 Gremlins 在生长,那么您的 ISP 正在阻止流量。

为了双重检查,您可以在服务器上运行 Wireshark 并监听端口 22 上的流量。由于 Wireshark 是一个第 2 层数据包嗅探实用程序,而 TCP/UDP 是第 3 层及以上(参见 IP 网络堆栈),如果您在远程连接时未收到任何内容,那么很可能是第三方正在阻止您到服务器的该端口上的流量。

诊断

安装 tcpdump 或带 wireshark-cli 包的 Wireshark。

对于 tcpdump

# tcpdump -ni interface "port 22"

对于 Wireshark

$ tshark -f "tcp port 22" -i interface

其中 interface 是 WAN 连接的网络接口(请参阅 ip a 进行检查)。如果您在尝试远程连接时没有收到任何数据包,那么您可以非常确定您的 ISP 正在阻止端口 22 上的入站流量。

可能的解决方案

解决方案是使用 ISP 未阻止的其他端口。打开 /etc/ssh/sshd_config 并将文件配置为使用不同的端口。例如,添加

Port 22
Port 1234

还要确保文件中的其他“Port”配置行已被注释掉。仅仅注释掉“Port 22”并放入“Port 1234”将无法解决问题,因为 sshd 将只监听端口 1234。同时使用这两行以在两个端口上运行 SSH 服务器。

重启 服务器 sshd.service,您就差不多完成了。您仍然需要配置您的客户端以使用另一个端口而不是默认端口。有许多解决方案可以解决这个问题,但让我们在这里介绍其中两个。

读取套接字失败:连接被对端重置

较新版本的 OpenSSH 有时在连接到旧版 SSH 服务器时会遇到上述错误。可以通过为该主机设置各种客户端选项来解决此问题。有关以下选项的更多信息,请参阅 ssh_config(5)

问题可能出在 ecdsa-sha2-nistp*-cert-v01@openssh 椭圆曲线主机密钥算法上。可以通过将 HostKeyAlgorithms 设置为不包含这些算法的列表来禁用它们。在客户端,也可以通过在 HostKeyAlgorithms 列表前加上 - 来设置客户端想要使用的 HostKeyAlgorithms,以从默认集中移除指定的算法(包括通配符)(请参阅 ssh_config(5))。您可以使用 ssh -v server_to_connect_to 检查实际使用的主机密钥算法,查看包含 kex: host key algorithm: 的行。

如果上述方法无效,可能是密码列表太长。将 Ciphers 选项设置为一个较短的列表(少于 80 个字符应该足够了)。同样,您也可以尝试缩短 MACs 的列表。

另请参阅 OpenSSH 错误论坛上的讨论

“[\u4f60\u7684 shell]: No such file or directory” / ssh_exchange_identification 问题

这的一种可能原因在于,某些 SSH 客户端需要从 $SHELL 中找到一个绝对路径(例如 whereis -b [your shell] 返回的路径),即使 shell 的二进制文件位于 $PATH 条目中的某个位置。

“Terminal unknown” 或 “Error opening terminal” 错误消息

如果在登录时收到上述错误,则表示服务器不认识您的终端。nano 等 ncurses 应用程序可能会因 Error opening terminal 消息而失败。

正确的解决方案是在服务器上安装客户端终端的 terminfo 文件。这会告诉服务器上的控制台程序如何与您的终端正确交互。您可以使用 infocmp 获取当前 terminfo 的信息,然后找出哪个软件包拥有它

如果无法正常安装,您可以将 terminfo 复制到服务器上的主目录中。

$ ssh myserver mkdir -p  ~/.terminfo/${TERM:0:1}
$ scp /usr/share/terminfo/${TERM:0:1}/$TERM myserver:~/.terminfo/${TERM:0:1}/

登录服务器并退出后,问题应该会得到解决。

TERM 技巧

注意 这只应作为最后的手段使用。

或者,您也可以简单地在服务器上的环境变量中设置 TERM=xterm(例如,在 .bash_profile 中)。这将消除错误消息,并允许 ncurses 应用程序再次运行,但您可能会遇到奇怪的行为和图形显示问题,除非您的终端的控制序列与 xterm 的完全匹配。

Connection closed by x.x.x.x [preauth]

如果在 sshd 日志中看到此错误,请确保已设置了有效的 HostKey

/etc/ssh/sshd_config
HostKey /etc/ssh/ssh_host_ed25519_key

subsystem request failed

OpenSSH 8.8 起,scp 使用 SFTP 作为数据传输的默认协议,通过请求名为 sftp 的子系统。如果您以详细模式运行 scpscp -v),则可以确定客户端正在使用哪个子系统(例如,Sending subsystem: <subsystem-name>)。subsystem request failed on channel 0 等错误可以通过配置服务器的 Subsystem 设置来修复:sshd_config(5) § Subsystem。服务器配置应类似于下面的示例。

/etc/ssh/sshd_config
...
Subsystem subsystem-name /path/to/subsystem-executable
...

id_dsa refused

OpenSSH 7.0 因安全原因弃用了 DSA 公钥,而 OpenSSH 9.8 默认情况下不包含对 DSA 密钥的支持。2025 年的第一个 OpenSSH 版本将完全移除 DSA 支持。目前,如果您绝对需要使用它们,则需要重新编译 openssh,同时向 configure 传递 --enable-dsa-keys[4]

OpenSSH 7.0 未找到匹配的密钥交换方法

OpenSSH 7.0 弃用了 diffie-hellman-group1-sha1 密钥算法,因为它存在安全隐患,并且理论上处于 Logjam 攻击(请参阅 https://www.openssh.com/legacy.html)的范围内。如果特定主机需要该密钥算法,ssh 将会产生类似这样的错误消息。

Unable to negotiate with 127.0.0.1: no matching key exchange method found.
Their offer: diffie-hellman-group1-sha1

解决这些失败的最佳方法是升级/配置服务器以不使用已弃用的算法。如果无法做到这一点,您可以通过客户端选项 KexAlgorithms +diffie-hellman-group1-sha1 强制客户端重新启用该算法。

tmux/screen 会话在断开 SSH 连接时被杀死

本文或本章节的准确性存在争议。

原因: 必须解决此问题,因为默认情况下使用了 logind。(在 Talk:OpenSSH 中讨论)

意译自一个 unix stackexchange 回答

如果您的进程在 SSH 会话结束时被杀死,可能是因为您使用了 socket 激活。当 systemd 检测到 SSH 会话进程已退出时,它会杀死您的进程。在这种情况下,有两个解决方案:

  • 避免使用 socket 激活,迁移到 sshd.service(推荐,请参阅 #Socket activation),或者
  • sshd@.service[Service] 部分设置 KillMode=process

虽然不是必需的,但在 sshd.service 中设置 KillMode=process 可能也有用,因为它避免在服务器停止或重启时杀死 SSH 会话进程及其 screentmux 进程。

请注意,这可能无法与 logind 一起正常工作。

SSH 会话停止响应

SSH 对流控制命令 XONXOFF 做出响应。当您按下 Ctrl+s 时,它会冻结/挂起/停止响应。使用 Ctrl+q 恢复您的会话。

Broken pipe

如果您尝试创建一个连接,导致 packet_write_wait 返回 Broken pipe,您应该在调试模式下重新尝试连接,并查看输出是否以错误结尾。

debug3: send packet: type 1
packet_write_wait: Connection to A.B.C.D port 22: Broken pipe

上面的 send packet 行表明从未收到回复包。因此,这是一个 QoS 问题。为了降低数据包丢失的可能性,请设置 IPQoS

/etc/ssh/ssh_config
Match all
    IPQoS reliability

reliability (0x04) 的服务类型(type-of-service)应能解决问题,同样 0x00throughput (0x08) 也可以。

终止无响应的 SSH 连接

如果客户端会话不再响应,并且无法通过指示运行的程序(例如shell)来终止它,您仍然可以通过依次按下 Enter~. 来终止会话。

~ 是一个伪终端转义字符(请参阅 ssh(1) § ESCAPE CHARACTERS),根据客户端会话的不同,可以多次添加该字符来终止。例如,如果您从 A 连接到 B,然后再从 B 连接到 C,并且 B 到 C 的会话冻结了,您可以通过按下 Enter 并输入 ~~. 来终止它,这将使您留在 B 上一个可用的会话中。

警告:远程主机标识已更改!

如果客户端警告 ssh 服务器的密钥已更改,您应该通过一个经过身份验证的(不一定加密的)通道验证新提供的密钥确实属于服务器操作员。然后使用 ssh-keygen -R $SSH_HOST 命令从 known_hosts 文件中删除旧密钥,并像接受新服务器一样接受新密钥。

连接到没有适当 terminfo 条目的远程主机

当连接到没有您的终端的 terminfo 条目的主机时,例如,当使用未随 ncurses 分发的终端模拟器(例如kittyrxvt-unicode)时,或者连接到具有有限 terminfo 数据库的主机(例如运行 OpenWrt 的系统)时,依赖于 terminfo(5) 的软件将出现各种问题。

一个正确的解决方案是在主机上放置适当的 terminfo 条目。如果不可行,则另一种选择是将 TERM 设置为一个远程主机支持且与终端兼容的值。

自 OpenSSH 8.7 起,可以通过简单的配置片段将自定义的 TERM 环境变量传递给远程主机。

~/.ssh/config
Host example.com
  SetEnv TERM=xterm-256color

通过跳转主机连接失败并显示“bash: No such file or directory”

如果您没有将 SHELL 环境变量设置为一个有效的完整路径(在跳转服务器上),连接将失败,并显示与此类似的错误消息。

bash: No such file or directory
kex_exchange_identification: Connection closed by remote host
Connection closed by UNKNOWN port 65535

您可以通过将 SHELL 设置为一个在跳转服务器上同样有效的 shell 的完整路径名,或者在您的 ~/.ssh/config 文件中为每个服务器设置一个特定的 SHELL 变量来轻松解决此问题。


参见