← 返回上一页

· 文章

Ubuntu 24.04:SSH 配置与基础安全

配置普通管理账号、SSH 公钥认证和端口迁移,并核对登录策略与系统维护状态。

本文以 Ubuntu Server 24.04 为例,完成普通管理员、公钥认证、SSH 端口迁移和基础软件维护。操作涉及 Windows 管理端、云网络控制台和服务器终端。修改 SSH 时保留控制台或现有会话,确认新连接可用后再关闭旧入口。

机柜中的网络交换机和以太网跳线
机房网络设备 · Brett Sayles / Pexels

适用范围、风险与变量

适用于 Ubuntu Server 24.04。开始前准备云控制台或可用的初始 SSH 会话,并预留一个新的 PowerShell 窗口测试连接。示例变量按实际环境填写。

变量 含义 操作位置
ADMIN_USER 普通管理用户 服务器
SERVER_ADDR 服务器公网地址或域名 PowerShell
OLD_SSH_PORT / NEW_SSH_PORT 迁移前后 SSH 端口 控制台、服务器、PowerShell
KEY_FILE Windows 私钥文件名 PowerShell

PowerShell 先定义变量:

$AdminUser = '<管理员用户>'
$ServerAddr = '<服务器地址>'
$OldSshPort = '<旧SSH端口>'
$NewSshPort = '<新SSH端口>'
$KeyFile = "$HOME\.ssh\<私钥文件>"

建立独立密钥和普通管理用户

在 Windows PowerShell 创建一套服务器专用密钥。公钥文件默认在私钥路径后追加 .pub;私钥只保留在管理端。

ssh-keygen -t ed25519 -a 100 -f $KeyFile -C '<用途说明>'
Get-Content "$KeyFile.pub"

在保留的初始服务器会话中创建用户。adduser 会交互设置服务器本地密码;不要记录或公开该密码。将 PowerShell 中读取的单行公钥粘贴到 heredoc 内。

ADMIN_USER='<管理员用户>'
sudo adduser "$ADMIN_USER"
sudo usermod -aG sudo "$ADMIN_USER"
sudo install -d -m 700 -o "$ADMIN_USER" -g "$ADMIN_USER" "/home/$ADMIN_USER/.ssh"
sudo tee "/home/$ADMIN_USER/.ssh/authorized_keys" >/dev/null <<'EOF_KEY'
<粘贴单行公钥>
EOF_KEY
sudo chown "$ADMIN_USER:$ADMIN_USER" "/home/$ADMIN_USER/.ssh/authorized_keys"
sudo chmod 600 "/home/$ADMIN_USER/.ssh/authorized_keys"
id "$ADMIN_USER"
sudo namei -l "/home/$ADMIN_USER/.ssh/authorized_keys"

确认账号、sudo 组和文件权限无误后,保持旧会话打开,转到新的 PowerShell 窗口测试登录。

在第二个 PowerShell 窗口验证公钥和 sudo:

ssh -p $OldSshPort -i $KeyFile -o IdentitiesOnly=yes "$AdminUser@$ServerAddr"

登录后执行:

id
sudo whoami

预期id显示新用户,sudo whoami输出root。还可以用另一把未授权密钥确认访问会被拒绝:

ssh -p $OldSshPort -i "$HOME\.ssh\<其他私钥文件>" -o IdentitiesOnly=yes -o BatchMode=yes -o PreferredAuthentications=publickey "$AdminUser@$ServerAddr"

负向测试仅尝试公钥认证,不应输入账号密码补救。先确认目标可达、主机指纹已核验,以及指定测试密钥能够被加载;带口令的测试密钥需要提前在受控的密钥代理中解锁。预期是服务器拒绝这把未授权公钥,而不是网络超时、主机验证失败或密钥无法解锁。

若登录失败,在旧会话或控制台检查用户名、公钥是否完整,以及.ssh和authorized_keys的属主与权限。修复配置后重新测试,不要先删除唯一可用的授权。

平滑迁移 SSH 端口

端口迁移遵循:新增网络规则 → 双端口监听 → 第二会话登录 → 单端口监听 → 第三会话登录 → 移除旧网络规则。非默认端口只减少常见扫描噪声,核心控制仍是密钥、私钥口令、禁止密码/root 登录和最小 sudo。

先在服务器识别实际监听单元。Ubuntu 24.04 可能使用 ssh.socket,不能假定只改 sshd_config 就会改变监听:

systemctl is-enabled ssh.socket ssh.service
systemctl status ssh.socket --no-pager
systemctl cat ssh.socket
systemctl show ssh.socket -p Listen -p ActiveState
ss -lntp | grep -E ':(<旧SSH端口>|<新SSH端口>)\b'

然后在云网络控制台新增 TCP NEW_SSH_PORT入站规则,暂时保留OLD_SSH_PORT。保存规则后还要检查服务器监听,并从新的 PowerShell 窗口实际登录。

在服务器写双端口 socket drop-in:

OLD_SSH_PORT='<旧SSH端口>'
NEW_SSH_PORT='<新SSH端口>'
sudo install -d -m 755 /etc/systemd/system/ssh.socket.d
sudo tee /etc/systemd/system/ssh.socket.d/10-ports.conf >/dev/null <<EOF_SOCKET
[Socket]
ListenStream=
ListenStream=$OLD_SSH_PORT
ListenStream=$NEW_SSH_PORT
EOF_SOCKET
sudo systemctl daemon-reload
sudo systemctl cat ssh.socket
sudo systemctl show ssh.socket -p Listen -p ActiveState
sudo systemctl restart ssh.socket
sudo systemctl status ssh.socket --no-pager
ss -lntp | grep -E ":($OLD_SSH_PORT|$NEW_SSH_PORT)\b"

空的 ListenStream= 重置列表;随后两行明确保留旧端口并新增新端口。daemon-reload、cat/show/restart/status 的退出状态用于发现加载问题,ss 才证明监听层。

在第二个新 PowerShell 窗口验证新端口和 sudo:

ssh -p $NewSshPort -i $KeyFile -o IdentitiesOnly=yes "$AdminUser@$ServerAddr"

成功后先让服务器只监听新端口:

sudo tee /etc/systemd/system/ssh.socket.d/10-ports.conf >/dev/null <<EOF_SOCKET
[Socket]
ListenStream=
ListenStream=$NEW_SSH_PORT
EOF_SOCKET
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo systemctl status ssh.socket --no-pager
ss -lntp | grep -E ":($OLD_SSH_PORT|$NEW_SSH_PORT)\b"

在第三个新 PowerShell 窗口再次登录新端口:

ssh -p $NewSshPort -i $KeyFile -o IdentitiesOnly=yes "$AdminUser@$ServerAddr"

第三个会话成功,才在云网络控制台删除旧端口放行。最后的新连接到旧端口应失败:

ssh -p $OldSshPort -i $KeyFile -o IdentitiesOnly=yes "$AdminUser@$ServerAddr"

若旧端口测试表现为超时,应结合网络规则和服务器ss输出确认是否已经关闭。

端口迁移回退

迁移失败时,先在网络控制台确认或恢复旧端口放行,再在保留会话恢复双端口:

sudo tee /etc/systemd/system/ssh.socket.d/10-ports.conf >/dev/null <<EOF_SOCKET
[Socket]
ListenStream=
ListenStream=$OLD_SSH_PORT
ListenStream=$NEW_SSH_PORT
EOF_SOCKET
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo systemctl status ssh.socket --no-pager
ss -lntp | grep -E ":($OLD_SSH_PORT|$NEW_SSH_PORT)\b"

随后用独立新终端测试旧端口。没有有效旧会话或控制台时,不应删除唯一入口。

仅允许公钥认证

确认新端口公钥登录正常后再收紧认证策略,并继续保留一个已登录会话。先查看现有配置;Ubuntu 的 OpenSSH 配置可能受多个文件影响,最终以sshd -T输出为准。

(
set -euo pipefail
# 将打印出的备份目录记录在私密维护记录中;回退时填写同一个目录。
BACKUP_DIR=$(sudo mktemp -d /etc/ssh/auth-backup.XXXXXXXX)
printf 'BACKUP_DIR=%s\n' "$BACKUP_DIR"
sudo cp -a /etc/ssh/sshd_config "$BACKUP_DIR/sshd_config"
snippet=/etc/ssh/sshd_config.d/00-publickey-only.conf
# 符号链接可能指向其他配置;不要沿链接写入,先另行核对。
if sudo test -L "$snippet"; then
printf 'snippet 是符号链接,停止变更\n' >&2
exit 1
fi
if sudo test -e "$snippet" || sudo test -L "$snippet"; then
sudo cp -a "$snippet" "$BACKUP_DIR/snippet"
else
sudo touch "$BACKUP_DIR/snippet.absent"
fi
sudo tee "$snippet" >/dev/null <<'EOF_SSHD'
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
EOF_SSHD
sudo sshd -t
sudo sshd -T | grep -Ei '^(pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|permitrootlogin)'
sudo systemctl reload ssh.service
)

上述括号创建子 Shell;备份、写入或语法检查失败会终止本块,不会继续 reload,也不会退出保留的交互会话。sshd -T 输出还需人工核对;如有 Match,使用 sudo sshd -T -C 'user=root,addr=<客户端地址>,host=<客户端主机名>' 等对应连接条件检查有效策略(先替换占位符),不要只看全局值。

用新的 PowerShell 窗口做正向验证:

ssh -p $NewSshPort -i $KeyFile -o IdentitiesOnly=yes "$AdminUser@$ServerAddr"

在新的 PowerShell 窗口分别做密码认证和 root 远程登录的负向测试:

ssh -p $NewSshPort -o PubkeyAuthentication=no -o PreferredAuthentications=password -o NumberOfPasswordPrompts=1 "$AdminUser@$ServerAddr"
ssh -p $NewSshPort -o BatchMode=yes "root@$ServerAddr"

root 测试失败也可能因为没有提供可用密钥,不能单凭失败证明禁止 root。应结合对应连接条件下的 permitrootlogin no、认证策略输出及服务端日志判断,且普通管理员的新会话必须仍能成功。

回退前核对本次备份目录及原 snippet 状态:原来存在则恢复原文件,原来不存在才删除本次新增文件。主配置未被本步骤修改,不应无条件覆盖回去:

(
set -euo pipefail
BACKUP_DIR='<本次记录的备份目录>'
snippet=/etc/ssh/sshd_config.d/00-publickey-only.conf
sudo test -d "$BACKUP_DIR"
if sudo test -e "$BACKUP_DIR/snippet" || sudo test -L "$BACKUP_DIR/snippet"; then
sudo cp -a --remove-destination "$BACKUP_DIR/snippet" "$snippet"
elif sudo test -f "$BACKUP_DIR/snippet.absent"; then
sudo rm -f -- "$snippet"
else
printf '备份状态不完整,停止回退\n' >&2
exit 1
fi
sudo sshd -t
sudo sshd -T | grep -Ei '^(pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|permitrootlogin)'
sudo systemctl reload ssh.service
)

再以新终端复验,最后才关闭回退会话。

软件索引、工具、升级与重启

升级前确认 SSH 仍可从新窗口登录,并检查磁盘、内存和失败服务:

df -h /
free -h
systemctl --failed
apt-cache policy git curl docker.io rsync

apt update 会刷新本地包索引,是状态变更但不会升级已安装包;apt install 安装指定包;apt upgrade 升级已安装包。模拟依赖动作仍需要可用索引:

sudo apt update
update_status=$?
printf 'apt update exit=%s\n' "$update_status"
sudo apt -s install git curl ca-certificates docker.io rsync
sudo apt -s upgrade

确认模拟结果和维护窗口后执行:

sudo apt install git curl ca-certificates docker.io rsync
sudo apt upgrade
sudo apt autoremove --dry-run

Compose 是否需要取决于业务和当前软件源,不应假定所有主机都有相同 Compose 包。检查重启标记:

test -e /var/run/reboot-required && cat /var/run/reboot-required || true

如果核心库或内核更新要求重启,选择维护时段重启,随后重新建立 SSH 连接并检查:

uname -r
uptime
systemctl is-system-running || true
systemctl --failed

degraded 是诊断信号,要结合失败单元判断;即使没有重启标记,也应复验关键服务和管理入口。

常见问题与最终验收

现象 先检查什么 不能据此断定什么
新端口超时 网络规则、ss、管理端地址 不能只凭超时认定是安全组问题
新用户被拒绝 公钥、权限、用户名、日志 本地文件存在不等于登录成功
sshd -t 成功 再看 sshd -T 语法成功不等于策略生效
systemctl 非零 systemctl --failed 不自动等于系统不能维护

完成后确认:普通管理员可以通过新端口和公钥登录并使用 sudo;旧端口已经关闭;密码与 root 远程登录被拒绝;软件更新和服务状态无异常。

Ubuntu 镜像和云平台的默认配置可能不同。修改前先查看当前 SSH unit 和生效配置,并始终保留控制台或已有会话。