Linux SSH 远程管理
Linux SSH 远程管理
简介
SSH(Secure Shell)是 Linux 系统远程管理的标准协议,由 IETF 制定,使用公钥加密技术保证通信安全。它替代了早期不安全的 Telnet、FTP 等明文协议,成为服务器运维、代码部署和自动化运维的基础工具。
SSH 不仅仅是一个远程登录工具,它还提供了端口转发(Port Forwarding)、X11 转发、SOCKS 代理、跳板机穿透等高级功能。掌握 SSH 的密钥认证、配置优化和安全加固,是每一个运维工程师和开发人员的必备技能。
本文将从密钥认证配置、SSH 客户端/服务端配置、端口转发、跳板机设置到安全加固,全面介绍 SSH 的使用方法。
SSH 协议基础
SSH 版本对比
| 特性 | SSH-1 | SSH-2 |
|---|---|---|
| 加密算法 | DES、3DES、Blowfish | AES、ChaCha20 |
| MAC 算法 | 无 | HMAC-SHA2 |
| 密钥交换 | 固定 | Diffie-Hellman |
| 安全性 | 已知漏洞,不推荐 | 安全,推荐使用 |
安全提示
SSH-1 协议存在已知安全漏洞,现代系统默认使用 SSH-2。确保服务端配置中 Protocol 2 生效,不要启用 SSH-1。
SSH 认证方式
| 认证方式 | 安全性 | 便利性 | 适用场景 |
|---|---|---|---|
| 密码认证 | 低(易被暴力破解) | 高 | 临时访问 |
| 公钥认证 | 高 | 中(需管理密钥) | 日常运维 |
| 键盘交互 | 中 | 中 | 二次验证 |
| GSSAPI | 高 | 高 | Kerberos 环境 |
密钥认证配置
生成密钥对
# 推荐使用 Ed25519 算法(现代、安全、密钥短)
ssh-keygen -t ed25519 -C "admin@example.com"
# 或者使用 RSA 4096 位(兼容性更好)
ssh-keygen -t rsa -b 4096 -C "admin@example.com"
# 指定密钥文件名(多密钥场景)
ssh-keygen -t ed25519 -C "admin@example.com" -f ~/.ssh/id_ed25519_work
# 生成密钥时的交互提示:
# Enter file in which to save the key: ← 回车使用默认路径
# Enter passphrase (empty for no passphrase): ← 设置密钥密码(可选但推荐)
# Enter same passphrase again: ← 确认密码Ed25519 vs RSA
| 特性 | Ed25519 | RSA-4096 |
|---|---|---|
| 密钥长度 | 256 位 | 4096 位 |
| 安全性 | 极高 | 高 |
| 性能 | 快(签名/验证) | 慢 |
| 兼容性 | SSH 6.5+(2014) | 所有 SSH 版本 |
| 推荐度 | 首选 | 兼容性需求时使用 |
分发公钥
# 方法1:使用 ssh-copy-id(推荐)
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server
# 指定端口
ssh-copy-id -i ~/.ssh/id_ed25519.pub -p 2222 user@server
# 方法2:手动复制
cat ~/.ssh/id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && chmod 700 ~/.ssh"
# 方法3:使用管道
ssh user@server "mkdir -p ~/.ssh"
scp ~/.ssh/id_ed25519.pub user@server:~/.ssh/
ssh user@server "cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys"权限设置(至关重要)
# 客户端权限
chmod 700 ~/.ssh # .ssh 目录权限必须为 700
chmod 600 ~/.ssh/id_ed25519 # 私钥权限必须为 600
chmod 644 ~/.ssh/id_ed25519.pub # 公钥权限 644
chmod 600 ~/.ssh/authorized_keys # authorized_keys 权限 600
chmod 644 ~/.ssh/known_hosts # known_hosts 权限 644
# 如果权限不对,SSH 会拒绝使用密钥
# 错误信息:Permissions 0644 for '/home/user/.ssh/id_ed25519' are too open.权限是安全的关键
SSH 对密钥文件权限非常严格。如果私钥文件权限不是 600(仅所有者可读写),SSH 会直接拒绝使用该密钥。这是 SSH 的安全设计,防止其他用户读取你的私钥。
使用 ssh-agent 管理密钥
# 启动 ssh-agent
eval "$(ssh-agent -s)"
# 添加密钥到 agent
ssh-add ~/.ssh/id_ed25519
# 如果密钥有密码,需要输入一次
# 之后在同一会话中使用该密钥不需要再输入密码
# 查看已加载的密钥
ssh-add -l
# 删除指定密钥
ssh-add -d ~/.ssh/id_ed25519
# 删除所有密钥
ssh-add -DSSH 客户端配置
~/.ssh/config 配置文件
通过 SSH 配置文件,可以为不同的服务器设置别名和连接参数,避免每次输入冗长的连接命令:
# ~/.ssh/config — 客户端配置
# ===== 开发服务器 =====
Host dev
HostName 192.168.1.100
User deploy
Port 22
IdentityFile ~/.ssh/id_ed25519
ForwardAgent yes
ServerAliveInterval 60
ServerAliveCountMax 3
TCPKeepAlive yes
# ===== 生产服务器 =====
Host prod
HostName 10.0.0.50
User admin
Port 2222
IdentityFile ~/.ssh/prod_key
StrictHostKeyChecking accept-new
# ===== Git 服务器 =====
Host github
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
Host gitlab
HostName gitlab.company.com
User git
IdentityFile ~/.ssh/id_ed25519_gitlab
ProxyCommand nc -X connect -x proxy.company.com:8080 %h %p
# ===== 跳板机配置 =====
Host bastion
HostName bastion.example.com
User jump
IdentityFile ~/.ssh/id_ed25519_bastion
Host internal-server
HostName 10.0.0.100
User admin
IdentityFile ~/.ssh/id_ed25519
ProxyJump bastion
# ===== 通配符配置(适用于同一网段的多个服务器) =====
Host web-*
User deploy
IdentityFile ~/.ssh/id_ed25519
StrictHostKeyChecking no
# 使用:ssh dev / ssh prod / ssh internal-server常用配置参数
| 参数 | 说明 | 示例 |
|---|---|---|
HostName | 服务器 IP 或域名 | 192.168.1.100 |
User | 登录用户名 | deploy |
Port | SSH 端口 | 2222 |
IdentityFile | 私钥文件路径 | ~/.ssh/id_ed25519 |
ProxyJump | 跳板机(SSH 7.3+) | bastion |
ProxyCommand | 跳板机命令 | nc -X connect -x proxy:8080 %h %p |
ForwardAgent | 转发 SSH Agent | yes |
ServerAliveInterval | 心跳间隔(秒) | 60 |
ServerAliveCountMax | 心跳超时次数 | 3 |
StrictHostKeyChecking | 主机密钥检查策略 | ask/yes/no/accept-new |
Compression | 启用压缩 | yes |
LogLevel | 日志级别 | ERROR |
SSH 端口转发
端口转发是 SSH 最强大的功能之一,它可以在不安全的网络中建立安全的加密隧道。
本地端口转发(-L)
将远程服务器上的服务映射到本地端口:
# 场景:通过跳板机访问内网 MySQL
# 本地 3306 端口 -> 跳板机 -> 内网 MySQL:3306
ssh -L 3306:127.0.0.1:3306 user@bastion -N
# 场景:访问远程服务器的 Web 界面
# 本地 8080 端口 -> 远程服务器的 80 端口
ssh -L 8080:localhost:80 user@server -N
# 场景:通过跳板机访问内网的另一台服务器
# 本地 3306 -> 跳板机 -> 内网数据库服务器(10.0.0.50):3306
ssh -L 3306:10.0.0.50:3306 user@bastion -N
# -N 参数:不执行远程命令,仅建立隧道
# -f 参数:后台运行
ssh -fNL 3306:10.0.0.50:3306 user@bastion远程端口转发(-R)
将本地服务映射到远程服务器的端口:
# 场景:让远程服务器访问本地开发环境
# 远程 8080 端口 -> 本地 3000 端口
ssh -R 8080:localhost:3000 user@server -N
# 场景:内网服务器对外暴露服务
# 远程 443 端口 -> 内网服务器的 443 端口
ssh -R 0.0.0.0:443:localhost:443 user@public-server -N远程端口转发安全风险
远程端口转发可能会在远程服务器上暴露本地服务。确保远程服务器的 GatewayPorts 设置合理,避免暴露到所有网络接口。
SOCKS 代理(-D)
建立动态端口转发,将本地端口作为 SOCKS 代理:
# 创建 SOCKS5 代理,监听本地 1080 端口
ssh -D 1080 user@server -N
# 配置浏览器使用 SOCKS5 代理
# 代理地址:socks5://127.0.0.1:1080
# 限制 SOCKS 代理只转发到指定网段
ssh -D 1080 user@server -NSSH 服务端安全加固
sshd_config 配置
# /etc/ssh/sshd_config
# ========== 基础安全 ==========
# 修改默认端口(减少被扫描的概率)
Port 2222
# 禁止 root 用户直接登录
PermitRootLogin no
# 禁用密码认证(只允许密钥认证)
PasswordAuthentication no
# 禁用空密码
PermitEmptyPasswords no
# ========== 认证配置 ==========
# 启用公钥认证
PubkeyAuthentication yes
# 最大认证尝试次数(防暴力破解)
MaxAuthTries 3
# 登录超时时间(秒)
LoginGraceTime 30
# ========== 用户限制 ==========
# 白名单用户(只允许指定用户登录)
AllowUsers deploy admin
# 或者按组限制
# AllowGroups ssh-users
# ========== 连接保活 ==========
# 心跳检测(防止连接因空闲断开)
ClientAliveInterval 300
ClientAliveCountMax 2
# ========== 会话限制 ==========
# 最大会话数
MaxSessions 5
# 最大未认证连接数
MaxStartups 3:50:10
# ========== 加密算法 ==========
# 推荐的加密算法
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
# 推荐的 MAC 算法
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# ========== 禁用不安全功能 ==========
# 禁用 X11 转发
X11Forwarding no
# 禁用端口转发(如不需要)
# AllowTcpForwarding no
# GatewayPorts no
# 禁用 .rhosts 文件
IgnoreRhosts yes
# 禁用主机认证
HostbasedAuthentication no
# ========== 日志 ==========
# 日志级别
LogLevel VERBOSE
# 打印 Banner 信息
Banner /etc/ssh/banner修改配置后重启 SSH 服务
# 检查配置文件语法(重要!修改前先检查)
sudo sshd -t
# 重启 SSH 服务
sudo systemctl restart sshd
# 如果修改了端口,确保防火墙也更新了
sudo firewall-cmd --add-port=2222/tcp --permanent
sudo firewall-cmd --reload修改 SSH 配置前的重要提醒
在修改 SSH 配置(尤其是端口和认证方式)之前,务必保持当前 SSH 会话不断开,另开一个终端测试新配置是否生效。如果新配置有问题导致无法登录,可以在原来的会话中回滚配置。
跳板机配置
单级跳板
# 使用 ProxyJump(SSH 7.3+,推荐)
ssh -J bastion internal-server
# 在 ~/.ssh/config 中配置
Host bastion
HostName bastion.example.com
User jump
Host internal-server
HostName 10.0.0.100
User admin
ProxyJump bastion
# 使用:ssh internal-server多级跳板
# 使用 ProxyJump 多级跳转
ssh -J bastion1,bastion2 internal-server
# 在 ~/.ssh/config 中配置
Host bastion1
HostName bastion1.example.com
User jump1
Host bastion2
HostName 10.0.0.50
User jump2
ProxyJump bastion1
Host internal-server
HostName 10.0.0.100
User admin
ProxyJump bastion2
# 使用:ssh internal-server通过跳板机传输文件
# 使用 scp 通过跳板机传输文件
scp -o ProxyJump=bastion local_file.txt user@internal-server:/remote/path/
# 使用 rsync 通过跳板机同步
rsync -avz -e "ssh -J bastion" ./local_dir/ user@internal-server:/remote_dir/
# 使用 sftp 通过跳板机
sftp -o ProxyJump=bastion user@internal-server常见问题排查
连接被拒绝
# 1. 检查 SSH 服务是否运行
systemctl status sshd
# 2. 检查端口是否监听
ss -tlnp | grep sshd
# 3. 检查防火墙
firewall-cmd --list-ports
# 4. 检查 SELinux
getenforce密钥认证失败
# 使用详细调试模式
ssh -vvv user@server
# 常见原因:
# 1. 权限不正确 → chmod 600 ~/.ssh/id_ed25519
# 2. authorized_keys 文件权限 → chmod 600 ~/.ssh/authorized_keys
# 3. .ssh 目录权限 → chmod 700 ~/.ssh
# 4. 服务端禁用了公钥认证 → 检查 PubkeyAuthentication yes
# 5. selinux 上下文问题 → restorecon -Rv ~/.ssh连接超时
# 检查网络连通性
ping server_ip
traceroute server_ip
# 检查端口是否可达
telnet server_ip 22
nc -zv server_ip 22
# 检查是否被防火墙拦截
# 查看服务端日志
journalctl -u sshd | tail -20连接空闲断开
# 解决方案1:在客户端配置中添加心跳
# ~/.ssh/config
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
# 解决方案2:在服务端配置中添加心跳
# /etc/ssh/sshd_config
ClientAliveInterval 300
ClientAliveCountMax 2SSH 高级技巧
SSH 多路复用(Connection Multiplexing)
SSH 多路复用允许通过一个 TCP 连接创建多个 SSH 会话,大幅减少重复连接的握手时间。
# ~/.ssh/config 中配置多路复用
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
# 创建 Socket 目录
mkdir -p ~/.ssh/sockets
chmod 700 ~/.ssh/sockets
# 第一次连接正常建立,后续连接复用同一 TCP 通道
ssh dev # 第一次 — 正常握手
ssh dev # 第二次 — 瞬间连接,无握手延迟
# 查看现有连接
ssh -O check dev
# 强制关闭主连接
ssh -O exit dev
# 转发端口到已有连接
ssh -O forward -L 3306:localhost:3306 devSSH ProxyJump 链路优化
# 使用 SSH Config 优化多级跳板
# ~/.ssh/config
# 使用 SSH 证书认证(企业环境)
Host *.internal.example.com
ProxyJump bastion.example.com
User admin
IdentityFile ~/.ssh/id_internal
# 跳板机复用连接
Host bastion.example.com
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
User jump
IdentityFile ~/.ssh/id_bastion
# 使用 SSH Agent 转发(在跳板机上使用本地密钥)
Host bastion
ForwardAgent yes
HostName bastion.example.comSSH 隧道自动化脚本
#!/bin/bash
# ssh-tunnel.sh — SSH 隧道管理脚本
TUNNEL_NAME="db-tunnel"
BASTION="bastion.example.com"
LOCAL_PORT=3306
REMOTE_HOST="10.0.0.50"
REMOTE_PORT=3306
PID_FILE="/tmp/ssh-tunnel-${TUNNEL_NAME}.pid"
start_tunnel() {
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
echo "隧道 $TUNNEL_NAME 已在运行 (PID: $(cat "$PID_FILE"))"
return 1
fi
ssh -fNL "${LOCAL_PORT}:${REMOTE_HOST}:${REMOTE_PORT}" \
-o ServerAliveInterval=60 \
-o ServerAliveCountMax=3 \
-o ExitOnForwardFailure=yes \
"$BASTION"
# 查找 SSH 进程 PID
pgrep -f "ssh -fNL ${LOCAL_PORT}:${REMOTE_HOST}:${REMOTE_PORT}" > "$PID_FILE"
echo "隧道 $TUNNEL_NAME 已启动 (PID: $(cat "$PID_FILE"))"
echo "本地 ${LOCAL_PORT} -> ${BASTION} -> ${REMOTE_HOST}:${REMOTE_PORT}"
}
stop_tunnel() {
if [ -f "$PID_FILE" ]; then
kill "$(cat "$PID_FILE")" 2>/dev/null
rm -f "$PID_FILE"
echo "隧道 $TUNNEL_NAME 已停止"
else
echo "隧道 $TUNNEL_NAME 未运行"
fi
}
status_tunnel() {
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
echo "隧道 $TUNNEL_NAME 运行中 (PID: $(cat "$PID_FILE"))"
# 测试端口是否监听
nc -z localhost "$LOCAL_PORT" && echo "端口 $LOCAL_PORT 正常监听" || echo "端口 $LOCAL_PORT 未监听"
else
echo "隧道 $TUNNEL_NAME 未运行"
rm -f "$PID_FILE"
fi
}
case "$1" in
start) start_tunnel ;;
stop) stop_tunnel ;;
status) status_tunnel ;;
restart) stop_tunnel; sleep 1; start_tunnel ;;
*) echo "Usage: $0 {start|stop|status|restart}" ;;
esacSSH 安全加固进阶
# ========== 使用 Fail2ban 防暴力破解 ==========
# 安装 Fail2ban
sudo yum install fail2ban -y
# /etc/fail2ban/jail.local
# [sshd]
# enabled = true
# port = 2222
# filter = sshd
# logpath = /var/log/secure
# maxretry = 3
# findtime = 600
# bantime = 3600
# 启动 Fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
# 查看被封禁的 IP
sudo fail2ban-client status sshd
# 手动解封 IP
sudo fail2ban-client set sshd unbanip 192.168.1.100
# ========== 使用 SSH 证书认证(企业推荐) ==========
# 生成 CA 密钥对
ssh-keygen -t ed25519 -f ~/.ssh/ca_key -C "SSH CA Key"
# 签发用户证书(有效期 1 天)
ssh-keygen -s ~/.ssh/ca_key -I user_alice -V +1d -n alice ~/.ssh/id_ed25519_alice.pub
# 服务端配置证书信任
# /etc/ssh/sshd_config
# TrustedUserCAKeys /etc/ssh/ca.pub
# AuthorizedPrincipalsFile /etc/ssh/principals/%u
# 将 CA 公钥复制到服务端
scp ~/.ssh/ca_key.pub server:/etc/ssh/ca.pub
# ========== 双因素认证(2FA) ==========
# 安装 Google Authenticator PAM 模块
sudo yum install google-authenticator -y
# 用户初始化
google-authenticator
# /etc/pam.d/sshd 添加:
# auth required pam_google_authenticator.so
# /etc/ssh/sshd_config 添加:
# ChallengeResponseAuthentication yes
# AuthenticationMethods publickey,keyboard-interactiveSSH 配置模板管理
# ========== 使用 Include 管理多环境配置 ==========
# ~/.ssh/config
# 全局默认配置
Host *
AddKeysToAgent yes
UseKeychain yes
ServerAliveInterval 60
ServerAliveCountMax 3
Compression yes
TCPKeepAlive yes
# 引入不同环境的配置
Include ~/.ssh/config.d/work
Include ~/.ssh/config.d/personal
Include ~/.ssh/config.d/cloud
# ~/.ssh/config.d/work
# Host bastion
# HostName bastion.company.com
# User jump
# IdentityFile ~/.ssh/id_work
# ~/.ssh/config.d/personal
# Host github
# HostName github.com
# User git
# IdentityFile ~/.ssh/id_personal
# 创建配置目录
mkdir -p ~/.ssh/config.d
chmod 700 ~/.ssh/config.d