服务端5月30日 21:21
SSH 协议是如何建立安全连接的?SSH 不是“把密码加密后发给服务器”这么简单。它先通过 TCP 连接到服务端 22 端口,双方确认协议版本和可用算法,再用密钥交换算法协商出临时会话密钥;后续真正传输命令、文件和端口转发数据时,主要靠对称加密保证速度,靠 MAC 或 AEAD 保证完整性。服务器会拿出自己的主机公钥证明“我就是你上次连过的那台机器”,客户端再用密码、公钥或键盘交互完成用户认证。
## 追问
### 为什么 SSH 既用非对称加密又用对称加密?
非对称加密适合身份验证和安全协商,但计算成本高,不适合持续传输大量数据。SSH 用它安全商量密钥,之后切到 AES、ChaCha20 这类对称算法。
### known_hosts 文件有什么用?
known_hosts 记录服务器主机公钥指纹,用来发现你是否连到了同一台服务器。生产环境指纹突然变化,要先确认是否重装、迁移或被中间人劫持。
### 公钥登录比密码登录安全吗?
通常更安全,因为私钥不需要发到网络上,服务端只验证签名结果。边界是私钥文件必须有口令和合理权限。
### SSH 能防住所有中间人攻击吗?
前提是客户端正确校验服务器主机指纹。第一次连接就接受了伪造指纹,SSH 也无法凭空知道对方是假的。
## 写段配置
```config
PasswordAuthentication no
PermitRootLogin no
AllowUsers deploy
PubkeyAuthentication yes
```标签
SSH
SSH(Secure Shell)是一种用于远程登录和其他网络服务的安全协议。它通过加密技术保障数据传输的安全性,防止信息在传输过程中被窃取或篡改。用户可以利用 SSH 在不安全的网络环境中安全地连接到远程服务器,执行命令、管理文件或进行系统维护。SSH 通常使用公钥和私钥进行身份验证,大大提高了安全性,避免了传统明文密码的风险。它已成为 Linux、Unix 等操作系统远程管理的标准工具,被广泛应用于开发、运维和数据传输等场景。SSH 还支持端口转发和安全的隧道功能,能够保护其他协议的数据传输。

服务端5月30日 20:53
SSH 公钥认证为什么更安全?如何正确配置?SSH 公钥认证的核心不是“把公钥当密码”,而是服务器用公钥验证客户端是否持有对应私钥。登录时私钥不会离开本机,客户端只用私钥对服务器给出的挑战做签名;服务器拿 `authorized_keys` 里的公钥验签,通过才放行。相比密码认证,它更抗撞库,也更适合自动化。
## 追问
### Ed25519、RSA、ECDSA 选哪个?
新环境优先 Ed25519,密钥短、速度快、安全边界清晰。需要兼容老系统时再考虑 RSA 4096。
### 私钥还需要密码短语吗?
需要,尤其是笔记本或开发机上的私钥。密码短语能在文件被复制走时多挡一层,配合 ssh-agent 使用也不会频繁输入。
### authorized_keys 有公钥仍失败怎么办?
先查权限,目录或文件可写范围太大时 sshd 会拒绝使用。再用 `ssh -vv` 看客户端是否提供了对应私钥。
### 禁用密码认证前注意什么?
先确认至少有一个普通用户能用密钥登录,并保留当前会话。云主机还要确认控制台救援方式可用。
## 写段命令
```bash
ssh-keygen -t ed25519 -C "deploy@company" -f ~/.ssh/id_ed25519_prod
ssh-copy-id -i ~/.ssh/id_ed25519_prod.pub user@host
chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys
```服务端5月30日 20:53
SSH 端口转发怎么用?-L、-R、-D 分别适合什么场景?SSH 端口转发就是用 SSH 连接临时搭一条加密通道,把某个端口流量转到另一个网络位置。常见参数是 `-L`、`-R`、`-D`:`-L` 是本地端口转发,`-R` 是远程端口转发,`-D` 是动态 SOCKS 代理。先判断流量入口在哪一端,就不容易选错。
## 追问
### -L、-R、-D 一句话怎么区分?
`-L` 是本机开入口访问远端资源;`-R` 是远端开入口访问本地资源;`-D` 是本机开 SOCKS 代理,目标动态决定。
### 为什么建议绑定 127.0.0.1?
绑定本地只允许本机访问,安全边界更小。绑定 0.0.0.0 可能让局域网甚至公网用户连上这个端口。
### 端口被占用怎么办?
先用 `lsof -i :3307` 或 `ss -tlnp` 找占用进程。临时调试可以换端口,不要随便杀不认识的进程。
### 和反向代理有什么区别?
SSH 转发偏临时、按连接建立;Nginx/Caddy 适合长期对外服务,有 TLS、日志、限流和路由能力。
## 写段命令
```bash
ssh -N -L 127.0.0.1:3307:db.internal:3306 user@jump
ssh -N -R 8080:localhost:3000 user@public
ssh -N -D 127.0.0.1:1080 user@jump
```服务端5月30日 20:53
SSH 配置文件哪些选项最影响安全和连接稳定性?SSH 配置文件要分清两类:`~/.ssh/config` 管客户端怎么连,`/etc/ssh/sshd_config` 管服务器允许谁连。客户端重点是减少手输参数、隔离不同环境密钥;服务端重点是关掉高风险入口,如 root 登录、密码登录、无用转发。不要整段复制加固清单,应该按访问路径逐项验证。
## 追问
### ssh_config 和 sshd_config 最大区别是什么?
前者是客户端读的,影响你发起连接时带哪些参数;后者是服务端读的,决定连接是否被允许以及认证策略。
### 为什么不建议关闭 StrictHostKeyChecking?
它会让客户端不再认真校验主机指纹,中间人攻击时更难发现。自动化脚本应预先分发 known_hosts。
### 改端口能提升安全吗?
只能减少低级扫描噪声,不是真正安全。核心仍是禁用密码、限制用户、控制来源 IP 和保留审计日志。
### 修改服务端配置怎样避免锁外面?
保留当前 SSH 会话,另开新终端测试登录。修改前跑 `sudo sshd -t`,通过后再 reload。
## 写段配置
```sshconfig
Host prod
HostName prod.example.com
User deploy
IdentityFile ~/.ssh/id_ed25519_prod
IdentitiesOnly yes
ServerAliveInterval 60
```
```bash
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
```服务端5月30日 20:53
SSH 密钥交换算法是如何生成会话密钥的?SSH 密钥交换负责让客户端和服务器在不可信网络上协商出同一个会话密钥,重点不是传输密钥,而是双方各自算出相同结果。现代 SSH 通常优先 `curve25519-sha256`,它速度快、实现成熟,并具备前向保密。传统 `diffie-hellman-group1-sha1`、`group14-sha1` 不建议继续启用。
## 追问
### 密钥交换和公钥认证有什么区别?
密钥交换是建立加密信道,发生在登录认证之前。公钥认证是证明“你是谁”,不负责生成会话密钥。
### Curve25519 为什么常被推荐?
它计算快、密钥短,实现上不容易踩参数选择坑。新版 OpenSSH 兼容性也足够好。
### 禁用 SHA-1 会影响老客户端吗?
会。老系统可能只支持 group14-sha1,若必须兼容,建议给遗留入口单独开策略。
### 前向保密解决什么问题?
它防的是长期私钥后来泄露,也不能直接解过去抓到的流量;但不解决当前服务器被入侵的问题。
## 写段配置
```bash
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
ssh -Q kex
ssh -vv user@host
```服务端5月30日 20:53
SSH 加密算法如何选择才兼顾安全和性能?SSH 加密算法主要分三类:会话数据用对称加密保护,身份和签名用非对称算法完成,完整性由 MAC 或 AEAD 兜底。实际配置时优先保留现代 OpenSSH 支持的组合:`aes256-gcm@openssh.com`、`chacha20-poly1305@openssh.com`、`aes256-ctr`。CBC、3DES 只适合遗留系统,不应出现在新服务器默认列表里。
## 追问
### AES-GCM 和 ChaCha20-Poly1305 怎么选?
有 AES-NI 的服务器优先 AES-GCM,吞吐通常更好。ARM 或老 CPU 上,ChaCha20-Poly1305 往往更稳定。
### 为什么不建议 CBC 和 3DES?
CBC 历史上暴露过填充相关攻击面,3DES 块大小和性能都不适合现代长连接。
### 只配置 Ciphers 就够了吗?
不够,SSH 安全还依赖 KEX、HostKey、MAC。使用 CTR 时,MAC 建议选 `*-etm@openssh.com`。
### 改配置最容易踩什么坑?
可能把旧客户端挡在门外。生产应保留备用会话,`sshd -t` 通过后再 reload。
## 写段配置
```bash
Ciphers aes256-gcm@openssh.com,chacha20-poly1305@openssh.com,aes256-ctr
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
```服务端5月30日 20:53
SSH Agent 是什么?如何安全管理私钥和转发认证?SSH Agent 是替你保管“已解锁私钥”的本地进程。它不会把私钥直接交给 ssh、git 或远程主机,而是在需要认证时完成签名,所以你不用每次连接都输入密钥密码。它解决效率问题,但 Agent 转发用错会放大安全风险。
## 追问
### SSH Agent 和私钥文件是什么关系?
私钥文件仍在磁盘上,Agent 只是把解锁后的密钥能力暂存在内存里。认证时 Agent 用私钥签名,不返回私钥内容。
### 为什么不建议全局开启 ForwardAgent?
远程机器可能使用转发过来的 agent socket。它偷不走私钥,但能在会话有效时冒用你的身份连接其他服务。
### Agent 转发和 ProxyJump 怎么选?
只是通过跳板机连接内网服务器,用 ProxyJump 更干净。只有登录跳板机后还要 git pull 或 ssh internal,才考虑 Agent 转发。
### ssh-add 没有 identities 怎么办?
说明 agent 正常但没加密钥。执行 `ssh-add ~/.ssh/id_ed25519`,再用 `ssh-add -l` 确认。
## 写段命令
```bash
eval "$(ssh-agent -s)"
ssh-add -t 3600 ~/.ssh/id_ed25519
ssh-add -l
ssh-add -D
```服务端5月30日 20:53
SSH 隧道有哪几种?本地、远程和动态转发怎么选?SSH 隧道本质是把一段 TCP 流量塞进 SSH 加密连接里传输。常见类型有本地隧道 `-L`、远程隧道 `-R`、动态隧道 `-D`。选型先看入口在哪边:你在本地访问远端内网服务,用 `-L`;远端要访问你本地服务,用 `-R`;目标很多且不固定,用 `-D` 做 SOCKS 代理。
## 追问
### 本地隧道和远程隧道最大区别是什么?
区别在入口端口开在哪里。`-L` 入口在本地,适合“我访问远端”;`-R` 入口在远端,适合“远端访问我”。
### 远程隧道为什么别人访问不到?
远端端口默认可能只监听 127.0.0.1。要检查 `GatewayPorts`、`AllowTcpForwarding`、安全组和防火墙。
### 动态隧道和 VPN 怎么取舍?
`ssh -D` 轻量临时,只代理支持 SOCKS 的应用;VPN 覆盖整机路由,更适合长期接入内网。
### 长连接怎么稳定?
加 `ServerAliveInterval` 和 `ServerAliveCountMax`,长期运行用 autossh 或 systemd 管理。
## 写段命令
```bash
ssh -L 3307:db.internal:3306 user@jump
ssh -R 8080:localhost:3000 user@public
ssh -D 127.0.0.1:1080 user@jump
```服务端5月30日 20:53
SSH 连接失败怎么排查?常见故障如何解决?SSH 故障排查不要一上来就改配置,先判断卡在哪一层:网络能不能到、端口有没有开、sshd 是否监听、认证是否通过、客户端是否选错密钥。timeout 多半是包没到服务端,refused 是端口没人接,Permission denied 则说明已经连到服务但认证失败。
## 追问
### timeout 和 refused 有什么区别?
timeout 优先看安全组、防火墙、路由和端口开放;refused 通常说明 sshd 没监听对应端口或服务没起来。先用 `nc -zv host 22` 验证。
### authorized_keys 有公钥还失败怎么办?
最常见是权限太开放、用户名不对、客户端没用对应私钥。用 `ssh -vvv` 看实际尝试的 key,再查服务端 auth.log 或 journalctl。
### 可以关闭 StrictHostKeyChecking 吗?
不建议。它会绕过主机身份校验,遇到 DNS 污染或中间人攻击风险更高。自动化脚本应预先分发 known_hosts。
### 改 sshd_config 最怕什么?
最怕语法错误或禁掉当前登录方式后断开连接。修改前跑 `sshd -t`,保留当前会话,再开新窗口测试。
## 写段命令
```bash
nc -zv hostname 22
ssh -vvv user@hostname
sudo systemctl status sshd
sudo sshd -t
```服务端5月29日 01:22
SSH 连接失败如何排查?常见原因有哪些?SSH 故障按报错分四类:Connection refused 说明目标端口未监听(sshd 未运行或防火墙阻断);Connection timeout 说明网络不可达(路由/防火墙丢弃包);Permission denied 是认证失败(密钥不匹配或权限错误);Host key verification failed 是 known_hosts 中记录的指纹与服务器当前密钥不一致。排查第一步始终是 ssh -vvv 看详细握手过程,日志会精确显示卡在哪个阶段。密钥权限是最常见的低级错误:私钥必须 600,.ssh 目录 700,authorized_keys 600,权限过宽 sshd 会拒绝认证。
## 追问
**ssh -vvv 输出中各阶段分别对应什么?**
三个 v 递增详细度。-v 显示连接建立、密钥交换、认证尝试;-vv 增加配置解析和 IO 细节;-vvv 再增加包级调试。重点关注 debug1: Authentications that can continue 和 debug1: Offering public key 两行,前者看服务端支持哪些认证方式,后者看客户端尝试了哪些密钥。
**连接频繁断开怎么解决?**
通常是 NAT/防火墙空闲连接超时淘汰。客户端配置 ServerAliveInterval 60(每60秒发心跳),服务端配置 ClientAliveInterval 300。autossh 可自动重连断开的会话。
**known_hosts 冲突一定是重装系统吗?**
不一定。IP 被复用、服务器更新了主机密钥、或中间人攻击都会导致。确认安全后用 ssh-keygen -R host 删除旧记录,不可盲目忽略否则失去 MITM 保护。
**如何不用密码只允许密钥登录?**
服务端 /etc/ssh/sshd_config 设置 PasswordAuthentication no 和 PubkeyAuthentication yes,改完 sshd -t 验证语法再 systemctl restart sshd。
## 写段代码
```bash
# 客户端 SSH 配置 ~/.ssh/config
Host myserver
HostName 10.0.0.1
User deploy
Port 2222
IdentityFile ~/.ssh/deploy_key
ServerAliveInterval 60
```服务端5月28日 06:59
SSH 安全加固怎么做?生产服务器必改的 8 项配置与面试追问SSH 安全加固是运维和后端面试中的高频考点,也是生产服务器上线前必须完成的配置。本文从实际生产场景出发,梳理 SSH 加固的核心配置项,每项给出"为什么做"和"怎么配",并在文末附上面试常见追问。
## 修改默认端口:降低被扫描概率
SSH 默认监听 22 端口,这是所有自动化扫描工具的首要目标。改成非标准端口后,扫描流量会大幅下降,日志噪音也明显减少。
```bash
# /etc/ssh/sshd_config
Port 2222
```
修改端口后记得同步更新防火墙规则和 Fail2Ban 配置,否则改了端口反而把自己锁在外面。另外,改端口属于"降低攻击面"而非"增强安全性",不要因此放松其他加固措施。
## 禁用 root 登录:权限最小化
允许 root 直接 SSH 登录意味着一旦密钥或密码泄露,攻击者立即获得最高权限。正确的做法是用普通用户登录,再通过 sudo 提权。
```bash
# /etc/ssh/sshd_config
PermitRootLogin no
```
部署前要确认普通用户的 sudo 权限已配置好,否则禁用 root 后将无法执行管理操作。
## 强制公钥认证:干掉暴力破解
密码认证最大的风险是暴力破解,即使设了复杂密码也难逃字典攻击。公钥认证用非对称加密,私钥不离开本地,攻击面极小。
```bash
# /etc/ssh/sshd_config
PasswordAuthentication no
PubkeyAuthentication yes
```
切换顺序很重要:先部署公钥到服务器,测试公钥登录成功,再禁用密码认证。如果反着来,你可能会丢掉唯一能登录的方式。
## 限制登录用户:白名单比黑名单可靠
不限制登录用户意味着系统上的所有账户(包括服务账户)都可以尝试 SSH 登录。用白名单方式只允许运维人员登录,是更安全的做法。
```bash
# /etc/ssh/sshd_config
AllowUsers deploy admin@10.0.0.0/8
AllowGroups sshusers
```
SSH 处理顺序是 DenyUsers → AllowUsers → DenyGroups → AllowGroups,建议用 AllowUsers 或 AllowGroups 做白名单,比 DenyUsers 黑名单更可控。
## 启用多因素认证:密钥 + 动态口令
密钥认证虽然安全,但如果私钥文件被盗,攻击者就能直接登录。多因素认证(MFA)要求同时持有密钥和动态口令,即使私钥泄露也无法单独使用。
```bash
# /etc/ssh/sshd_config
AuthenticationMethods publickey,keyboard-interactive:pam
# 安装 Google Authenticator PAM 模块
sudo apt-get install libpam-google-authenticator
# 为用户生成 TOTP 密钥
google-authenticator
# /etc/pam.d/sshd 添加
auth required pam_google_authenticator.so
```
生产环境建议 MFA 仅对跳板机或入口机开启,内网机器可以只做密钥认证,平衡安全和效率。
## 加密算法优化:淘汰弱算法
OpenSSH 支持多种加密算法,其中一些已被证明存在安全缺陷(如 3DES、CBC 模式、SHA1)。只保留安全的算法可以防止降级攻击。
```bash
# /etc/ssh/sshd_config
Ciphers aes256-gcm@openssh.com,chacha20-poly1305@openssh.com,aes256-ctr
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
```
可以用 `ssh -Q cipher` 查看当前 OpenSSH 支持的算法列表,用 `sshd -T | grep -i cipher` 查看实际生效配置。推荐使用 ssh-audit 工具对服务器做算法安全审计。
## 连接限制与超时:防暴力破解和会话劫持
暴力破解靠大量尝试碰运气,限制认证次数和连接速率能有效遏制。空闲会话不断开则可能被他人利用,设置超时是必要的。
```bash
# /etc/ssh/sshd_config
MaxAuthTries 3
MaxSessions 2
MaxStartups 10:30:100
LoginGraceTime 60
ClientAliveInterval 300
ClientAliveCountMax 0
```
`MaxStartups 10:30:100` 的含义:未完成认证的连接超过 10 个时,新连接有 30% 概率被拒绝;超过 100 个则全部拒绝。`ClientAliveCountMax 0` 表示连续 0 次无响应即断开,配合 `ClientAliveInterval 300` 实现 5 分钟无操作自动断开。
## 网络层防护:防火墙 + Fail2Ban + TCP Wrapper
SSH 加固不能只靠 sshd_config,网络层防护是第二道防线。三层配合使用效果最好。
**防火墙限制来源 IP**:
```bash
# iptables: 仅允许内网段访问
iptables -A INPUT -p tcp --dport 2222 -s 10.0.0.0/8 -j ACCEPT
iptables -A INPUT -p tcp --dport 2222 -j DROP
# ufw: 更简洁的写法
ufw allow from 10.0.0.0/8 to any port 2222
```
**Fail2Ban 自动封禁**:
```bash
# /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
```
**TCP Wrapper 兜底**:
```bash
# /etc/hosts.allow
sshd: 10.0.0.0/8
# /etc/hosts.deny
sshd: ALL
```
三者防护逻辑不同:防火墙在网络层过滤数据包,Fail2Ban 根据行为模式动态封禁,TCP Wrapper 在应用层做访问控制。不要只依赖其中一种。
## 面试追问与回答
**Q: 只改端口能防住攻击吗?**
不能。改端口只是降低了被自动化工具发现的概率,端口扫描器仍然可以遍历所有端口找到 SSH 服务。改端口是辅助手段,核心安全还是要靠密钥认证、禁用 root、限制来源 IP 这些实质性加固。
**Q: 密钥认证就够安全了吗?什么场景还需要 MFA?**
密钥认证比密码安全得多,但私钥文件一旦泄露(比如开发机被入侵),攻击者就能直接登录服务器。以下场景必须上 MFA:面向公网暴露的跳板机、拥有高权限的核心服务器、合规要求(等保三级及以上)的场景。
**Q: ClientAliveInterval 和 LoginGraceTime 区别是什么?**
两者作用阶段完全不同。LoginGraceTime 控制的是"连接建立后多久没完成认证就断开",防的是半开连接占用资源;ClientAliveInterval 控制的是"认证成功后多久没操作就断开",防的是空闲会话被利用。生产环境建议前者设 60 秒,后者设 300 秒。
**Q: 怎么验证 SSH 加固配置是否生效?**
分三步验证:用 `sshd -T` 检查配置是否被正确加载;用 `ssh-audit` 工具扫描服务器,它会给出算法强度和配置问题的评分;用另一台机器尝试以 root 登录、密码登录、从非白名单 IP 登录,确认都被拒绝。修改配置前务必保留一个已登录的会话,防止配置错误把自己锁在外面。服务端5月28日 02:10
如何进行 SSH 安全加固?有哪些最佳实践和安全配置建议?SSH 安全加固是运维和后端面试中的高频考点,也是生产环境必须落地的安全措施。一次配置不当的 SSH 服务,可能让整个服务器暴露在暴力破解和未授权访问的风险之下。本文从认证、网络、加密、密钥管理、监控五个层面系统讲解 SSH 安全加固方案。
## 核心原则:最小权限 + 纵深防御
SSH 安全加固不是单一配置的修改,而是从多个层面构建防线。核心思路:即使某一层被突破,还有下一层阻拦。没有任何一项配置能单独保证安全,只有组合使用才能形成有效防御体系。
## 认证层加固
### 禁用密码认证,仅允许密钥登录
密码认证是暴力破解的最大突破口。密钥认证从根本上消除了密码被猜解的风险——密钥空间足够大,暴力破解在计算上不可行。
```bash
# /etc/ssh/sshd_config
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
```
密钥类型选择:优先 ED25519(更短、更快、更安全),RSA 至少 4096 位。
```bash
# 推荐:ED25519
ssh-keygen -t ed25519 -C "user@company"
# 兼容场景:RSA 4096
ssh-keygen -t rsa -b 4096 -C "user@company"
```
注意:ED25519 不支持 `-b` 参数指定密钥长度,它的密钥长度是固定的。原始文章中 `ssh-keygen -t ed25519 -b 4096` 的写法是错误的,`-b` 参数会被忽略。
### 禁止 root 直接登录
生产环境中,root 直接登录是高危行为。应先以普通用户登录,再通过 sudo 提权,这样所有操作都有审计记录,便于事后追溯。
```bash
# /etc/ssh/sshd_config
PermitRootLogin no
```
如果必须允许 root 登录(极少数场景),至少使用 `PermitRootLogin prohibit-password`,仅允许密钥认证。`prohibit-password` 比 `without-password` 更明确,是 OpenSSH 7.0+ 的推荐写法。
### 限制可登录的用户和组
不要让系统上所有用户都能 SSH 登录,明确指定允许登录的白名单。
```bash
# /etc/ssh/sshd_config
AllowUsers admin deploy
AllowGroups ssh-users
```
`AllowUsers` 和 `AllowGroups` 是白名单机制,不在名单中的用户即使有密钥也无法登录。两者同时配置时取交集,即用户必须同时满足用户和组的限制。搭配 `DenyUsers` 使用时,deny 优先于 allow。
### 限制认证尝试次数和连接速率
暴力破解依赖大量快速尝试,限制速率可以直接阻断此类攻击。
```bash
# /etc/ssh/sshd_config
MaxAuthTries 3
MaxStartups 10:30:100
LoginGraceTime 60
```
`MaxStartups 10:30:100` 的含义:超过 10 个未完成连接时,以 30% 概率拒绝新连接;超过 100 个时全部拒绝。这个参数是防止连接耗尽型攻击的关键配置。
## 网络层加固
### 修改默认端口
改端口不是安全措施,而是降低噪声的手段。自动化扫描工具通常只扫 22 端口,改端口后日志中的扫描噪音会大幅减少,便于发现真正有威胁的连接。
```bash
# /etc/ssh/sshd_config
Port 2222
```
修改后务必同步更新防火墙规则和安全组,否则会被自己挡在门外。建议在修改前先通过 console 或带外管理确认回退方案。
### 防火墙限制来源 IP
最有效的访问控制是只允许已知 IP 连接。
```bash
# ufw 示例:仅允许办公网段
ufw allow from 10.0.1.0/24 to any port 2222 proto tcp
ufw enable
# iptables 示例
iptables -A INPUT -p tcp --dport 2222 -s 10.0.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 2222 -j DROP
```
如果来源 IP 不固定,可以配合 VPN 或跳板机使用,将 SSH 访问收敛到单一入口。云上环境建议通过安全组实现,比 iptables 更不易出错。
### 使用 fail2ban 自动封禁
fail2ban 通过分析日志自动封禁异常 IP,是对暴力破解的自动化防御。
```bash
# /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 3600
findtime = 600
```
注意事项:`maxretry` 不宜设太小,否则合法用户输错一次密码就可能被封。生产环境建议配合 `ignoreip` 将管理网段加入白名单,避免误封。`bantime` 建议设为较长时间(如 86400),频繁攻击的 IP 没必要短时间解封。
### 配置连接超时
长时间空闲的 SSH 会话是安全隐患,终端可能被旁人操作。
```bash
# /etc/ssh/sshd_config
ClientAliveInterval 300
ClientAliveCountMax 2
```
300 秒(5 分钟)无操作发送一次心跳探测,连续 2 次无响应则断开。实际超时时间 = ClientAliveInterval * ClientAliveCountMax = 600 秒。根据业务场景调整:高频操作环境可以设短一些,长时间编译部署的环境适当延长。
## 加密层加固
### 使用强加密算法
默认配置可能包含已淘汰的弱算法(如 3des、arcfour)。显式指定安全算法列表,确保通信强度。
```bash
# /etc/ssh/sshd_config
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
```
可以用 `ssh-audit` 工具检测当前配置的加密强度:
```bash
# 安装
pip3 install ssh-audit
# 检测
ssh-audit server_ip
```
`ssh-audit` 会输出当前使用的密钥交换、加密、MAC 算法的安全等级,标记出需要替换的弱算法。
### 禁用不必要的功能
每多开一个功能就多一个攻击面。不需要的功能一律关闭。
```bash
# /etc/ssh/sshd_config
X11Forwarding no
GatewayPorts no
PermitTunnel no
```
`AllowTcpForwarding` 需要根据业务判断:如果需要端口转发(如数据库调试),保留 `yes`;否则设为 `no`。注意设为 `no` 并不能完全阻止端口转发,用户仍可通过 `command=` 方式间接实现,真正需要禁止时应配合 `no-port-forwarding` 在 authorized_keys 中限制。
## 密钥管理
### 为私钥设置密码短语
即使私钥文件泄露,没有密码短语也无法使用。这是密钥安全的最后一道防线。
```bash
ssh-keygen -t ed25519 -C "user@company"
# 在提示时输入强密码短语
```
已有密钥可以补设密码短语:
```bash
ssh-keygen -p -f ~/.ssh/id_ed25519
```
配合 `ssh-agent` 使用,输入一次密码短语后,后续连接自动认证,不必每次都输入。
### 限制密钥用途
在 `authorized_keys` 中可以为每个密钥设置限制条件,实现精细化的访问控制。
```bash
# ~/.ssh/authorized_keys
# 限制只能执行特定命令
command="/usr/local/bin/backup.sh" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
# 限制来源 IP
from="10.0.1.0/24" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
# 组合限制:来自特定 IP 且不能端口转发
from="10.0.1.0/24",no-port-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
```
这种机制特别适合 CI/CD 部署场景:为部署密钥绑定 `command`,即使密钥泄露,攻击者也只能执行指定命令。可用的限制选项还包括 `no-pty`(禁用交互式终端)、`no-agent-forwarding`(禁用 agent 转发)等。
### 定期轮换密钥
密钥应该像密码一样定期更换,尤其在人员离职时必须及时清理。
```bash
# 生成新密钥
ssh-keygen -t ed25519 -f ~/.ssh/new_key -C "user@company"
# 部署新公钥
ssh-copy-id -i ~/.ssh/new_key.pub user@server
# 确认新密钥可用后再删除旧密钥
rm ~/.ssh/old_key ~/.ssh/old_key.pub
```
建议在密钥的 `-C` 注释中包含创建日期,方便追踪轮换周期。
## 多因素认证
### SSH 证书替代传统密钥
大规模团队管理中,逐个分发密钥并维护 `authorized_keys` 效率极低。SSH 证书通过 CA 签发,自动过期,无需手动清理。
```bash
# 生成 CA 密钥
ssh-keygen -t ed25519 -f /etc/ssh/ca_key
# 签发用户证书(有效期 52 周)
ssh-keygen -s /etc/ssh/ca_key -I user_id -n username -V +52w ~/.ssh/user_key.pub
# 服务器端信任 CA
# /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/ca_key.pub
```
证书到期自动失效,人员离职只需不再签发新证书,无需逐台服务器删除公钥。这是 SSH 证书相比传统公钥的最大优势——集中化管理。Facebook(Meta)的基础设施就是大规模使用 SSH 证书的典型案例。
### TOTP 双因素认证
在密钥认证基础上增加动态验证码,即使密钥泄露,没有验证码也无法登录。
```bash
# 安装 Google Authenticator PAM 模块
apt-get install libpam-google-authenticator
# 每个用户独立配置
google-authenticator
# /etc/pam.d/sshd 添加
auth required pam_google_authenticator.so
# /etc/ssh/sshd_config
KbdInteractiveAuthentication yes
```
注意:OpenSSH 8.2+ 推荐使用 `KbdInteractiveAuthentication` 替代已弃用的 `ChallengeResponseAuthentication`。配置前确保有 console 访问作为回退手段,万一 PAM 配置出错不会锁死服务器。
## 监控与应急
### 日志监控
```bash
# /etc/ssh/sshd_config
LogLevel VERBOSE
SyslogFacility AUTHPRIV
```
VERBOSE 级别会记录密钥指纹,便于追踪是哪个密钥登录的。INFO 级别不够详细,DEBUG 级别日志量过大影响性能,VERBOSE 是生产环境的平衡选择。
查看登录活动:
```bash
# 最近成功登录
last -n 20
# 失败登录尝试
lastb -n 20
# 实时监控
tail -f /var/log/auth.log | grep sshd
```
### 登录告警
在关键服务器上配置登录通知,异常登录可以第一时间发现。
```bash
# /etc/profile 或 ~/.bashrc
if [ -n "$SSH_CLIENT" ]; then
echo "SSH login: $(whoami) from $(echo $SSH_CLIENT | awk '{print $1}') at $(date)" | mail -s "SSH Login Alert: $(hostname)" admin@company.com
fi
```
大规模环境建议接入集中化告警系统(如 Prometheus AlertManager),而不是依赖邮件通知。
### 入侵应急响应
一旦发现异常登录迹象,按以下步骤处置:
```bash
# 1. 查看异常连接
ss -tnp | grep :2222
# 2. 紧急封禁可疑 IP
iptables -A INPUT -s 可疑IP -j DROP
# 3. 如果确认被入侵,立即切断 SSH 服务
systemctl stop sshd
# 4. 修改配置后重启(先验证语法)
sshd -t && systemctl start sshd
```
`sshd -t` 在重启前检查配置语法,避免配置错误导致无法远程连接。养成任何配置修改后都先执行 `sshd -t` 的习惯。
## 配置检查清单
每次加固后逐项确认:
- 端口已改为非默认值,防火墙规则已同步更新
- 密码认证已禁用,仅允许密钥登录
- root 直接登录已禁止
- 可登录用户已限制为白名单
- 认证尝试次数已限制(MaxAuthTries <= 3)
- 加密算法已更新为安全列表
- fail2ban 已启用且白名单已配置
- 空闲连接超时已设置
- 日志级别设为 VERBOSE
- 配置修改后已执行 `sshd -t` 验证语法
- 已备份原配置文件(cp sshd_config sshd_config.bak)
- 私钥已设置密码短语
- 旧密钥和离职人员密钥已清理
## 常见误区
**误区一:改了端口就安全了。** 端口扫描工具可以遍历所有端口,改端口只是降低噪音,不能替代其他加固措施。
**误区二:密钥认证就不需要 fail2ban 了。** fail2ban 还能防御探测行为和异常连接模式,两者互补而非替代。
**误区三:加固一次就万事大吉。** SSH 软件需要定期更新,密钥需要轮换,日志需要审计。安全是持续过程,不是一次性操作。
**误区四:配置越严格越安全。** 过度加固可能导致运维不便甚至锁死自己。加固策略要根据实际场景权衡,始终确保有可恢复的手段(如 console 访问或带外管理)。
**误区五:AllowTcpForwarding no 就能禁止端口转发。** 用户仍可通过 `command=` 方式或 SSH 代理转发间接实现。真正需要禁止时应配合 authorized_keys 中的 `no-port-forwarding` 限制。服务端5月28日 02:06
如何配置 SSH 密钥认证?密钥认证相比密码认证有哪些优势?SSH 密钥认证基于非对称加密,客户端持有私钥、服务器持有公钥,登录时通过加密挑战完成身份验证,无需传输密码。相比密码认证,密钥认证抗暴力破解、免输入密码、可精细控制权限,是服务器安全运维的基本要求。
## 密钥类型怎么选
主流密钥算法对比:
| 算法 | 密钥长度 | 安全性 | 性能 | 推荐度 |
|------|---------|--------|------|--------|
| ED25519 | 256 bit | 极高 | 最快 | 首选 |
| ECDSA | 256 bit | 高 | 快 | 可用 |
| RSA | 4096 bit | 高 | 慢 | 兼容性场景 |
ED25519 基于 Curve25519 椭圆曲线,密钥短、签名快、抗侧信道攻击,是当前首选。RSA 4096 仅在需要兼容老旧系统时使用。
## 生成密钥对
```bash
# 生成 ED25519 密钥(推荐)
ssh-keygen -t ed25519 -C "user@example.com"
# 生成 RSA 4096 密钥(兼容旧系统)
ssh-keygen -t rsa -b 4096 -C "user@example.com"
# 指定密钥文件名
ssh-keygen -t ed25519 -f ~/.ssh/deploy_key -C "ci/deploy"
# 生成带硬件绑定的 FIDO2 密钥(需要 YubiKey 等设备)
ssh-keygen -t ed25519-sk -C "user@example.com"
```
生成后会产生两个文件:
- **私钥**(`~/.ssh/id_ed25519`):绝对不能泄露,等同于你的身份凭证
- **公钥**(`~/.ssh/id_ed25519.pub`):可以公开,上传到目标服务器
私钥建议设置 passphrase,即使私钥被盗,攻击者也无法直接使用。
## 配置服务器端密钥登录
### 第一步:复制公钥到服务器
```bash
# 方法一:ssh-copy-id(最简单)
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@hostname
# 方法二:手动追加(目标机器没有 ssh-copy-id 时)
cat ~/.ssh/id_ed25519.pub | ssh user@hostname \
"mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
```
### 第二步:确认服务器 SSH 配置
编辑服务器上的 `/etc/ssh/sshd_config`:
```bash
# 启用公钥认证
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
# 确认密钥登录成功后,再禁用密码认证
PasswordAuthentication no
```
修改后重启服务:
```bash
# Ubuntu/Debian
sudo systemctl restart sshd
# CentOS/RHEL
sudo systemctl restart sshd
```
**注意**:先测试密钥登录成功,再禁用密码认证,否则可能锁死自己。
### 第三步:设置文件权限
权限不对是密钥登录失败最常见的原因:
```bash
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_ed25519 # 私钥
chmod 644 ~/.ssh/id_ed25519.pub # 公钥
```
## 密钥认证比密码认证强在哪
**抗暴力破解**:密码可被字典攻击逐个尝试,而 256 bit 的 ED25519 私钥暴力破解概率在计算上不可能。一次暴力尝试的代价相差约 2^128 倍。
**零密码传输**:密码认证每次登录都要把密码发送到服务器,存在中间人截获风险。密钥认证只传输加密签名,私钥永不离开本地。
**免交互登录**:配合 ssh-agent 或无 passphrase 的密钥,可实现自动化部署、定时备份、CI/CD 流水线免密操作,密码认证做不到。
**细粒度权限控制**:在 `authorized_keys` 中可限制单个密钥的权限:
```bash
# 限制只能执行 git 操作
command="/usr/bin/git-shell" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
# 限制来源 IP
from="10.0.0.0/8" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
# 禁用端口转发和 X11
no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
# 组合限制:只允许从内网 SCP 文件
command="/usr/libexec/openssh/sftp-server",from="10.0.0.0/8",no-port-forwarding ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
```
**多因素叠加**:密钥本身是「你有的」,加上 passphrase 就是「你知道的」,再加 FIDO2 硬件密钥就是「你有的物理设备」,三因素认证也轻松实现。
## ssh-agent 管理多个密钥
管理多台服务器或多个 Git 平台时,需要不同的密钥:
```bash
# 启动 agent
eval "$(ssh-agent -s)"
# 添加密钥(会提示输入 passphrase)
ssh-add ~/.ssh/id_ed25519
ssh-add ~/.ssh/deploy_key
# 查看已加载的密钥
ssh-add -l
# 删除所有密钥
ssh-add -D
```
配置 `~/.ssh/config` 让不同主机使用不同密钥:
```bash
Host github.com
IdentityFile ~/.ssh/id_ed25519
User git
Host production-server
IdentityFile ~/.ssh/deploy_key
User deploy
Port 2222
Host jump-server
IdentityFile ~/.ssh/id_ed25519
ProxyJump none
Host internal-*
ProxyJump jump-server
IdentityFile ~/.ssh/id_ed25519
```
## 常见排错
密钥登录失败时,按以下顺序排查:
**1. 用 verbose 模式看详细日志**
```bash
ssh -vvv user@hostname
```
重点看 `Offering public key` 和 `Server accepts key` 两行。
**2. 检查权限**
```bash
# 客户端
ls -la ~/.ssh/
# 私钥必须是 600,目录必须是 700
# 服务器端
ls -la ~/.ssh/ ~/.ssh/authorized_keys
# authorized_keys 必须 600,~/.ssh 必须 700
```
**3. 检查服务器端日志**
```bash
# 查看 SSH 服务日志
sudo journalctl -u sshd --since "10 minutes ago"
# 或
sudo tail -f /var/log/auth.log
```
常见错误原因:
| 现象 | 原因 | 解决 |
|------|------|------|
| Permission denied | authorized_keys 权限 644 | chmod 600 |
| Permission denied | .ssh 目录权限 755 | chmod 700 |
| Permission denied | 家目录被组可写 | chmod 750 ~ |
| Connection refused | sshd 未运行 | systemctl start sshd |
| 密钥不被接受 | 公钥内容被截断/换行 | 重新复制 |
| SELinux 阻止 | 文件安全上下文不对 | restorecon -Rv ~/.ssh |
## 安全加固清单
- 使用 ED25519 或 RSA 4096,不用 RSA 2048 及以下
- 私钥设置 passphrase,用 ssh-agent 避免反复输入
- 服务器端禁用密码认证:`PasswordAuthentication no`
- 禁用 root 远程登录:`PermitRootLogin no` 或 `prohibit-password`
- 限制 SSH 端口,不用默认 22
- 定期轮换密钥,移除 authorized_keys 中的旧公钥
- 敏感服务器使用 FIDO2 硬件密钥(`ed25519-sk`)
- 配置 fail2ban 防止扫描探测
- 审计 SSH 登录日志,关注异常 IP服务端5月28日 00:54
SSH 配置文件有哪些常用选项?如何通过配置文件简化连接管理?SSH 配置文件(~/.ssh/config 和 /etc/ssh/sshd_config)是管理和简化远程连接的核心工具。掌握常用选项后,可以将冗长的命令行参数转化为可复用的配置块,实现连接复用、跳板代理和端口转发等高级功能,大幅提升日常运维效率。
## 客户端配置文件(~/.ssh/config)
### 配置文件位置与优先级
SSH 客户端从三个来源读取配置,优先级从高到低:
1. **命令行参数**:如 `ssh -p 2222 user@host`,优先级最高
2. **用户配置文件**:`~/.ssh/config`(最常用,日常管理的核心)
3. **全局配置文件**:`/etc/ssh/ssh_config`(系统管理员设置默认值)
三个来源的配置合并后生效,命令行参数覆盖用户配置,用户配置覆盖全局配置。注意:用户配置文件权限必须为 600,否则 SSH 会拒绝读取。
### 基本连接配置
```bash
# ~/.ssh/config
# 最基本的主机配置
Host server1
HostName 192.168.1.100
User admin
Port 2222
IdentityFile ~/.ssh/id_ed25519
# 使用别名简化连接
Host prod
HostName prod.example.com
User deploy
IdentityFile ~/.ssh/prod_key
```
配置完成后,`ssh prod` 等价于 `ssh -i ~/.ssh/prod_key deploy@prod.example.com`,无需记忆每台服务器的连接参数。
### 通配符与批量配置
Host 指令支持通配符 `*` 和 `?`,可以对一组主机应用相同配置:
```bash
# 对所有 example.com 主机使用相同用户和密钥
Host *.example.com
User webadmin
IdentityFile ~/.ssh/web_key
# 对所有连接启用连接保持
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
```
关键细节:SSH 按配置文件中的顺序逐段匹配 Host,第一个匹配段中出现的选项生效,不会与后续段合并。因此通配符配置应放在文件末尾,避免被提前匹配覆盖。
### 连接复用(ControlMaster)
连接复用是 SSH 最实用的性能优化手段。首次连接建立 master 连接并创建 socket 文件,后续到同一主机的连接直接复用该通道,跳过认证和握手过程:
```bash
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
```
各参数含义:
| 参数 | 说明 |
|------|------|
| `ControlMaster auto` | 自动创建新连接或复用已有 master 连接 |
| `ControlPath` | socket 文件路径,`%r` 远程用户名、`%h` 主机名、`%p` 端口 |
| `ControlPersist 600` | 最后一个会话关闭后,master 连接保持 600 秒 |
使用前需创建 socket 目录:`mkdir -p ~/.ssh/sockets`。管理命令:`ssh -O exit hostname` 关闭复用连接,`ssh -O check hostname` 查看复用状态。
### 跳板机配置
#### ProxyJump(推荐,OpenSSH 7.3+)
```bash
Host jump
HostName jump.example.com
User jumper
IdentityFile ~/.ssh/jump_key
Host internal
HostName 10.0.0.50
User root
ProxyJump jump
```
ProxyJump 语法简洁,支持多跳串联:`ProxyJump jump1,jump2`。命令行等价写法:`ssh -J jump internal`。
#### ProxyCommand(兼容旧版本)
```bash
Host internal
HostName 10.0.0.50
User root
ProxyCommand ssh -W %h:%p jump.example.com
```
核心区别:ProxyJump 是 ProxyCommand 的高层封装,底层都是通过跳板机建立到目标的 TCP 通道。两者互斥,不能同时配置。OpenSSH 7.3 以下只能用 ProxyCommand。
### 端口转发配置
SSH 支持三种端口转发,都可以写入配置文件持久化:
```bash
# 本地端口转发:将远程 Redis 映射到本地
Host db-tunnel
HostName db.example.com
User admin
LocalForward 16379 127.0.0.1:6379
# 远程端口转发:将本地服务暴露到远程
Host expose-local
HostName remote.example.com
User admin
RemoteForward 8080 127.0.0.1:3000
# 动态端口转发:SOCKS5 代理
Host socks-proxy
HostName tunnel.example.com
User admin
DynamicForward 1080
```
三种转发的区别:
| 类型 | 配置项 | 数据流方向 | 典型场景 |
|------|--------|-----------|----------|
| 本地转发 | `LocalForward` | 本地 → 远程内网 | 访问远程数据库 |
| 远程转发 | `RemoteForward` | 远程 → 本地 | 将本地服务暴露给远程 |
| 动态转发 | `DynamicForward` | 本地 → 任意(SOCKS5) | 通过 SSH 安全代理上网 |
### Include 指令与配置拆分
OpenSSH 7.3+ 支持 Include 指令,可将配置按项目或环境拆分为多个文件:
```bash
# ~/.ssh/config
Include ~/.ssh/config.d/*
# ~/.ssh/config.d/work
Host prod-*
User deploy
IdentityFile ~/.ssh/work_key
# ~/.ssh/config.d/personal
Host github
HostName github.com
User git
IdentityFile ~/.ssh/personal_key
```
这种方式便于按项目或环境管理配置,也方便脚本自动化生成。Include 可以出现在配置文件的任何位置,被包含的文件内容在该位置展开。
### Token 替换
配置文件中的部分选项支持 Token 动态替换:
| Token | 含义 | 常见用途 |
|-------|------|----------|
| `%h` | 远程主机名 | ControlPath、ProxyCommand |
| `%p` | 远程端口号 | ControlPath、ProxyCommand |
| `%r` | 远程用户名 | ControlPath |
| `%d` | 本地用户家目录 | IdentityFile 路径 |
| `%u` | 本地用户名 | 日志路径 |
### 其他常用选项
| 选项 | 默认值 | 说明 |
|------|--------|------|
| `IdentitiesOnly yes` | no | 只使用配置的密钥,防止 ssh-agent 干扰 |
| `Compression yes` | no | 启用压缩,低带宽环境有效 |
| `StrictHostKeyChecking` | ask | 主机密钥验证策略:ask/yes/no |
| `ForwardAgent yes` | no | 转发认证代理,用于跳板机链式认证 |
| `ConnectTimeout 10` | 无 | 连接超时秒数 |
| `ServerAliveInterval 60` | 0 | 客户端心跳间隔,防止连接被防火墙断开 |
## 服务器端配置文件(/etc/ssh/sshd_config)
### 认证与安全加固
```bash
# /etc/ssh/sshd_config
# 认证方式
PasswordAuthentication no # 禁用密码认证,只允许密钥登录
PubkeyAuthentication yes # 启用公钥认证
PermitRootLogin prohibit-password # 允许 root 但禁止密码登录
MaxAuthTries 3 # 限制单次连接认证尝试次数
# 访问控制(白名单优先)
AllowUsers admin deploy # 只允许这些用户登录
AllowGroups ssh-users # 只允许这些组登录
DenyUsers test guest # 拒绝这些用户登录
```
`PermitRootLogin prohibit-password` 比 `no` 更灵活:允许 root 通过密钥登录但禁止密码,适合需要 root 权限的自动化脚本。
### 连接与性能
```bash
MaxStartups 10:30:100 # 未认证连接速率限制:10个开始拒绝30%,100个全部拒绝
LoginGraceTime 60 # 认证超时时间(秒)
ClientAliveInterval 300 # 服务器端心跳间隔(秒)
ClientAliveCountMax 2 # 心跳无响应次数上限
```
### 功能开关
```bash
X11Forwarding no # 禁用 X11 转发
AllowTcpForwarding yes # 允许 TCP 转发
GatewayPorts no # 禁止远程转发绑定非本地地址
PermitTunnel no # 禁用 tun 设备
```
### Match 条件块
sshd_config 支持 Match 指令,按条件应用不同配置,实现精细化访问控制:
```bash
# 内网允许密码认证
Match Address 192.168.1.0/24
PasswordAuthentication yes
# 特定用户允许端口转发
Match User deploy
AllowTcpForwarding yes
GatewayPorts clientspecified
# 特定组禁用端口转发
Match Group restricted
AllowTcpForwarding no
```
Match 支持的条件:User、Group、Host、Address,可组合使用。
## 常见面试追问
**ProxyJump 和 ProxyCommand 有什么区别?**
ProxyJump 是 ProxyCommand 的高层封装(OpenSSH 7.3+),语法更简洁。底层原理相同:都是通过跳板机建立到目标主机的 TCP 通道。ProxyJump 支持逗号分隔的多跳串联(`ProxyJump j1,j2`),而 ProxyCommand 实现多跳需要嵌套。两者互斥,不能同时配置。
**ControlPath 中 %r %h %p 分别代表什么?**
`%r` 是远程用户名,`%h` 是远程主机名,`%p` 是远程端口号。这三个 Token 组合可以确保到不同主机的连接使用不同的 socket 文件,避免复用冲突。
**StrictHostKeyChecking 三个值有什么区别?**
`ask`(默认)首次连接时提示确认主机指纹;`yes` 只允许 known_hosts 中已有的主机连接,未知主机直接拒绝;`no` 自动接受新主机密钥存入 known_hosts。生产环境建议 `ask` 或 `yes`,`no` 存在中间人攻击风险。
**ClientAliveInterval 和 ServerAliveInterval 有什么区别?**
两者都是心跳机制,但发起方和用途不同。ServerAliveInterval 由客户端定期发心跳给服务器,防止客户端侧连接被防火墙或 NAT 设备断开。ClientAliveInterval 由服务器定期发心跳给客户端,用于检测客户端是否存活并主动断开僵死连接。服务端5月28日 00:53
什么是 SSH 隧道和跳板机?如何配置多级跳板连接?SSH 隧道是通过 SSH 协议在客户端和服务器之间建立加密通道的技术,所有经过该通道的流量都受到加密保护。跳板机(Jump Host / Bastion Host)是部署在网络边界的唯一入口,外部用户必须先登录跳板机才能访问内网服务器。两者结合是企业运维中实现安全远程访问的标准方案——绝大多数生产环境都不允许直连内网服务器,必须经过跳板机中转。
## SSH 隧道的三种转发模式
理解 SSH 隧道的核心在于区分三种端口转发方式,它们解决不同的问题,选错模式会导致连接不通或不必要的复杂性。
### 本地端口转发(-L)
将本地某个端口的流量,通过 SSH 连接转发到远端服务器的指定端口。典型场景:从本地机器访问远端内网的数据库、Redis 等服务。
```bash
# 在本地 3306 端口访问内网 MySQL
ssh -L 3306:db-server:3306 user@bastion -N
# 连接后,本地执行 mysql -h 127.0.0.1 即可访问远程数据库
```
`-L` 格式为 `本地端口:目标主机:目标端口`,其中"目标主机"是从 SSH 服务端(跳板机)的视角解析的,不是从本地解析——这是最常踩的坑。
### 远程端口转发(-R)
将远端服务器的某个端口流量,通过 SSH 连接转发到本地指定端口。典型场景:让远端服务器访问你本地运行的服务,比如本地开发环境的 Web 服务需要被远端回调。
```bash
# 让远端服务器通过 8080 端口访问你本地的 Web 服务
ssh -R 8080:localhost:3000 user@remote-server -N
# 远端服务器上访问 localhost:8080 即可到达你本地的 3000 端口
```
注意:远程转发需要 SSH 服务端开启 `GatewayPorts` 选项,否则默认只绑定到远端的 `localhost`。
### 动态端口转发(-D)
在本地创建一个 SOCKS5 代理,所有通过该代理的流量都会经由 SSH 连接转发。典型场景:需要通过跳板机访问内网多个服务,不想为每个服务单独配一条 `-L` 规则。
```bash
# 在本地 1080 端口创建 SOCKS5 代理
ssh -D 1080 user@bastion -N
# 配置浏览器或 curl 使用 socks5://127.0.0.1:1080 代理
curl --socks5 127.0.0.1:1080 http://internal-web:8080
```
**选取原则**:访问单个固定服务用 `-L`,需要暴露本地服务给远端用 `-R`,需要灵活访问多个内网服务用 `-D`。`-N` 参数表示不打开远程 shell,仅做端口转发,生产环境推荐加上。
## 跳板机配置方式
### ProxyJump(推荐)
OpenSSH 7.3+ 支持,是最简洁的配置方式。`-J` 参数直接指定跳板机,SSH 客户端自动处理中间的连接建立,流量端到端加密,中间跳板机无法解密。
```bash
# 命令行
ssh -J jump-user@jump-host:22 target-user@target-host
# 配置文件(~/.ssh/config)
Host jump-host
HostName jump.example.com
User jump-user
Host target-host
HostName target.example.com
User target-user
ProxyJump jump-host
```
配置完成后,`ssh target-host` 即可自动经跳板机连接目标服务器,scp、rsync、sftp 等工具同样适用。
### ProxyCommand(兼容旧版本)
适用于 OpenSSH 7.3 以下版本,通过 `ssh -W` 参数在跳板机上建立 TCP 转发通道。
```bash
# 命令行
ssh -o ProxyCommand="ssh -W %h:%p jump-user@jump-host" target-user@target-host
# 配置文件
Host target-host
HostName target.example.com
User target-user
ProxyCommand ssh -W %h:%p jump-user@jump-host
```
`%h` 和 `%p` 是占位符,分别代表最终目标的主机名和端口,SSH 客户端会自动替换。ProxyCommand 的优势是可以在命令中嵌入更复杂的逻辑,比如根据网络环境选择不同的跳板路径。
### 多级跳板配置
当网络拓扑存在多层隔离(如 DMZ → 应用区 → 数据区)时,需要串联多个跳板机。ProxyJump 天然支持链式配置。
```bash
# 命令行:两级跳板,用逗号分隔
ssh -J jump1@host1,jump2@host2 target@final-host
# 配置文件:逐层声明依赖关系
Host jump1
HostName host1.example.com
User jump1
Host jump2
HostName host2.example.com
User jump2
ProxyJump jump1
Host final
HostName final.example.com
User target
ProxyJump jump2
```
关键在于配置文件中的"依赖链":`final → jump2 → jump1`。每层只需声明自己的上一跳,SSH 会递归建立连接。所有经过跳板机的流量都是端到端加密的,中间跳板机只能看到加密数据流。
## 典型使用场景
### 通过跳板机访问内网数据库
结合本地端口转发和 ProxyJump,将内网数据库映射到本地端口,用本地客户端工具直连。
```bash
ssh -L 3306:db-server:3306 -J bastion@bastion.example.com user@bastion.example.com -N
```
连接后,本地 `mysql -h 127.0.0.1 -P 3306` 直接访问远程数据库,对本地客户端完全透明。
### 通过跳板机传输文件
scp 和 rsync 原生支持 ProxyJump,无需额外配置。
```bash
# scp 传输
scp -o ProxyJump="bastion@bastion.example.com" local-file admin@internal-server:/path/
# rsync 同步
rsync -avz -e "ssh -J bastion@bastion.example.com" ./src/ admin@internal-server:/path/
```
### 用 SOCKS 代理浏览内网 Web 服务
当你需要通过浏览器访问内网多个 Web 应用时,动态转发比逐一配置本地端口更方便。
```bash
ssh -D 1080 -J bastion@bastion.example.com user@bastion.example.com -N
```
在浏览器代理设置中配置 SOCKS5:`127.0.0.1:1080`,即可访问所有内网 Web 服务。Chrome 可配合 SwitchyOmega 插件实现按规则自动代理。
### 用配置文件简化日常操作
将常用服务器和跳板关系写入 `~/.ssh/config`,日常操作只需 `ssh <别名>`。
```bash
Host bastion
HostName bastion.example.com
User bastion
IdentityFile ~/.ssh/bastion_key
Host web-server
HostName 192.168.1.100
User webadmin
ProxyJump bastion
IdentityFile ~/.ssh/internal_key
Host db-server
HostName 192.168.1.200
User dbadmin
ProxyJump bastion
IdentityFile ~/.ssh/internal_key
```
这样 `ssh web-server` 和 `ssh db-server` 都会自动经 bastion 跳转,VS Code Remote-SSH 也会自动读取该配置。
## 密钥转发与安全要点
### SSH Agent 转发
当跳板机不允许存放私钥时,可以通过 Agent 转发在跳板机上使用本地的 SSH 密钥,私钥始终留在本地。
```bash
ssh -A -J bastion@bastion.example.com target@internal-server
```
安全提醒:Agent 转发存在风险——如果跳板机被入侵,攻击者可以利用转发的 Agent 连接你能访问的其他服务器。仅在可信跳板机上启用,用完及时关闭。
### 跳板机加固配置
```bash
# /etc/ssh/sshd_config
PasswordAuthentication no # 禁用密码登录
PermitRootLogin no # 禁止 root 登录
AllowUsers bastion # 限制可登录用户
MaxAuthTries 3 # 限制认证尝试次数
ClientAliveInterval 300 # 空闲连接超时检测
```
更完善的方案是部署堡垒机(如 Teleport、JumpServer),提供会话录制、访问审计和多因素认证。
### 密钥管理实践
- 跳板机和目标服务器使用不同的密钥对,单密钥泄露不会影响全局
- 私钥必须设置密码短语(passphrase),用 `ssh-keygen -p` 添加
- 定期轮换密钥,使用 `ssh-keygen -R` 清理旧授权
- 用 `ssh-add -l` 检查 Agent 中加载的密钥,避免残留
## 连接复用与性能优化
每次建立 SSH 连接都要完成 TCP 握手和密钥交换,频繁连接时开销明显。SSH 支持连接复用(Multiplexing),复用已有连接的通道建立新会话,后续连接几乎瞬时完成。
```bash
# ~/.ssh/config
Host *
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 600
```
`ControlPersist 600` 让最后一个会话关闭后连接保持 600 秒,避免频繁重建。其他优化手段:`-C` 启用压缩(低带宽高延迟网络有效),`ServerAliveInterval 60` 保持心跳防止连接断开。
## 常见问题排查
连接失败时,`-v` / `-vv` / `-vvv` 是第一工具,逐级增加调试信息量。
```bash
ssh -vvv -J jump@jump-host target@target-host
```
**常见错误及对策**:
- **`Permission denied (publickey)`**:密钥未正确配置。检查 `ssh-add -l` 是否加载了对应密钥,目标服务器的 `~/.ssh/authorized_keys` 是否包含公钥,文件权限是否正确(目录 700,authorized_keys 600)
- **`Connection refused`**:目标 SSH 服务未运行或防火墙阻止。检查 `systemctl status sshd` 和端口开放情况
- **`open failed: connect failed`**:端口转发目标不可达。从跳板机上 `telnet 目标 端口` 确认能否连通
- **`Broken pipe`**:连接空闲超时断开。配置 `ServerAliveInterval 60` 和 `ServerAliveCountMax 3` 保持心跳
- **配置文件不生效**:`~/.ssh/config` 的缩进必须用空格不能用 Tab;`~/.ssh/` 目录权限 700;`config` 文件权限 644
服务端5月28日 00:52
如何使用 SSH 进行自动化运维?有哪些常用的自动化工具和脚本?SSH 自动化运维是后端工程师和运维人员的高频工作场景。无论是批量部署、定时巡检还是故障恢复,SSH 都是底层通道。下面从工具选型、实战脚本到生产注意事项,系统梳理 SSH 自动化运维的核心知识。
## 核心答案:SSH 自动化运维的常用工具
SSH 自动化运维工具按复杂度可分为三层:
- **轻量脚本层**:Shell 脚本 + SSH 原生命令,适合简单批量操作
- **编程封装层**:Fabric、Paramiko、Pexpect,适合需要逻辑判断的任务
- **配置管理层**:Ansible、SaltStack(SSH 模式),适合大规模集群管理
选型依据:服务器规模 < 10 台用 Shell 脚本即可;10-100 台用 Fabric;100+ 台上 Ansible。三者都基于 SSH 协议,无需在目标机器装代理。
## Ansible:最主流的 SSH 自动化工具
Ansible 是当前使用最广泛的 SSH 自动化配置管理工具,核心优势是无代理(Agentless),通过 SSH 连接目标机器执行任务。
### 安装与连接配置
```bash
# 安装
pip install ansible
# 配置主机清单 /etc/ansible/hosts
[webservers]
web1.example.com
web2.example.com
[dbservers]
db1.example.com
[all:vars]
ansible_user=admin
ansible_ssh_private_key_file=~/.ssh/ansible_key
ansible_ssh_common_args=-o StrictHostKeyChecking=no
```
连接验证:
```bash
ansible all -m ping
```
### Playbook 实战:Nginx 部署
```yaml
# deploy_nginx.yml
---
- hosts: webservers
become: yes
tasks:
- name: Install nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Deploy config
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: reload nginx
- name: Ensure nginx is running
service:
name: nginx
state: started
enabled: yes
handlers:
- name: reload nginx
service:
name: nginx
state: reloaded
```
执行:`ansible-playbook deploy_nginx.yml`
### Ansible 的关键概念
- **幂等性**:同一个 Playbook 执行多次,结果一致。`apt: state=present` 已安装则跳过,不会重复安装
- **Handler**:只在被 notify 触发时执行,且在所有 task 结束后统一执行,适合重启服务这类操作
- **变量与模板**:Jinja2 模板渲染配置文件,不同环境用不同变量文件(`group_vars/`、`host_vars/`)
## Fabric:Python 族的 SSH 自动化利器
Fabric 是基于 Paramiko 的 Python 库,用 Python 函数定义任务,适合需要条件判断、异常处理的自动化场景。
### 安装与基本用法
```bash
pip install fabric
```
```python
# fabfile.py
from fabric import Connection, task
@task
def deploy(c):
"""Deploy app to remote server"""
conn = Connection('user@web1.example.com')
conn.run('cd /var/www/myapp && git pull origin main')
conn.run('pip install -r requirements.txt')
conn.sudo('systemctl restart myapp')
@task
def check_disk(c, host):
"""Check remote disk usage"""
conn = Connection(f'admin@{host}')
result = conn.run('df -h / --output=pcent | tail -1', hide=True)
usage = int(result.stdout.strip().replace('%', ''))
if usage > 80:
print(f"WARNING: {host} disk usage {usage}%")
else:
print(f"OK: {host} disk usage {usage}%")
```
运行:`fab deploy` 或 `fab check_disk --host=web1.example.com`
### Fabric 的适用场景
Fabric 的优势在于你可以用 Python 写任意逻辑:根据返回值分支、调用 API、发送通知。这在 Shell 脚本里会很啰嗦。适合中小规模(几十台)的运维自动化,尤其是团队已经用 Python 的场景。
## Shell 脚本:最直接的 SSH 批处理方式
不想引入额外依赖时,Shell 脚本 + SSH 是最简方案。
### 并行批量执行
```bash
#!/bin/bash
# parallel_ssh.sh
SERVERS=(
"admin@web1.example.com"
"admin@web2.example.com"
"admin@web3.example.com"
)
SSH_OPTS="-i ~/.ssh/batch_key -o ConnectTimeout=10 -o StrictHostKeyChecking=no"
for server in "${SERVERS[@]}"; do
ssh $SSH_OPTS "$server" "$1" &
done
wait
echo "All done."
```
使用:`./parallel_ssh.sh "uptime"` 或 `./parallel_ssh.sh "systemctl restart nginx"`
### 带错误处理和日志的脚本
```bash
#!/bin/bash
# batch_deploy.sh
set -euo pipefail
LOG="/var/log/batch_deploy.log"
SERVERS_FILE="servers.txt"
BRANCH="${1:-main}"
log() { echo "[$(date '+%F %T')] $1" | tee -a "$LOG"; }
failed=0
while read -r server; do
log "Deploying to $server (branch=$BRANCH)..."
if ssh -o ConnectTimeout=10 "$server" bash -s <<DEPLOY
set -e
cd /var/www/app
git fetch origin && git reset --hard "origin/$BRANCH"
npm ci && npm run build
pm2 restart app
DEPLOY
then
log "SUCCESS: $server"
else
log "FAILED: $server"
((failed++))
fi
done < "$SERVERS_FILE"
log "Finished. Failed: $failed"
[ $failed -eq 0 ] || exit 1
```
### 用 SSH ControlMaster 加速连接
每次 SSH 都要建 TCP 连接和密钥交换,批量操作时开销很大。开启 ControlMaster 复用连接:
```bash
# ~/.ssh/config
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
```
```bash
mkdir -p ~/.ssh/sockets
```
首次连接建立 socket,后续连接复用同一 socket,速度提升明显。
## Pexpect:处理交互式 SSH 会话
有些 SSH 操作需要交互(输入密码、确认 yes/no),Pexpect 可以自动化这些交互。
```python
import pexpect
def ssh_with_password(host, user, password, command):
"""Auto-handle password prompt in SSH session"""
child = pexpect.spawn(f'ssh {user}@{host}', timeout=30)
i = child.expect(['password:', 'Are you sure you want to continue connecting'])
if i == 1:
child.sendline('yes')
child.expect('password:')
child.sendline(password)
child.expect(r'\$')
child.sendline(command)
child.expect(r'\$')
print(child.before.decode().strip())
child.sendline('exit')
child.close()
```
注意:生产环境应优先使用密钥认证而非密码。Pexpect 更适合测试环境或遗留系统的临时自动化。
## Paramiko:纯 Python SSH 库
Paramiko 是 Fabric 的底层依赖,也可以直接使用,适合需要精细控制 SSH 连接的场景。
```python
import paramiko
def batch_check(servers, command):
"""Batch execute command and collect results"""
results = {}
for server in servers:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect(server['host'], username=server['user'],
key_filename=server.get('key'))
stdin, stdout, stderr = ssh.exec_command(command)
results[server['host']] = stdout.read().decode().strip()
except Exception as e:
results[server['host']] = f"ERROR: {e}"
finally:
ssh.close()
return results
servers = [
{'host': 'web1.example.com', 'user': 'admin', 'key': '~/.ssh/id_ed25519'},
{'host': 'web2.example.com', 'user': 'admin', 'key': '~/.ssh/id_ed25519'},
]
print(batch_check(servers, 'uptime'))
```
## 生产环境的安全加固
SSH 自动化的安全是重中之重,以下是必须做的加固措施。
### 密钥认证 + 最小权限
```bash
# 生成专用密钥(不要用默认 id_rsa)
ssh-keygen -t ed25519 -f ~/.ssh/automation_key -C "automation@deploy"
# 在目标机器限制密钥权限
# ~/.ssh/authorized_keys
command="/usr/local/bin/automation-wrapper.sh",no-port-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAAC3...
```
`command=` 限制该密钥只能执行指定脚本,即使密钥泄露也无法执行任意命令。
### SSH 服务端配置
```bash
# /etc/ssh/sshd_config 关键配置
PermitRootLogin no # 禁止 root 登录
PasswordAuthentication no # 禁用密码认证
MaxAuthTries 3 # 限制尝试次数
AllowUsers admin deploy # 白名单用户
ClientAliveInterval 300 # 空闲超时
ClientAliveCountMax 2 # 超时次数
```
### 跳板机/堡垒机架构
生产环境不应让自动化脚本直连服务器,而应通过跳板机:
```bash
# ~/.ssh/config 配置跳板机
Host bastion
HostName jump.example.com
User jump_user
Host web-*
ProxyJump bastion
User admin
```
这样所有连接都经过审计和管控。
## 工具对比与选型
| 维度 | Shell 脚本 | Fabric | Ansible |
|------|-----------|--------|---------|
| 学习成本 | 低 | 中(需会 Python) | 中(需学 YAML + 模块) |
| 适用规模 | <10 台 | 10-100 台 | 100+ 台 |
| 幂等性 | 需手动保证 | 需手动保证 | 内置支持 |
| 错误处理 | set -e 粗粒度 | Python try/except | 模块级失败检测 |
| 配置管理 | 不支持 | 不支持 | 完整支持 |
| 依赖 | 无 | Python + pip | Python + pip |
| 生态 | 无 | 一般 | 丰富(Galaxy) |
选型建议:个人项目或临时任务用 Shell;Python 团队做部署自动化用 Fabric;企业级集群管理用 Ansible。三者可以混合使用,不是互斥关系。
## 自动化运维实战场景
### 批量部署
多台服务器同时部署应用是最常见的 SSH 自动化场景。用 Ansible 实现时,Playbook 天然支持批量执行和回滚:
```yaml
# app_deploy.yml
---
- hosts: webservers
become: yes
serial: 2 # 每次部署2台,降低风险
tasks:
- name: Create backup
archive:
path: /var/www/app
dest: "/backup/app_{{ ansible_date_time.iso8601 }}.tar.gz"
- name: Pull latest code
git:
repo: https://github.com/user/app.git
dest: /var/www/app
version: "{{ deploy_version | default('main') }}"
- name: Install dependencies
command: npm ci
args:
chdir: /var/www/app
- name: Build and restart
shell: npm run build && pm2 restart app
args:
chdir: /var/www/app
```
`serial: 2` 实现滚动部署,避免所有服务器同时不可用。如果前两台部署失败,后续不会继续。
### 定时巡检与告警
用 cron + Shell 脚本实现轻量巡检,异常时通过企业微信或钉钉推送告警:
```bash
#!/bin/bash
# health_check.sh
WEBHOOK_URL="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"
SERVERS=("web1" "web2" "db1")
for server in "${SERVERS[@]}"; do
# 检查 CPU 负载
load=$(ssh -o ConnectTimeout=5 "$server" "cat /proc/loadavg | awk '{print \$1}'")
threshold=4.0
if (( $(echo "$load > $threshold" | bc -l) )); then
curl -s "$WEBHOOK_URL" -H 'Content-Type: application/json' \
-d "{\"content\":\"ALERT: $server load=$load (> $threshold)\"}"
fi
# 检查磁盘
disk_pct=$(ssh -o ConnectTimeout=5 "$server" "df -h / | awk 'NR==2{print \$5}' | tr -d '%'")
if [ "$disk_pct" -gt 85 ]; then
curl -s "$WEBHOOK_URL" -H 'Content-Type: application/json' \
-d "{\"content\":\"ALERT: $server disk=${disk_pct}%\"}"
fi
done
```
配合 cron 每分钟执行:`* * * * * /opt/scripts/health_check.sh`
### 自动备份与清理
```bash
#!/bin/bash
# auto_backup.sh
set -euo pipefail
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d)
RETENTION=7
for server in db1 db2; do
ssh admin@$server "mysqldump -u root --all-databases --single-transaction | gzip" \
> "$BACKUP_DIR/${server}_${DATE}.sql.gz"
done
# 保留最近7天备份
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$RETENTION -delete
echo "[$(date)] Backup completed" >> /var/log/backup.log
```
`--single-transaction` 保证 InnoDB 备份一致性,不锁表。
## 自动化运维的常见踩坑
**连接超时导致脚本卡死**:SSH 默认没有连接超时,批量操作时一台机器卡住整个脚本就挂了。解决:始终设置 `ConnectTimeout`,Shell 用 `timeout` 命令包裹,Python 用 `paramiko.SSHClient().connect(timeout=10)`。
**known_hosts 拒绝连接**:首次连接新机器时 SSH 会要求确认指纹,自动化脚本会卡住。解决:`StrictHostKeyChecking=no`(测试环境)或在跳板机上预置 known_hosts。
**并发连接数过多被拒**:目标机器的 `MaxStartups` 限制了并发 SSH 连接数。Ansible 默认 5 个 fork,可通过 `-f` 调整。大批量操作建议分批执行。
**密码写死在脚本里**:这是最常见的安全隐患。应该用密钥认证,密钥用 ssh-agent 管理,不写进代码。
**非幂等操作导致重复执行**:比如部署脚本每次都执行数据库迁移,可能导致重复操作。Ansible 的模块天生幂等,Shell/Fabric 需要自己判断状态再执行。
SSH 自动化运维从 Shell 脚本到 Ansible,复杂度递增但能力也递增。掌握工具选型、安全加固和常见陷阱,才能在生产环境用得稳、用得安全。服务端5月28日 00:52
什么是 SSH 协议?它有哪些主要功能和工作原理?SSH(Secure Shell)是一种加密网络协议,用于在不安全的网络中安全地进行远程登录和其他网络服务。它替代了 Telnet、FTP 等明文传输协议,是目前 Linux/Unix 远程管理的标准工具。
## 核心功能
- **远程登录**:通过加密通道登录远程服务器执行命令
- **文件传输**:SFTP 和 SCP 提供安全的文件收发
- **端口转发**:建立加密隧道,将本地或远程端口流量通过 SSH 隧道转发,实现安全代理
- **X11 转发**:在本地显示远程图形界面应用
## 工作原理
SSH 采用客户端-服务器架构,一次完整的连接建立分为五个阶段:
1. **版本协商**:客户端连接服务器 22 端口后,双方交换版本号,协商使用 SSHv1 还是 SSHv2。当前生产环境应统一使用 SSHv2,SSHv1 已存在已知安全漏洞。
2. **算法协商**:双方交换各自支持的算法列表,按优先级选出共同支持的最强算法,包括密钥交换算法(ECDH、Diffie-Hellman)、对称加密算法(AES-256-GCM、ChaCha20-Poly1305)、公钥算法(RSA、ECDSA、Ed25519)和 HMAC 算法。
3. **密钥交换**:通过 Diffie-Hellman 或 ECDH 算法,双方在不直接传输密钥的情况下协商出相同的会话密钥(Session Key),后续所有通信都用该密钥加密。此阶段还会生成会话 ID,用于后续认证过程。
4. **身份认证**:客户端向服务器证明自己的身份,主要两种方式:
- **密码认证**:直接输入用户名密码,简单但易被暴力破解
- **公钥认证**:客户端持有私钥,服务器持有对应公钥。客户端用私钥签名一段数据,服务器用公钥验证。安全性更高,推荐生产使用
5. **会话交互**:认证通过后客户端请求建立会话,服务器分配资源,双方开始加密通信。
## 关键安全机制
- **主机密钥验证**:首次连接时服务器发送主机公钥,客户端将其存入 known_hosts 文件。后续连接时核对该公钥,若变化则发出警告,防止中间人攻击
- **前向保密**:SSHv2 使用临时密钥交换,即使长期私钥泄露,历史会话密钥也无法被推算
- **完整性校验**:每个数据包附带 HMAC,确保传输过程中数据未被篡改
## 端口转发的三种类型
- **本地转发**(-L):将本地端口映射到远程服务器可达的某个地址,例如将本地 3306 端口安全访问内网数据库
- **远程转发**(-R):将远程端口映射回本地,常用于内网穿透
- **动态转发**(-D):创建 SOCKS 代理,按需转发流量
## 常用命令
```bash
# 基本连接
ssh user@hostname
# 指定端口
ssh -p 2222 user@hostname
# 使用密钥认证
ssh -i ~/.ssh/id_ed25519 user@hostname
# 本地端口转发
ssh -L 8080:localhost:80 user@remote
# SCP 文件传输
scp file.txt user@hostname:/tmp/
```
## 生产环境安全加固
- 禁用密码登录,仅允许公钥认证(PasswordAuthentication no)
- 禁用 root 直接登录(PermitRootLogin no)
- 更换默认端口减少扫描
- 使用 Ed25519 替代 RSA 生成密钥对(更短更安全)
- 配置 AllowUsers 限制可登录用户
SSH 的安全性建立在加密通信和密钥验证之上,理解其连接建立过程和认证机制是运维和后端面试的高频考点。服务端5月28日 00:51
什么是 SSH 连接复用?如何配置和使用连接复用提高性能?SSH 连接复用(Connection Multiplexing)是指复用一条已建立的 SSH 连接来创建新的会话,省去重复的 TCP 握手和密钥交换环节。面试中常考的是三个配置参数:ControlMaster、ControlPath、ControlPersist,以及复用带来的性能收益和潜在风险。
## 连接复用怎么工作
正常的 SSH 连接每次都要经历 TCP 三次握手、SSH 协议版本协商、Diffie-Hellman 密钥交换、用户认证四个阶段。在延迟较高的网络环境(如跨机房、通过跳板机)中,这个过程可能耗时 1-3 秒。
连接复用的做法是:第一次连接建立后,把这条连接作为"主连接"(master)保持在后台,后续对同一目标的连接直接通过 Unix 域套接字(ControlPath)复用主连接,跳过握手和认证,几乎瞬间完成。
```
首次连接: 客户端 --TCP握手--> --密钥交换--> --认证--> 建立主连接
复用连接: 客户端 --通过套接字--> 直接复用主连接(毫秒级)
```
这带来的性能差异在脚本批量执行远程命令时尤为明显——10 次连接从 15 秒降到 1 秒以内是常见的。
## 三个核心配置参数
### ControlMaster
决定是否启用复用以及复用的行为:
- `no`:禁用(默认值)
- `auto`:如果已有主连接就复用,没有就创建新的——最常用的选项
- `yes`:强制创建主连接,如果已存在则失败
- `ask`:复用前询问用户确认
### ControlPath
指定 Unix 域套接字文件的路径。支持的占位符:
- `%r` — 远程用户名
- `%h` — 主机名
- `%p` — 端口号
- `%C` — 连接参数的 SHA1 哈希(推荐,避免路径过长)
```bash
# 常见写法
ControlPath ~/.ssh/cm-%r@%h:%p
# 使用 %C 避免路径超长(macOS 上常见问题)
ControlPath ~/.ssh/cm-%C
```
套接字文件路径有长度限制(通常 104-108 字节),路径太长会导致复用失败。使用 `%C` 可以规避这个问题。
### ControlPersist
控制主连接在最后一个会话关闭后继续存活的时间:
- `no`:最后一个会话断开后立即关闭主连接
- `yes`:永久保持,直到手动关闭或网络中断
- `600` / `10m`:保持 10 分钟
- `4h`:保持 4 小时
对于日常开发,`10m` 到 `1h` 是比较合理的范围。设成 `yes` 会导致主连接进程一直驻留,不推荐。
## 最小可用配置
```bash
# ~/.ssh/config
Host *
ControlMaster auto
ControlPath ~/.ssh/cm-%C
ControlPersist 10m
```
三行配置即可生效。建好配置后,第一次 `ssh user@server` 正常连接,第二次起就能感知到速度差异。
如果只想对特定主机启用:
```bash
Host prod-*
ControlMaster auto
ControlPath ~/.ssh/cm-%C
ControlPersist 30m
```
## 管理复用连接
```bash
# 检查主连接是否存活
ssh -O check user@server
# 主动关闭主连接(所有复用会话也会断开)
ssh -O exit user@server
# 停止接受新的复用请求(已有会话不受影响)
ssh -O stop user@server
# 查看控制套接字文件
ls -l ~/.ssh/cm-*
```
网络中断后,残留的套接字文件会导致新连接报错 `Control socket connect: Connection refused`。直接删除即可:
```bash
rm -f ~/.ssh/cm-*
# 或只删除特定主机的
find ~/.ssh -name "cm-*" -type s -mtime +1 -delete
```
## 哪些场景收益最大
**批量远程命令执行**——在脚本中循环 `ssh user@server "cmd"`,复用后只有第一次有连接延迟。
**Git over SSH**——`git push` / `git pull` / `git fetch` 走 SSH 时自动受益,频繁提交代码的开发者体感明显。
**跳板机 / ProxyJump**——通过跳板机连接目标机器时,到跳板机的连接可以复用。当 `~/.ssh/config` 中配置了 `ProxyJump` 时,ControlMaster 对跳板机连接同样生效:
```bash
Host bastion
HostName jump.example.com
ControlMaster auto
ControlPath ~/.ssh/cm-%C
ControlPersist 10m
Host internal-*
ProxyJump bastion
# internal-* 的连接也会触发 bastion 的复用
```
**rsync / scp 文件传输**——底层走 SSH,同样能复用连接。
## 需要注意的风险
**单点故障**:主连接断开时,所有复用它的会话同时失效。在长时运行的会话中(如远程编译、持续部署),这意味着一个网络抖动可能打掉所有窗口。
**资源泄漏**:ControlPersist 设为 `yes` 时,主连接的 ssh 进程会一直驻留。长时间运行后可能积累大量僵尸进程和套接字文件。建议设定具体时间。
**权限风险**:控制套接字文件如果权限不当,同一台机器上的其他用户可能劫持你的 SSH 会话。确保 `~/.ssh/` 目录权限为 `700`。
**与某些 SSH 功能冲突**:`-W`(netcat 模式)、`-J`(ProxyJump 的命令行形式)在特定版本下与 ControlMaster 存在兼容性问题,遇到问题时可以先 `ssh -O exit` 清除主连接再试。
## 面试追问参考
**Q: ControlMaster 设为 auto 和 yes 有什么区别?**
`auto` 在没有主连接时自动创建,有则复用;`yes` 强制要求自己成为主连接,如果已有主连接则连接失败。`yes` 适合脚本中明确需要"我是第一个连接"的场景。
**Q: 连接复用会带来安全风险吗?**
会。控制套接字本质上是一个 Unix 域套接字,本地有权限的用户理论上可以通过它建立 SSH 会话。所以必须保证套接字路径的目录权限正确(700),不要放在 `/tmp` 等公共目录。
**Q: 主连接断了怎么办?**
所有复用该主连接的会话都会立即断开。需要删除残留的套接字文件后重新建立连接。这也是为什么不建议在生产环境的关键操作中过度依赖复用。
服务端5月28日 00:49
什么是 SSH 证书认证?如何配置和管理 SSH 证书?SSH 证书认证用 CA(证书颁发机构)对用户或主机公钥进行签名,生成带有效期和身份信息的证书,服务器只需信任 CA 公钥即可验证所有由该 CA 签发的证书。相比手动分发 authorized_keys,证书方式在大规模环境下管理成本更低、安全性更强。
## 为什么用证书而不是密钥?
传统 SSH 密钥认证的痛点在于:每台服务器都要维护 authorized_keys 文件,用户入职要在所有服务器上添加公钥,离职要逐台删除——服务器一多就是运维噩梦。证书认证从根本上解决了这个问题:
- **服务器不存用户公钥**,只配置一条 `TrustedUserCAKeys` 指向 CA 公钥
- **证书自带有效期**,到期自动失效,不存在"永不过期的密钥"
- **撤销只需更新列表**,不用逐台机器删 authorized_keys
- **可限制权限**,比如只允许从特定 IP 连接、只能执行指定命令
Meta、Uber、Google 等公司内部都在用 SSH 证书方案管理数万台服务器的访问权限。
## 证书认证的工作原理
核心流程只有三步:
1. **建立 CA**:生成一对 CA 密钥,私钥严格保管(建议离线存储或用 HSM),公钥分发到所有需要信任该 CA 的服务器
2. **签发证书**:用 CA 私钥对用户的公钥签名,生成包含身份标识(Key ID)、授权主体(Principals)、有效期等信息的证书文件
3. **验证连接**:用户 SSH 连接时出示证书,服务器用本地配置的 CA 公钥验证签名,检查有效期和 Principals 后放行
## 搭建 CA 并签发用户证书
### 生成 CA 密钥对
```bash
# 用户 CA(用于签发用户证书)
ssh-keygen -t ed25519 -f /etc/ssh/ca_user_key -C "User CA"
# 主机 CA(用于签发主机证书,可选)
ssh-keygen -t ed25519 -f /etc/ssh/ca_host_key -C "Host CA"
```
CA 私钥权限必须设为 600,且只在签发证书时使用。生产环境建议将 CA 私钥存放在离线机器或 HSM 中。
### 签发用户证书
```bash
ssh-keygen -s /etc/ssh/ca_user_key \
-I "user_zhangsan" \
-n "zhangsan" \
-V +52w \
-z 1 \
~/.ssh/zhangsan_key.pub
```
参数含义:
- `-I`:证书的身份标识,用于日志审计,建议用 `user_用户名` 格式
- `-n`:Principals,允许登录的系统用户名,多个用逗号分隔
- `-V`:有效期,`+52w` 表示 52 周,也可写 `+365d`、`20240101-20250101` 等
- `-z`:证书序列号,用于撤销时定位,每次签发应递增
签发后生成 `zhangsan_key-cert.pub` 文件,用户连接时需同时持有私钥和此证书文件。
### 签发主机证书
主机证书解决的是"首次连接时如何确认服务器身份"的问题,避免中间人攻击和 `known_hosts` 的手动维护。
```bash
ssh-keygen -s /etc/ssh/ca_host_key \
-I "host_web01" \
-h \
-n "web01.example.com,10.0.1.50" \
-V +52w \
/etc/ssh/ssh_host_ed25519_key.pub
```
`-h` 标志区分主机证书和用户证书。
## 服务器端配置
### 信任用户 CA
在 `/etc/ssh/sshd_config` 中添加:
```bash
# 信任用户 CA,所有由该 CA 签发的用户证书均被接受
TrustedUserCAKeys /etc/ssh/ca_user_key.pub
# 可选:限制每个用户可用的 Principals
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
# 确保公钥认证开启
PubkeyAuthentication yes
```
`AuthorizedPrincipalsFile` 的作用是限制哪些 Principal 可以映射到当前系统用户。比如 `/etc/ssh/auth_principals/root` 文件内容为 `admin ops`,那么只有证书中 Principals 包含 `admin` 或 `ops` 的才能以 root 登录。
### 部署主机证书
```bash
# 确认主机证书文件在位
ls /etc/ssh/ssh_host_ed25519_key-cert.pub
# 在 sshd_config 中指定主机证书
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
# 重启 SSH 服务
systemctl restart sshd
```
**重要**:修改 sshd_config 后务必在另一个终端保留当前连接,先用新终端测试证书登录成功再关闭旧连接,防止配置错误锁死自己。
## 客户端配置
### 使用证书连接
证书文件和私钥放在同一目录,文件名遵循 OpenSSH 约定(私钥 `id_ed25519`,证书 `id_ed25519-cert.pub`)时无需额外配置:
```bash
ssh zhangsan@web01.example.com
```
如果证书文件名不是默认约定,可以手动指定:
```bash
# 命令行指定
ssh -i ~/.ssh/zhangsan_key -o CertificateFile=~/.ssh/zhangsan_key-cert.pub zhangsan@web01
# 或写入 ~/.ssh/config
Host web01
HostName web01.example.com
User zhangsan
IdentityFile ~/.ssh/zhangsan_key
CertificateFile ~/.ssh/zhangsan_key-cert.pub
```
### 信任主机 CA
在 `~/.ssh/known_hosts` 中添加一行:
```
@cert-authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
```
这表示所有 `*.example.com` 域名下由该 CA 签发的主机证书都被信任,不再需要逐台确认指纹。
## 证书撤销
### 撤销列表(RevokedKeys)
SSH 证书的撤销不像 TLS 那样有 OCSP/CRL 协议,而是使用一个简单的密钥列表文件。
```bash
# 创建撤销列表文件,每行一个要撤销的公钥
cat > /etc/ssh/revoked_keys << 'EOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... compromised_key
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... terminated_user
EOF
# 也可以直接从证书文件生成撤销条目
ssh-keygen -k -f /etc/ssh/revoked_keys ~/.ssh/zhangsan_key-cert.pub
```
然后在 `sshd_config` 中配置:
```bash
RevokedKeys /etc/ssh/revoked_keys
```
**注意**:`RevokedKeys` 文件中存放的是公钥或证书的原始内容,不是序列号。这是常见的误区——OpenSSH 的撤销列表不是 `serial:reason` 格式,而是标准的公钥列表格式。每次撤销后需重启或 reload sshd 才能生效。
### 更实用的做法:缩短有效期
与其维护撤销列表,不如一开始就把用户证书有效期设短(如 24 小时),配合自动化签发服务(如 HashiCorp Vault),用户每次连接前自动获取新证书。证书天然过期,撤销列表的维护压力就小很多。
## 高级权限控制
### 限制证书能力
```bash
# 签发受限证书:只能执行部署脚本
ssh-keygen -s /etc/ssh/ca_user_key \
-I "deploy_ci" \
-n "deploy" \
-V +1d \
-O clear \
-O no-port-forwarding \
-O no-X11-forwarding \
-O force-command=/usr/local/bin/deploy.sh \
-O source-address=10.0.0.0/8 \
~/.ssh/deploy_key.pub
```
关键选项:
- `-O clear`:清除所有默认权限(包括 pty、port-forwarding 等),之后再逐项添加需要的权限
- `-O force-command=...`:限制只能执行指定命令,适合 CI/CD 场景
- `-O source-address=...`:限制来源 IP 段
- `-O no-pty`:禁止分配终端,适合自动化脚本
### 按角色签发不同证书
```bash
# 管理员:完整权限,有效期较长
ssh-keygen -s $CA_KEY -I "admin_lisi" -n "root,lisi" -V +4w \
-O permit-pty admin_key.pub
# 只读巡检:无 pty,只能看
ssh-keygen -s $CA_KEY -I "readonly_wangwu" -n "readonly" -V +1w \
-O no-pty readonly_key.pub
# CI/CD 部署:限定命令和 IP
ssh-keygen -s $CA_KEY -I "cicd_gitlab" -n "deploy" -V +1h \
-O clear -O force-command=/usr/local/bin/deploy.sh \
-O source-address=10.1.0.0/16 cicd_key.pub
```
## 证书查看与审计
```bash
# 查看证书详细信息
ssh-keygen -L -f ~/.ssh/zhangsan_key-cert.pub
```
输出示例:
```
Type: ssh-ed25519-cert-v01@openssh.com user certificate
Public key: ED25519-CERT SHA256:abc123...
Signing CA: ED25519 SHA256:def456... (using ssh-ed25519)
Key ID: "user_zhangsan"
Serial: 1
Valid: from 2024-01-15T10:00:00 to 2025-01-13T10:00:00
Principals:
zhangsan
Critical Options: (none)
Extensions:
permit-X11-forwarding
permit-agent-forwarding
permit-port-forwarding
permit-pty
permit-user-rc
```
通过 Key ID 和 Serial 可以追踪证书签发记录,配合日志系统实现访问审计。
## 与 HashiCorp Vault 集成
手动签发证书在小型环境可行,但用户多了需要自动化。Vault 的 SSH Secrets Engine 可以按需签发短期证书:
```bash
# 启用 SSH secrets engine
vault secrets enable ssh
# 配置 CA
vault write ssh/config/ca generate_signing_key=true
# 配置角色:开发环境,1 小时有效期
vault write ssh/roles/dev \
key_type=ca \
allowed_users="*" \
default_user="dev" \
ttl="1h"
# 用户申请证书
vault write -field=signed_key ssh/sign/dev \
public_key=@$HOME/.ssh/id_ed25519.pub \
> $HOME/.ssh/id_ed25519-cert.pub
```
Vault 的优势:证书有效期短(通常 1 小时到 1 天),每次按需签发,自动记录审计日志,无需手动维护 CA 私钥的安全。
## 常见问题排查
**连接时提示 `Permission denied (publickey)`**
- 检查证书是否过期:`ssh-keygen -L -f cert.pub` 查看 Valid 字段
- 检查 Principals 是否匹配:证书中的 `-n` 值必须出现在目标用户的 AuthorizedPrincipalsFile 中
- 检查证书是否被撤销:查看服务器 `RevokedKeys` 文件
- 检查 sshd 是否加载了 CA 公钥:`sshd -T | grep trustedusercakeys`
**首次连接仍提示确认指纹**
- 客户端 `known_hosts` 中的 `@cert-authority` 行未正确配置,或域名通配符不匹配
- 主机证书未在 `sshd_config` 中用 `HostCertificate` 指定
**证书签发后立即失效**
- `-V` 参数的时区问题:服务器时间与签发机器时间不一致
- 序列号冲突:同一序列号签发多张证书可能导致问题
## 最佳实践总结
1. **CA 私钥离线保管**:只在签发时使用,日常不放在可达的网络中
2. **用户证书有效期不超过 1 天**:配合 Vault 等工具实现按需签发
3. **主机证书有效期可设 1 年**:主机证书变更频率低
4. **用 AuthorizedPrincipalsFile 做细粒度控制**:不同角色映射不同系统用户
5. **CI/CD 用 force-command 限定命令**:防止部署密钥被滥用
6. **保留密码登录作为兜底**:直到确认证书方案完全跑通再关闭密码认证
7. **证书签发流程自动化**:手动签发容易出错且不可审计服务端5月28日 00:47
SSH 端口转发有哪些类型?本地转发、远程转发和动态转发怎么用?SSH 端口转发(Port Forwarding),也叫 SSH 隧道(SSH Tunneling),是通过 SSH 加密连接转发任意 TCP 流量的技术。它能让不安全的协议获得加密保护,也能穿透网络限制访问内网服务。SSH 端口转发有三种类型:本地转发(-L)、远程转发(-R)和动态转发(-D),三者数据流向和使用场景各不相同。
## 本地端口转发(-L)
本地端口转发将本地某个端口的流量,经 SSH 隧道转发到远程服务器可达的目标地址。换句话说,你访问本机的一个端口,数据会自动通过 SSH 加密隧道到达远端目标。
**数据流向**:本机应用 → 本地端口 → SSH 隧道 → SSH 服务器 → 目标主机:目标端口
```bash
# 语法
ssh -L [本地地址:]本地端口:目标主机:目标端口 用户@SSH服务器
# 访问远程 MySQL
ssh -L 3306:localhost:3306 user@remote-server
# 现在连接 localhost:3306 等同于连接远程服务器的 MySQL
# 通过跳板机访问内网服务
ssh -L 8080:192.168.1.50:80 user@jump-host
# 本机访问 localhost:8080 → jump-host 转发 → 192.168.1.50:80
```
注意 `-L` 后面的「目标主机:目标端口」是从 SSH 服务器的视角解析的,所以 `localhost` 指的是 SSH 服务器自身。这是理解本地转发的关键——目标地址是远端网络中的地址,不是你本机的地址。
**典型场景**:远程数据库只有内网可访问,你在外网通过 SSH 跳板机建立本地转发,即可用本地客户端直连远程数据库。
## 远程端口转发(-R)
远程端口转发与本地转发方向相反:把远程服务器上的某个端口流量,经 SSH 隧道转发回本机可达的目标地址。这在本地服务需要暴露给远程网络时使用。
**数据流向**:远程客户端 → 远程端口 → SSH 隧道 → 本机 → 目标主机:目标端口
```bash
# 语法
ssh -R [远程地址:]远程端口:目标主机:目标端口 用户@SSH服务器
# 让远程服务器能访问你本地的 Web 服务
ssh -R 8080:localhost:3000 user@public-server
# 他人访问 public-server:8080 → SSH 隧道 → 你本机的 localhost:3000
# 绑定到远程服务器的所有网络接口
ssh -R 0.0.0.0:8080:localhost:3000 user@public-server
```
远程转发默认只绑定到远程服务器的 127.0.0.1,外部无法连接。要让其他机器也能通过该端口访问,需要在远程服务器的 `/etc/ssh/sshd_config` 中设置 `GatewayPorts yes`,然后重启 sshd。
**典型场景**:本地开发了一个 Web 应用,需要临时让外部人员预览,但本机没有公网 IP。通过远程转发把本地服务映射到公网服务器的端口上,外部即可访问。
## 动态端口转发(-D)
动态端口转发在本地创建一个 SOCKS 代理端口,根据应用层协议动态决定流量转发目标。与本地转发只能指定一个固定目标不同,动态转发支持任意目标。
**数据流向**:本机应用(SOCKS 客户端)→ 本地 SOCKS 端口 → SSH 隧道 → SSH 服务器 → 任意目标
```bash
# 语法
ssh -D [本地地址:]本地端口 用户@SSH服务器
# 创建 SOCKS5 代理
ssh -D 1080 user@proxy-server
# 配置浏览器或系统代理为 socks5://127.0.0.1:1080
```
动态转发本质上把 SSH 服务器变成了一个代理服务器,所有通过 SOCKS5 协议发出的请求都由 SSH 服务器代为访问,再把结果加密返回。
**典型场景**:在不安全的网络环境中,通过 SSH 服务器代理所有流量,确保通信加密且无法被中间人窃听。
## 三种转发的区别与选择
| 对比项 | 本地转发 -L | 远程转发 -R | 动态转发 -D |
|--------|------------|------------|------------|
| 数据方向 | 本机→远端 | 远端→本机 | 本机→远端(动态目标) |
| 目标数量 | 固定一个 | 固定一个 | 任意多个 |
| 协议支持 | 任意 TCP | 任意 TCP | SOCKS5 代理 |
| 典型用途 | 访问内网服务 | 内网穿透 | 安全代理/翻墙 |
选择原则:访问特定内网服务用 -L,暴露本地服务用 -R,需要灵活代理多种目标用 -D。
## SSH 配置文件简化操作
频繁使用端口转发时,可以在 `~/.ssh/config` 中预设,避免每次输入长命令:
```
Host db-tunnel
HostName jump.example.com
User deploy
LocalForward 3306 db-server:3306
ServerAliveInterval 60
ServerAliveCountMax 3
Host dev-expose
HostName public.example.com
User deploy
RemoteForward 8080 localhost:3000
```
使用时只需 `ssh db-tunnel` 或 `ssh dev-expose`,转发规则自动生效。
## 常用参数组合
```bash
# -N: 不执行远程命令,只做端口转发
# -f: 后台运行
# -C: 启用压缩
# 后台运行本地转发
ssh -f -N -L 3306:db-server:3306 user@jump-host
# 保持连接不断
ssh -o ServerAliveInterval=60 -N -L 3306:localhost:3306 user@remote-server
# 使用 autossh 自动重连(适合持久化隧道)
autossh -M 0 -o ServerAliveInterval=60 -N -L 3306:localhost:3306 user@remote-server
```
`-N` 和 `-f` 是端口转发最常用的两个参数:`-N` 避免 SSH 打开一个不需要的 shell,`-f` 让隧道在后台运行不占用终端。
## 安全注意事项
1. **默认绑定 localhost**:-L 和 -D 默认只监听 127.0.0.1,不要随意改为 0.0.0.0,否则局域网内任何人都可能使用你的隧道
2. **GatewayPorts 慎开**:开启后远程转发的端口对公网可见,务必配合防火墙限制来源 IP
3. **禁用端口转发**:服务器可在 `sshd_config` 中设置 `AllowTcpForwarding no` 禁止所有端口转发,适用于只允许交互式登录的场景
4. **密钥认证优于密码**:端口转发往往配置为自动连接,使用密钥认证更安全且免输入密码
5. **审计活跃隧道**:定期检查服务器上的 SSH 转发连接,防止未授权的隧道
## 故障排查
```bash
# 确认端口是否在监听
ss -tlnp | grep 3306
# 测试隧道是否通畅
curl -x socks5://127.0.0.1:1080 http://目标地址 # 动态转发
telnet localhost 3306 # 本地转发
# 查看 SSH 连接日志
ssh -v -L 3306:localhost:3306 user@remote-server
# -v 参数会输出详细的连接过程,定位握手或认证问题
# 常见错误
# bind: Address already in use → 本地端口被占用,换一个端口
# Channel 3: open failed: connect failed → 目标地址从 SSH 服务器不可达
# Permission denied → SSH 认证失败,检查密钥或密码
```