拿到一臺新的雲伺服器後,不要立刻關閉 root 登入或修改防火牆。正確的順序是:先確認當前會話和登入來源,再建立普通管理員賬戶並驗證金鑰登入,最後啟用 SSH 加固、Fail2Ban 和防火牆規則。

本文主要適用於使用 systemd 的現代 Linux 發行版。命令以 RHEL、Rocky Linux、AlmaLinux、Fedora 的 firewalld 為主,同時給出 Debian、Ubuntu 的對應操作。

在雲廠商控制台中保留一個可用的序列終端、VNC 或救援模式。修改 SSH 和防火牆時始終保留當前會話,並用另一個終端測試新設定;確認新會話可以登入後,再退出舊會話。

1. 先檢查誰正在登入

安全加固前,首先確認伺服器上是否存在不認識的會話和來源 IP。

1.1 檢視當前登入使用者和會話

# 当前登录用户、终端、登录时间、空闲时间和来源地址
who -uH

# 当前用户及其正在运行的命令
w

# systemd 管理的登录会话
loginctl list-sessions

# 查看某个会话的详细信息,将 SESSION_ID 替换为上一条命令中的 ID
loginctl session-status SESSION_ID

whow 讀取登入會話記錄,適合回答“目前有哪些使用者登入”;loginctl 還能顯示會話型別、本地或遠端來源以及相關行程。它們可能包含同一使用者的多個終端,因此不要只看使用者名稱,還要核對來源地址和登入時間。

如果只想檢查已經建立的 SSH TCP 連線,可以執行:

sudo ss -tnp state established '( sport = :22 )'

如果 SSH 不使用 22 埠,需要替換埠號。ss 展示的是網路連線,和 who 的會話記錄互為補充;檔案傳輸、埠轉發等 SSH 連線不一定表現為互動式登入會話。

當前 SSH 會話的用戶端 IP 位於 $SSH_CONNECTION 的第一列:

printf '%s\n' "$SSH_CONNECTION"
ADMIN_IP=$(printf '%s\n' "$SSH_CONNECTION" | awk '{print $1}')
printf '当前管理端 IP: %s\n' "$ADMIN_IP"

後面手工封禁 IP 時,先核對目標地址不等於這個管理端 IP。

1.2 檢視近期成功和失敗的登入

# 近期成功登录和重启记录;-a 将来源地址放到最后一列,-i 保留数字 IP
last -ai | head -n 30

# 近期失败登录,需要 root 权限;部分系统没有启用 btmp 记录
sudo lastb -ai | head -n 30

# 从 systemd journal 查看当天的 sshd 日志
sudo journalctl _COMM=sshd --since today

# 只筛选常见的成功、失败和无效用户事件
sudo journalctl _COMM=sshd --since today | grep -E 'Accepted|Failed|Invalid user'

發現陌生的成功登入時,不要只封禁一個 IP。還應立即檢查 ~/.ssh/authorized_keys、sudoers、計劃任務、systemd 服務和異常行程,並輪換可能洩露的金鑰或密碼。

2. 建立普通管理員賬戶

直接使用 root 遠端登入有兩個明顯問題:賬戶名固定、權限沒有緩衝。更穩妥的做法是建立一個普通賬戶,僅在需要時通過 sudo 提權。

2.1 建立賬戶並授予 sudo 權限

RHEL、Rocky Linux、AlmaLinux、Fedora:

sudo useradd --create-home --shell /bin/bash userA
sudo passwd userA
sudo usermod -aG wheel userA

Debian、Ubuntu:

sudo adduser userA
sudo usermod -aG sudo userA

新開一個終端,用 userA 登入並驗證:

ssh userA@SERVER_IP
sudo -v
sudo id

輸出中出現 uid=0(root) 才說明 sudo 設定有效。在驗證完成前,不要關閉當前 root 會話。

如果需要單獨設定 sudoers,使用 visudo 檢查語法,不要直接編輯 /etc/sudoers

sudo visudo -f /etc/sudoers.d/userA

允許完整 sudo 權限的語法是:

userA ALL=(ALL:ALL) ALL

免密 sudo 會讓竊取到普通賬戶或其 SSH 金鑰的攻擊者更容易直接取得 root 權限,因此不建議全域設定 NOPASSWD: ALL。自動化任務確實需要免密時,只授權固定的、不能被使用者替換或間接執行任意命令的程式。

3. 設定 SSH 金鑰登入

3.1 在用戶端生成金鑰

新的設定優先使用 Ed25519,併為私鑰設定口令:

ssh-keygen -t ed25519 -a 100 -C "userA@client"

預設會生成 ~/.ssh/id_ed25519 私鑰和 ~/.ssh/id_ed25519.pub 公鑰。私鑰不能上傳到伺服器,也不能通過聊天或郵件傳送;建議將私鑰口令交給密碼管理器儲存。

3.2 把公鑰安裝到伺服器

最簡單的方法是在用戶端執行:

ssh-copy-id -i ~/.ssh/id_ed25519.pub userA@SERVER_IP

如果用戶端沒有 ssh-copy-id,可以先把公鑰複製到伺服器,然後以 userA 身份執行:

install -d -m 700 ~/.ssh
cat ~/id_ed25519.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
rm ~/id_ed25519.pub

這裡必須使用 >> 追加公鑰;使用 > 會覆蓋已有公鑰。目錄權限應為 700authorized_keys 應為 600

在用戶端驗證金鑰登入:

ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 userA@SERVER_IP

失敗時增加 -vvv 檢視用戶端除錯日誌,同時在伺服器檢視 SSH 日誌:

ssh -vvv -i ~/.ssh/id_ed25519 userA@SERVER_IP
sudo journalctl _COMM=sshd -n 100 --no-pager

3.3 設定用戶端別名

編輯用戶端的 ~/.ssh/config

Host cloud-server
    HostName SERVER_IP
    User userA
    Port 22
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
    ServerAliveInterval 30
    ServerAliveCountMax 3

以後使用 ssh cloud-server 即可登入。只有明確需要代理轉發時才啟用 ForwardAgent,不要把它作為預設設定。

4. 加固 SSH 服務

確認普通賬戶的金鑰登入已經成功後,再修改 /etc/ssh/sshd_config,或使用發行版已經啟用的 /etc/ssh/sshd_config.d/*.conf

PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
MaxAuthTries 3
LoginGraceTime 30

如果使用 AllowUsers,要把所有需要遠端登入的賬戶列全,否則未列出的賬戶會被拒絕:

AllowUsers userA

應用設定前必須先檢查語法:

sudo sshd -t

命令沒有輸出才表示語法檢查通過。然後重新載入服務:

# RHEL、Rocky Linux、AlmaLinux、Fedora
sudo systemctl reload sshd

# Debian、Ubuntu 通常使用 ssh.service
sudo systemctl reload ssh

不要立即退出當前會話。新開終端確認普通賬戶仍能登入,再使用下面的命令檢查最終生效值:

sudo sshd -T | grep -E 'permitrootlogin|pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|maxauthtries|logingracetime'

4.1 可選:啟用真正的 2FA

2FA 應要求兩種不同因素,而不是“公鑰或驗證碼”二選一。通過 PAM 配好 TOTP 後,可以要求用戶端依次通過公鑰和互動式驗證碼:

UsePAM yes
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive:pam

AuthenticationMethods 中逗號表示必須依次完成多個驗證步驟,空格才表示不同的可選組合。PAM 模組的安裝、恢復碼儲存和緊急賬戶策略因發行版不同,啟用前應保留控制台入口,並先為每一個允許登入的使用者完成 TOTP 註冊。

SSH 二次驗證

5. 用防火牆臨時封禁登入來源

手工封禁適合正在發生的攻擊或應急處置;持續的暴力破解應交給下一節的 Fail2Ban。執行前先通過 whowss$SSH_CONNECTION 核對 IP,避免封掉自己的管理地址。

5.1 firewalld:設定自動過期時間

先確認網路卡所在的活動 zone:

sudo firewall-cmd --get-active-zones

下面示例在 public zone 中封禁 IPv4 地址 203.0.113.10 對 SSH 22 埠的訪問,並在 1 小時後自動刪除規則:

BLOCK_IP=203.0.113.10
SSH_PORT=22
ZONE=public
RULE="rule family=\"ipv4\" source address=\"$BLOCK_IP\" port port=\"$SSH_PORT\" protocol=\"tcp\" reject"

sudo firewall-cmd --zone="$ZONE" --add-rich-rule="$RULE" --timeout=1h
sudo firewall-cmd --zone="$ZONE" --query-rich-rule="$RULE"
sudo firewall-cmd --zone="$ZONE" --list-rich-rules

--timeout 接受秒數以及 smh 字尾。這個規則只存在於執行時設定,過期後自動移除,不能和 --permanent 一起使用。需要提前解封時,在同一個 shell 中執行:

sudo firewall-cmd --zone="$ZONE" --remove-rich-rule="$RULE"

IPv6 地址需要把規則中的 family="ipv4" 改成 family="ipv6"。如果伺服器修改過 SSH 埠,也要同步修改 SSH_PORT。規則語法和超時行為可參考 firewall-cmd 官方手冊firewalld rich rule 說明文件

5.2 UFW:手工新增和刪除臨時規則

UFW 的常規命令沒有規則 TTL,可以先插入規則,處置結束後用完全相同的條件刪除:

sudo ufw insert 1 deny from 203.0.113.10 to any port 22 proto tcp
sudo ufw status numbered

# 解除封禁
sudo ufw delete deny from 203.0.113.10 to any port 22 proto tcp

不要同時手工維護 firewalld、UFW、iptables 和 nftables 多套規則。先用 systemctl is-active firewalldufw status 判斷當前伺服器實際由誰管理防火牆。

6. 使用 Fail2Ban 自動封禁 SSH 暴力破解

Fail2Ban 持續讀取認證日誌,在一個時間視窗內失敗次數超過閾值後呼叫防火牆封禁來源 IP,並在 bantime 到期後自動解封。它能減少日誌噪聲和暴力破解,但不能替代金鑰認證、及時更新系統和最小權限。

6.1 安裝 Fail2Ban

Fedora,或已經啟用 EPEL 的 RHEL 相容發行版:

sudo dnf install fail2ban

如果 Rocky Linux、AlmaLinux 等系統提示找不到軟體包,需要先按對應系統版本啟用 EPEL,再安裝 Fail2Ban。Debian、Ubuntu:

sudo apt update
sudo apt install fail2ban

6.2 設定 sshd jail

不要直接修改發行版提供的 /etc/fail2ban/jail.conf,升級軟體包時它可能被覆蓋。新建 /etc/fail2ban/jail.d/sshd.local

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
findtime = 10m
maxretry = 5
bantime = 1h
bantime.increment = true
bantime.maxtime = 1w

[sshd]
enabled = true
port = ssh
backend = systemd

如果 SSH 使用自定義埠,把 port = ssh 改成實際埠,例如 port = 2222backend = systemd 直接讀取 journal,不應再同時設定 logpath

Fail2Ban 會使用發行版設定的預設封禁動作。如果希望在 firewalld 的 rich rules 中直接看到封禁,可在 [sshd] 下增加:

banaction = firewallcmd-rich-rules

如果網路卡不在 public zone,應先檢查本機 /etc/fail2ban/action.d/firewallcmd-common.conf 的 zone 設定並按實際環境覆蓋。Fail2Ban 官方建議把本地修改放在 .local 檔案中,而不是修改隨軟體釋出的 .conf 檔案,詳見 jail.conf 手冊;firewalld 動作的實現可檢視 firewallcmd-rich-rules.conf

6.3 驗證並啟動

# 检查配置,修复所有错误后再启动
sudo fail2ban-client -t

sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban --no-pager

# 查看所有 jail 和 sshd jail 状态
sudo fail2ban-client status
sudo fail2ban-client status sshd

持續觀察封禁日誌:

sudo journalctl -u fail2ban -f

也可以通過 Fail2Ban 手工封禁或解封一個地址,用於驗證整條防火牆動作鏈:

sudo fail2ban-client set sshd banip 203.0.113.10
sudo fail2ban-client status sshd
sudo fail2ban-client set sshd unbanip 203.0.113.10

測試時使用一個確定不是當前管理端的地址。不要在唯一可用的 SSH 來源上故意連續輸錯密碼。

7. 日常檢查清單

完成初始化後,至少確認以下專案:

  1. who -uHwloginctl list-sessions 中沒有陌生會話。
  2. 普通管理員賬戶可以通過金鑰新建 SSH 會話並正常使用 sudo。
  3. sudo sshd -t 無輸出,sudo sshd -T 顯示 root 和密碼登入已按預期關閉。
  4. firewalld 或 UFW 只啟用一套,雲廠商安全組也只開放必要埠。
  5. fail2ban-client status sshd 顯示 jail 正常執行,並且日誌中沒有 backend 或防火牆錯誤。
  6. 系統安全更新、時間同步和日誌持久化正常,雲控制台救援入口可用。

總結

雲伺服器安全不是單個 SSH 設定項,而是一條連續的防線:先看清當前使用者和連線,再用普通賬戶、金鑰和可選 2FA 降低認證風險,通過 SSH 設定縮小入口,用防火牆處理緊急來源,最後由 Fail2Ban 自動響應持續的暴力破解。所有可能中斷遠端連線的修改都應遵循“保留舊會話、檢查語法、新開終端驗證”的順序。