RHEL 9 与 WSL2 初始环境配置与常用工具安装教程

适用于 Red Hat Enterprise Linux 9(裸机、云服务器或 WSL2 导入),从订阅注册到常用工具一站式配置。

📌 这是一份独立专题——聚焦 RHEL 9 的初始环境配置、常用工具安装与安全加固;WSL 专属行为单独标注,不展开讨论通用 WSL 基础(详见 WSL 虚拟机创建与管理指南.md)。

⚠️ RHEL 与 AlmaLinux 的核心差异

  • RHEL 默认仓库是空的,必须先注册 Red Hat 订阅才能 dnf install
  • RHEL 仓库 id 形如 rhel-9-for-x86_64-baseos-rpms(与 AlmaLinux 的 baseos 不同)
  • RHEL 9 在 SCA(Simple Content Access)模式下无需手动 attach 订阅,注册即获仓库访问权
  • 推荐:能联网就直接走 Red Hat CDN,无需换源;国内访问慢再换阿里云 RHEL 镜像

目录


一、文档总览

flowchart TD A["🟣 RHEL 9 初始环境配置"]:::root --> S["🔵 订阅与仓库"]:::l1 A --> B["🔵 SSH 远程服务"]:::l1 A --> C["🔵 常用工具集"]:::l1 A --> D["🔵 网络与系统"]:::l1 A --> E["🔵 安全与优化"]:::l1 A --> G["🟠 WSL 用户专属指南(仅 WSL2)"]:::wsll1 A --> F["🔵 命令速查"]:::l1 S --> S1["🟢 注册 subscription-manager"]:::l2 S --> S2["🟢 启用 EPEL / CRB"]:::l2 S --> S3["🟢 换源(国内)"]:::l2 B --> B1["🟢 安装 openssh-server"]:::l2 B --> B2["🟢 配置 sshd_config"]:::l2 B --> B3["🟢 密钥免密登录"]:::l2 B --> B4["🟢 防火墙放行"]:::l2 C --> C1["🟢 dnf 装常用包"]:::l2 C --> C2["🟢 Git / vim / bash"]:::l2 D --> D1["🟢 静态 IP / DNS"]:::l2 D --> D2["🟢 主机名 / hosts"]:::l2 D --> D3["🟢 时区 / NTP"]:::l2 E --> E1["🟢 firewalld 防火墙"]:::l2 E --> E2["🟢 fail2ban"]:::l2 E --> E3["🟢 SELinux"]:::l2 E --> E4["🟢 内核参数 / FD 限制"]:::l2 G --> G1["🟠 启用 systemd + wsl.conf"]:::wsll2 G --> G2["🟠 主机名 / hosts"]:::wsll2 G --> G3["🟠 SELinux 切 permissive"]:::wsll2 G --> G4["🟠 firewalld 关掉"]:::wsll2 G --> G5["🟠 数据规划 + vhdx 回收"]:::wsll2 G --> G6["🟠 重启正确方式"]:::wsll2 classDef root fill:#ffffff,stroke:#9b59b6,stroke-width:3px,color:#9b59b6 classDef l1 fill:#ffffff,stroke:#3498db,stroke-width:2px,color:#3498db classDef l2 fill:#ffffff,stroke:#2ecc71,stroke-width:2px,color:#2ecc71 classDef wsll1 fill:#fff7e6,stroke:#e67e22,stroke-width:2px,color:#e67e22 classDef wsll2 fill:#fff7e6,stroke:#d35400,stroke-width:1px,color:#d35400

章节索引:

序号 章节 说明
文档总览 流程图 + 索引
初始环境检查 确认系统版本 / 内核 / 当前用户 / 订阅状态
Red Hat 订阅与仓库 核心:注册 + 仓库可用 + 换源
SSH 远程服务 安装 + 配置 + 密钥免密
常用工具集 dnf 一键装包 + Git / vim / bash 偏好
网络与系统配置 静态 IP / 主机名 / 时区
安全与优化 firewalld / fail2ban / SELinux / 内核参数
WSL 用户专属指南 WSL 必读:systemd 启用、主机名、SELinux 噪音、firewalld 关掉、数据规划、vhdx 回收、重启方式
验证检查清单 一键自检
常用命令速查 RHEL ↔ Ubuntu 对照 + 订阅管理

二、初始环境检查

在动手前,先确认当前系统的版本、内核、当前用户、是否已注册订阅。

# 系统版本
cat /etc/os-release
# 关键字段:NAME="Red Hat Enterprise Linux"、VERSION="9.x (Plow)"、ID=rhel、ID_LIKE="fedora centos"

# 内核版本
uname -r

# 当前用户与权限
whoami
id

# 订阅状态(重点:未注册会显示 "Unknown" 或 "Not Registered")
sudo subscription-manager status

# 是否已安装 SSH(多数 RHEL 镜像默认已装)
rpm -q openssh-server
ss -tlnp 2>/dev/null | grep :22 || netstat -tlnp 2>/dev/null | grep :22

2.1 设置 root 密码

RHEL 安装时通常会要求设 root 密码,但云厂商轻量镜像WSL 装出来的版本可能 root 锁死或密码为空。建议先确认:

# 查看是否能切到 root
su -
whoami
# 报错或提示密码错误 = 没设好

为 root 设置一个强密码

# 为 root 用户设置密码(按提示输入两次)
sudo passwd root

验证切换是否成功

# 切换到 root 用户
su -

# 确认当前身份
whoami
id

💡 提示

  • RHEL 9 默认 PermitRootLoginprohibit-password(允许密钥但禁止密码登录 root),生产环境建议显式设为 no(详见 4.3)。
  • WSL 环境下:首次 wsl --import 装出来的版本默认以 root 登录,没有 root 密码;sudo passwd root 设上之后更方便维护。

🔐 安全建议

  • root 密码应使用强密码(长度 12 位以上,包含大小写字母、数字、特殊字符)。
  • 日常操作优先使用 sudo,仅在必要时切换到 root。

三、Red Hat 订阅与仓库(核心)

🎯 章节定位:RHEL 9 装完后必须完成本章才能 dnf install。AlmaLinux 不需要这一步(默认仓库就开),这是两者最大的差异。

推荐阅读顺序:

  1. 3.1 注册开发者订阅 —— 必做(免费 16 个实例授权)
  2. 3.2 验证仓库可用 —— 确认注册成功
  3. 3.3 启用 EPEL / CRB —— 装第三方包(按需)
  4. 3.4 换源 —— 国内访问慢时再做

3.1 注册开发者订阅

📋 关于 Red Hat 开发者订阅

  • 免费:developers.redhat.com 注册账号即可
  • 授权数:单个账号最多 16 个 RHEL 实例(个人/小团队够用)
  • 模式:SCA(Simple Content Access)——注册成功后无需手动 attach 订阅,自动获得全部仓库访问权
  • 到期:需每年手动续期(登录 developers.redhat.com 重新激活)
  • 真机/云服务器:通常用 Red Hat Customer Portal 账号;个人开发者推荐 developers.redhat.com 账号
  • WSL 镜像自带订阅:直接从 developers.redhat.com 下载的 WSL2 rootfs 也是按"开发者订阅"思路设计,导入后用同一账号注册即可

3.1.1 注册系统

# 用 Red Hat 账号注册(替换成你的用户名/密码)
sudo subscription-manager register \
  --username YOUR_REDHAT_USERNAME \
  --password 'YOUR_REDHAT_PASSWORD'

🔐 安全注意

  1. 密码含特殊字符(! & * $)必须用单引号包裹。
  2. 真实账号不要写入文档、截图或代码仓库;推荐用环境变量或交互式输入:
    read -rs REDHAT_PASSWORD
    sudo subscription-manager register \
      --username "$REDHAT_USERNAME" \
      --password "$REDHAT_PASSWORD"
    

注册成功标志

The system has been registered with ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
The registered system name is: xxxxxxx

3.1.2 刷新并查看状态

sudo subscription-manager refresh
sudo subscription-manager status

预期输出(SCA 模式):

+-------------------------------------------+
   System Status Details
+-------------------------------------------+
Overall Status: Registered
Content Access Mode is set to Simple Content Access. This host has access to content, regardless of subscription status.

💡 syspurpose 警告:注册时可能有 syspurpose 相关 warning,不影响使用。SCA 模式下注册成功就自动获得内容权限。

3.2 验证仓库可用

sudo dnf repolist

预期输出(关键仓库):

repo id                                    repo name
rhel-9-for-x86_64-appstream-rpms           Red Hat Enterprise Linux 9 for x86_64 - AppStream (RPMs)
rhel-9-for-x86_64-baseos-rpms              Red Hat Enterprise Linux 9 for x86_64 - BaseOS (RPMs)

更新系统

sudo dnf update -y

📋 仓库 id 命名规则

发行版 仓库 id
RHEL 9 rhel-9-for-x86_64-baseos-rpms / rhel-9-for-x86_64-appstream-rpms
AlmaLinux 9 baseos / appstream
Rocky Linux 9 baseos / appstream
CentOS Stream 9 baseos / appstream

写脚本时不要假设 baseos 是 RHEL 的仓库 id(RHEL 的 baseurl 默认指向 Red Hat CDN,不是国内镜像)。

3.3 启用 EPEL 与 CRB(按需)

📋 用途说明

  • EPEL(Extra Packages for Enterprise Linux):社区维护的扩展包,很多常用包在这里htopfail2banp7zipfd-find 等)
  • CRB(CodeReady Builder):相当于 RHEL 8 的 PowerTools,包含开发工具链的元数据

3.3.1 启用 CRB(开发工具链元数据)

📋 本节场景:装 *-devel 编译包、源码编译时才需要 CRB。日常用包装 EPEL 即可(参见 3.3.2 节),可以跳过本节。

⚠️ 关键提醒dnf config-manager --enable crbWSL 装的官方 RHEL 9 EUS rootfs永远报 No matching repo to modify: crb——本节用 sed 改文件绕开,跨平台通用。

主命令(3 条 sed,按顺序复制执行)

sudo sed -i '/^\[codeready-builder-for-rhel-9-x86_64/,/^\[/s/^enabled = 0/enabled = 1/' /etc/yum.repos.d/redhat.repo
sudo sed -i '/^\[codeready-builder-for-rhel-9-x86_64-eus-/,/^\[/s/^enabled = 1/enabled = 0/' /etc/yum.repos.d/redhat.repo
sudo dnf clean all

验证(装一个 CRB 依赖包,能装就说明通了)

sudo dnf install -y openssl-devel

预期输出

Installed:
  openssl-devel-1:3.5.5-5.el9_8.x86_64

Complete!

📋 三条 sed 各自的作用

命令 作用
第 1 条 把所有 codereaby 段(dist + EUS + debug + source)enabled 改 1
第 2 条 把 EUS 段(-eus-rpms 等 3 个)enabled 改 0
第 3 条 清 dnf 缓存,让 dnf 重读 repo 文件

为什么不保留 EUS 段:subscription-manager 在每次 dnf 调用时自动重写 redhat.repo(用 $releasever 变量),把字面量版本号改回变量。EUS 路径 /content/eus/rhel9/$releasever/... 变量解析成 9 时变成 /rhel9/9/...404。永久 disable EUS 段避免反复 404。

📋 为什么 dnf config-manager --enable crb 不能用

平台 redhat.repo 段 id 命名 dnf config-manager --enable crb
RHEL 9 裸机/CDN(默认) 短名 crb ✅ 工作
WSL EUS rootfs 长名 codeready-builder-for-rhel-9-x86_64-rpms ❌ 永远报 No matching repo

结论

  • 裸机/CDN 用户:用 dnf config-manager --enable crb(段 id 是短名)
  • WSL 用户:用本节的 sed 三条(段 id 是长名)
  • 永远不要在 WSL 上用 dnf config-manager --enable/disable(已实测 4 次都失败)

📋 跨点版本

sed 用前缀匹配 codeready-builder-for-rhel-9-x86_64——未来升 RHEL 9.9 / 9.10 仍可用(前提是段 id 命名规则不变)。


禁用 CRB(按需)

sudo sed -i '/^\[codeready-builder-for-rhel-9-x86_64/,/^\[/s/^enabled = 1/enabled = 0/' /etc/yum.repos.d/redhat.repo
sudo dnf clean all

📚 踩坑背景(理解用,不再当操作步骤)

dnf config-manager --enable crb 在 WSL 装的官方 RHEL 9 EUS rootfs 上永远报 No matching repo to modify——根因有两层:

  1. 仓库 id 命名差异:裸机/CDN 默认 rootfs 的 redhat.repo 段 id 叫 crb(短名),WSL EUS rootfs 是 codeready-builder-for-rhel-9-x86_64-rpms(长名)。dnf config-manager 找的是仓库 id 字面值,匹配不到长名。

  2. EUS 路径 + $releasever 不兼容subscription-manager 会在每次 dnf 调用时自动重写 redhat.repo(用 $releasever 变量),但 EUS 路径 /content/eus/rhel9/$releasever/... 在变量解析成 9 时变成 /rhel9/9/...404。永久 disable EUS 段就是从根本上绕开这个 404。

为什么上面的 sed 三条方案稳

  • 第 1 条用前缀匹配 codeready-builder-for-rhel-9-x86_64——一次覆盖所有 6 个 CRB 变体段
  • 第 2 条 disable 掉 3 个 EUS 段——避免 subscription-manager 反复重写导致的 404
  • 不依赖 dnf config-manager / subscription-manager——直接改文件

3.3.2 安装 EPEL(推荐)

RHEL 9 的 EPEL 由 Fedora EPEL 组维护,仓库名是 epel-releaseEPEL 不依赖 CRB——日常用包(htop / tmux / jq / tree / fd-find / p7zip 等)都在 EPEL,建议先装 EPEL 再按需启用 CRB。

# 直接装 EPEL(不需要先 enable CRB,EPEL 装包元数据只依赖 BaseOS / AppStream)
sudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm

# 验证
sudo dnf repolist | grep epel
# 应输出:
#   epel                    Extra Packages for Enterprise Linux 9 - x86_64
#   epel-cisco-openh264    Extra Packages for Enterprise Linux 9 openh264 (From Cisco) - x86_64

⚠️ 关键提示

  • EPEL 装包时不要走 dnf config-manager --add-repo(GPG key 不会自动导入,会报 GPG 错误)
  • 装好 EPEL 后,dnf 装包时仍可能报 CRB 404(如果 redhat.repo 里有未禁用的 EUS 段且 baseurl 是 $releasever)——按 3.3.1 节 的 sed 方案 disable EUS 段即可

3.3.3 EPEL 仓库结构

EPEL 装完后会在 /etc/yum.repos.d/ 下创建:

文件 用途
epel.repo 主仓库(启用)
epel-debuginfo.repo 调试符号包(默认禁用)
epel-source.repo 源码包(默认禁用)
epel-testing.repo 测试包(默认禁用)

📋 CRB 与 PowerTools 命名差异

  • RHEL 8 仓库叫 powertools(沿用 CentOS 8 命名)
  • RHEL 9 仓库叫 crb(CodeReady Builder)
  • 切版本时必须改仓库名

3.4 换源(国内访问慢时)

🎯 适用场景:直接连 Red Hat CDN 速度慢(国内常见),换到阿里云 RHEL 镜像。国外服务器或 CDN 速度可接受时跳过本节

⚠️ 换源前

  1. 必须已经注册(3.1 节),否则换了源也访问不到内容
  2. RHEL 9 仓库没有 mirrorlist(不像 CentOS),换源需要直接改 baseurl

3.4.1 备份默认源

TS=$(date +%Y%m%d%H%M%S)
sudo mkdir -p /etc/yum.repos.d.bak.$TS
sudo cp /etc/yum.repos.d/redhat.repo /etc/yum.repos.d.bak.$TS/

⚠️ 为什么用变量:两个独立命令里的 $(date) 会各执行一次,秒数可能不同 → cp 会找不到刚建的目录。用变量复用同一个时间戳是稳妥写法。
📋 RHEL 的仓库文件只有一个:/etc/yum.repos.d/redhat.repo(与 AlmaLinux 的多个 almalinux*.repo 不同)

3.4.2 替换 baseurl 为阿里云

# 阿里云 RHEL 9 镜像路径:mirrors.aliyun.com/rhel/9/...
# 用 sed 批量替换:
#   - 不要求行首有 #(RHEL 9 WSL EUS rootfs 的 baseurl 默认就是启用态,不是注释态)
#   - \s* 容忍 baseurl = ... 等号两边的空格
sudo sed -i.bak \
  -e 's|^\s*baseurl\s*=\s*https://cdn.redhat.com|baseurl = https://mirrors.aliyun.com|g' \
  /etc/yum.repos.d/redhat.repo

# 验证改对了
grep "^baseurl" /etc/yum.repos.d/redhat.repo | head -5

📋 注意:redhat.repo 里默认是 cdn.redhat.com 域名前缀baseurl = https://cdn.redhat.com/content/dist/rhel9/...),跟早期版本的 rhuipulp 路径不同。换源时匹配 cdn.redhat.com 即可。

RHEL 9 WSL EUS rootfs 默认是启用态baseurl = ...,不是 #baseurl = ... 注释态),sed 模式去掉 ^# 兼容两种状态。

3.4.3 应用并验证

sudo dnf clean all
sudo dnf makecache

# 验证用的是阿里云源
dnf repolist -v | grep -E "Repo-id|baseurl"
# baseurl 应是 mirrors.aliyun.com

3.4.4 换 EPEL 源(如果 3.3 装的 EPEL 是默认源)

📋 跳过条件:如果你执行完 3.3.2 装 EPEL 后,dnf repolist | grep epel 能看到 epel 仓库且装包正常(速度 OK),跳过本节——EPEL 默认安装时会自动用国内镜像(mirror.nyist.edu.cn 等),不需要手动换源。

只有当你的 EPEL baseurl 是 download.example.org 占位符(极少见,通常意味着 EPEL release 包损坏)时,才执行下面的覆盖修复。

# EPEL 默认 baseurl 是 download.example.org 占位符,直接用 tee 覆盖最稳
sudo tee /etc/yum.repos.d/epel.repo > /dev/null <<'EOF'
[epel]
name=Extra Packages for Enterprise Linux 9 - $basearch
baseurl=https://mirrors.aliyun.com/epel/9/Everything/$basearch
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-9

[epel-debuginfo]
name=Extra Packages for Enterprise Linux 9 - $basearch - Debug
baseurl=https://mirrors.aliyun.com/epel/9/Everything/$basearch/debug
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-9
gpgcheck=1

[epel-source]
name=Extra Packages for Enterprise Linux 9 - $basearch - Source
baseurl=https://mirrors.aliyun.com/epel/9/Everything/source/tree
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-9
gpgcheck=1
EOF

# 验证
grep "^baseurl" /etc/yum.repos.d/epel.repo
# 期望:3 行 baseurl=https://mirrors.aliyun.com/epel/9/Everything/...

3.4.5 其他国内镜像(按需替换 baseurl

镜像 baseurl 替换模式 说明
阿里云(最稳,首选) cdn.redhat.commirrors.aliyun.com/rhel RHEL / EPEL 都覆盖
华为云 cdn.redhat.commirrors.huaweicloud.com/rhel 通用
腾讯云 cdn.redhat.commirrors.cloud.tencent.com/rhel 通用
清华 TUNA 仅 EPEL:download.fedoraproject.orgmirrors.tuna.tsinghua.edu.cn/epel 学术网友好

📋 注意:上述镜像只镜像了 BaseOS / AppStream / EPELCodeReady Builder (crb) 和 Extra Packages for Enterprise Linux 之外的部分不在镜像中,仍要走 Red Hat CDN。如遇 Failed to download metadata 报错,多半是 crb 仓库没换到,保留默认 CDN 即可。

3.4.6 常见问题

Q1:换源后 dnf makecache 报 GPG 错误?

# 重新导入 GPG key(RHEL 9)
sudo rpm --import https://mirrors.aliyun.com/rhel/RPM-GPG-KEY-redhat-release

# 验证 key 已装
rpm -qa gpg-pubkey | grep -i "redhat\|epel"

Q2:换源后 dnf makecache 报 404 Not Found?

# 99% 是 baseurl 拼错了(大小写、路径、版本号)
# 正确路径示例:
#   https://mirrors.aliyun.com/rhel/9/BaseOS/$basearch/os/
#   https://mirrors.aliyun.com/rhel/9/AppStream/$basearch/os/
#   https://mirrors.aliyun.com/epel/9/Everything/$basearch/

Q3:想临时恢复默认源?

# 还原备份
sudo cp /etc/yum.repos.d.bak.*/redhat.repo /etc/yum.repos.d/
sudo dnf clean all
sudo dnf makecache

Q4:订阅过期后怎么处理?

# 1. 登录 developers.redhat.com 续期(免费开发者订阅每年手动续一次)
# 2. 回到机器上刷新订阅
sudo subscription-manager refresh
# 3. 验证仓库恢复
sudo dnf repolist

3.4.7 一键脚本(适合批量初始化)

#!/bin/bash
# =============================================
# RHEL 9 阿里云源一键配置(前提:已注册 subscription-manager)
# 对应文档:3.4 节
# 幂等:重复运行不会破坏既有配置
# =============================================
set -e

# 0. 前置检查:必须已注册
if ! sudo subscription-manager status 2>/dev/null | grep -q "Overall Status: Registered"; then
  echo "❌ 系统未注册 Red Hat 订阅,请先按 3.1 节执行 subscription-manager register"
  exit 1
fi

BAK_DIR="/etc/yum.repos.d.bak.$(date +%Y%m%d%H%M%S)"

# 1. 备份 redhat.repo
if [ -f /etc/yum.repos.d/redhat.repo ]; then
  sudo mkdir -p "$BAK_DIR"
  sudo cp /etc/yum.repos.d/redhat.repo "$BAK_DIR/"
  echo "✅ 备份到 $BAK_DIR"
fi

# 2. 替换 redhat.repo 的 baseurl
if [ -f /etc/yum.repos.d/redhat.repo ]; then
  sudo sed -i \
    -e 's|^\s*baseurl\s*=\s*https://cdn.redhat.com|baseurl = https://mirrors.aliyun.com|g' \
    /etc/yum.repos.d/redhat.repo
  echo "✅ redhat.repo 已替换为阿里云"
fi

# 3. 替换 EPEL 源(如果 EPEL 已装)
if [ -f /etc/yum.repos.d/epel.repo ]; then
  sudo tee /etc/yum.repos.d/epel.repo > /dev/null <<'EOF'
[epel]
name=Extra Packages for Enterprise Linux 9 - $basearch
baseurl=https://mirrors.aliyun.com/epel/9/Everything/$basearch
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-9
EOF
  echo "✅ EPEL 源已覆盖为阿里云"
fi

# 4. 刷新
sudo dnf clean all
sudo dnf makecache
echo "✅ dnf makecache 完成"

保存为 ~/set-aliyun-mirror-rhel9.sh,跑:

chmod +x ~/set-aliyun-mirror-rhel9.sh
sudo ~/set-aliyun-mirror-rhel9.sh

四、常用工具集

💡 章节定位:本节是 SSH 配好之后紧接着要做的——补齐开发/运维常用的命令工具。WSL 默认 rootfs 没有这些,每次新建 WSL 都要装一遍。

网络、主机名、时区等"系统级"配置放 六、网络与系统配置;安全加固放 七、安全与优化;WSL 专属配置放 八、WSL 用户专属指南

4.1 一键安装清单

# =============================================
# 网络工具
# =============================================
# net-tools: netstat / ifconfig / route 等传统命令
# iproute:   ip / ss / tc 等新一代命令(RHEL 9 自带)
# bind-utils:nslookup / dig / host
# iputils:   ping
# traceroute:路由追踪
# wget / curl: 命令行下载工具(WSL 官方 rootfs 默认不装 wget;curl-minimal 与 curl 冲突需 --allowerasing)
sudo dnf install -y --allowerasing net-tools bind-utils iputils traceroute mtr wget curl NetworkManager NetworkManager-libnm

# =============================================
# 编辑器与文件处理
# =============================================
# vim: 增强版 vi
# nano: 简单文本编辑器
# unzip / p7zip / p7zip-plugins: 解压 zip / 7z
# lrzsz: rz / sz 上传下载(Xshell/SecureCRT 适用)
sudo dnf install -y vim nano unzip p7zip p7zip-plugins lrzsz

# =============================================
# 版本控制
# =============================================
# git: 分布式版本控制
sudo dnf install -y git

# =============================================
# Shell 增强
# =============================================
# bash-completion: 命令补全
# zsh + oh-my-zsh: 更友好的 shell
# tmux: 终端复用器(断线不丢任务)
sudo dnf install -y bash-completion tmux

# =============================================
# 时间同步
# =============================================
# chrony: RHEL 9 默认的 NTP 客户端
sudo dnf install -y chrony

# =============================================
# SELinux 工具
# =============================================
# policycoreutils: 提供 getenforce / setenforce / sestatus
sudo dnf install -y policycoreutils

# =============================================
# 系统监控与诊断
# =============================================
# htop: 增强版 top
# iotop: 磁盘 IO 监控
# iftop: 网络流量监控
# sysstat: iostat / mpstat / sar
# lsof: 查看打开的文件
# strace: 系统调用跟踪
# ncdu: 交互式磁盘占用分析
sudo dnf install -y htop iotop iftop sysstat lsof strace ncdu

# =============================================
# 开发工具链(按需)
# =============================================
# gcc / gcc-c++ / make / automake / autoconf
# python3 / pip3
# java-17-openjdk
# nodejs / npm
# sudo dnf install -y gcc gcc-c++ make automake autoconf \
#   python3 python3-pip \
#   java-17-openjdk \
#   nodejs npm

# =============================================
# 文本搜索 / 处理
# =============================================
# ripgrep (rg): 更快更现代的 grep
# fd-find (fd): 更快更现代的 find
# jq: 命令行 JSON 处理
# tree: 目录树展示
# 注:EPEL 包名是 fd-find(提供 fd 命令,需要 alias 或建软链)
sudo dnf install -y ripgrep fd-find jq tree
# fd-find 在 RHEL 9 上是 /usr/bin/fd-find,要 alias 成 fd 才顺手
echo "alias fd='fd-find'" >> ~/.bashrc

# =============================================
# 安全相关
# =============================================
# fail2ban: 防 SSH 暴力破解
# firewalld: 防火墙(默认已装)
sudo dnf install -y fail2ban

📋 装完验证

# 一次性看哪些工具可用
for tool in htop tmux jq tree vim git curl wget dnf mtr; do
  if command -v $tool &> /dev/null; then
    echo "✅ $tool"
  else
    echo "❌ $tool 缺失"
  fi
done

4.2 工具速查表

🔧 工具 📝 用途 📋 常用命令
netstat 网络连接/端口查看 netstat -tlnp
ss 现代版 netstat ss -tlnpss -s
ip 网络配置 ip addrip route
nmcli NetworkManager 命令行 nmcli connection show
dig DNS 查询 dig baidu.com
mtr 网络诊断(ping+traceroute) mtr baidu.com
vim 文本编辑 vim /etc/hosts
tmux 终端复用 tmux new -s name
htop 进程监控 htop
iotop 磁盘 IO 监控 sudo iotop
iftop 流量监控 sudo iftop -i ens33
lsof 查看打开的文件 lsof -i :8080
tree 目录树 tree -L 2 /data
jq JSON 处理 cat x.json | jq .
rg 快速搜索 rg "error" /var/log
subscription-manager RHEL 订阅管理 sudo subscription-manager status

4.3 配置 Git(开发者必做)

git config --global user.name "Your Name"
git config --global user.email "your_email@example.com"

# 让命令输出带颜色
git config --global color.ui auto

# 解决中文文件名显示问题
git config --global core.quotepath false

4.4 配置 vim(可选但推荐)

vim ~/.vimrc
" 显示行号
set number

" 语法高亮
syntax on

" 缩进 4 空格
set tabstop=4
set softtabstop=4
set shiftwidth=4
set expandtab

" 搜索高亮
set hlsearch
set incsearch

" 编码
set encoding=utf-8
set fileencodings=utf-8,gbk,gb18030,gb2312

" 鼠标支持
set mouse=a

4.5 配置 bash 提示符(可选)

vim ~/.bashrc

在末尾追加:

# 彩色提示符
PS1='[\u@\h \W]\$ '

# 命令别名
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
alias gs='git status'
alias gp='git pull'
alias ..='cd ..'
source ~/.bashrc

💡 RHEL 9 默认 bash 提示符是 [\u@\h \W]\$ ,只显示最后一级目录名(W = basename of PWD);想显示完整路径用 \w

PS1='[\u@\h \w]\$ '

五、SSH 远程服务

本章为优先配置项。所有运维操作几乎都依赖 SSH,建议在完成 三、订阅与仓库 之后立即配置。

5.1 安装 openssh-server

⚠️ 前置:必须先按 三、订阅与仓库 完成注册,否则 dnf install 装不上 openssh-server。

# 更新软件源缓存
sudo dnf makecache

# 安装 SSH 服务端与客户端
sudo dnf install -y openssh-server openssh-clients

# 启动并设置开机自启
sudo systemctl enable --now sshd

🔧 验证

sudo systemctl status sshd   # 输出中出现 active (running) 即表示成功
ss -tlnp | grep :22         # 看到 0.0.0.0:22 或 *:22 监听中

📋 WSL 用户必读:WSL 默认 rootfs 通常没装 openssh-server,本节命令是必须的;且需要在 8.1 启用 systemd 之后才能 systemctl enable sshd 真正生效。

5.2 配置文件结构

📄 文件 📝 用途
/etc/ssh/sshd_config 服务端主配置(修改后要重启 sshd 服务)
/etc/ssh/ssh_config 客户端配置(一般不动)
~/.ssh/authorized_keys 存放客户端公钥,启用免密登录
~/.ssh/known_hosts 客户端记录过的服务端指纹
~/.ssh/id_rsa / id_rsa.pub 客户端的私钥 / 公钥

修改 sshd_config强烈建议先备份:

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak

5.3 修改 sshd_config(生产推荐配置)

sudo vim /etc/ssh/sshd_config
# =============================================
# 基础配置
# =============================================
# 监听端口(生产建议改为非默认端口,降低被扫描爆破概率)
Port 22

# 监听地址(0.0.0.0 = 所有网卡;如仅内网访问可改为内网IP)
ListenAddress 0.0.0.0

# 协议版本(v2 比 v1 安全)
Protocol 2

# =============================================
# 登录认证(关键)
# =============================================
# 禁止 root 直接登录(必开,RHEL 9 默认是 prohibit-password,改为 no 更严)
PermitRootLogin no

# 是否允许密码登录。生产推荐 no + 改用密钥登录
PasswordAuthentication yes

# 是否允许空密码(生产环境应设为 no)
PermitEmptyPasswords no

# 公钥认证(密钥免密登录)
PubkeyAuthentication yes
AuthorizedKeysFile      .ssh/authorized_keys

# =============================================
# 安全加固
# =============================================
# 最大重试次数
MaxAuthTries 3

# 最大并发未认证连接
MaxSessions 5

# 登录超时(30 秒无输入断开)
LoginGraceTime 30

# 客户端空闲超时(600 秒无操作断开)
ClientAliveInterval 60
ClientAliveCountMax 10

# 禁止 X11 转发(图形转发,服务器无必要)
X11Forwarding no

# 限制登录用户/组(白名单)
AllowUsers youruser
# AllowGroups ssh-users

# =============================================
# 显示与日志
# =============================================
# 登录前后显示的 banner 文件路径(可选)
# Banner /etc/issue.net

# 日志级别
LogLevel VERBOSE

# =============================================
# RHEL 9 特有:SELinux 相关
# =============================================
# SELinux 启用时,chroot 目录权限由 SELinux 策略控制
# 一般保持默认即可

📋 配置说明

  • PermitRootLogin no:禁止 root 直登,运维需用普通用户 + sudo 提权。
  • PasswordAuthentication yes → 后续验证完密钥登录后可改为 no
  • AllowUsers youruser:把 youruser 替换为你的实际登录用户,多用户用空格分隔
  • RHEL 9 默认 PermitRootLogin prohibit-password(只允许密钥),更严;本配置显式设为 no 完全禁止。

⚠️ 关键踩坑:/etc/ssh/sshd_config.d/ 覆盖文件

按上面修改完主配置后仍然密码登录不上?最常见原因是 /etc/ssh/sshd_config.d/ 下有 .conf 覆盖文件——它在 sshd_config 之后加载,优先级更高,会把主配置的 PasswordAuthentication yes 覆盖成 no

典型场景:WSL 官方 RHEL rootfs 默认带 /etc/ssh/sshd_config.d/50-cloud-init.conf,里面硬编码 PasswordAuthentication no

完整修复流程

# 1. 查看覆盖文件列表
ls /etc/ssh/sshd_config.d/
# 2. 定位 PasswordAuthentication 被覆盖的位置
grep -r PasswordAuthentication /etc/ssh/sshd_config.d/
# 3. 把覆盖文件里的 no 改成 yes
sudo sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config.d/50-cloud-init.conf
# 4. 重启 sshd 生效
sudo systemctl restart sshd
# 5. 验证 sshd 实际生效配置(不是覆盖文件里的,而是 sshd -T 输出的最终生效值)
sudo sshd -T | grep -i passwordauthentication

预期sshd -T 是 sshd 实际生效配置):

passwordauthentication yes

5.4 启动 SSH 并加入开机自启

# 检查配置语法(非常关键,避免配错导致无法登录)
sudo sshd -t
# 若命令无输出,则表示语法正确

# 重启服务
sudo systemctl restart sshd

# 确认开机自启
sudo systemctl enable sshd

# 查看状态
sudo systemctl status sshd

⚠️ 高危提醒:修改 SSH 配置后,不要立即关闭当前会话。应新开一个窗口测试能正常登录,确认没问题再退出,避免配置错误导致锁在外面。

5.5 配置密钥免密登录(推荐)

5.5.1 在客户端生成密钥对

Windows(PowerShell / CMD):

ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
# 一直回车即可,会在 C:\Users\你的用户\.ssh\ 下生成 id_rsa 和 id_rsa.pub

Linux / macOS:

ssh-keygen -t rsa -b 4096
# 提示 passphrase 时可设置密码(更安全),也可直接回车

5.5.2 上传公钥到服务器

方法一:ssh-copy-id(最简单)

# Linux / macOS / Windows 11 (PowerShell 自带 OpenSSH)
ssh-copy-id -i ~/.ssh/id_rsa.pub youruser@服务器IP
# 输入一次密码后即完成

方法二:手动复制

# 在客户端打印公钥
cat ~/.ssh/id_rsa.pub

# 在服务器上操作
mkdir -p ~/.ssh
chmod 700 ~/.ssh
vim ~/.ssh/authorized_keys   # 粘贴公钥,一行一个
chmod 600 ~/.ssh/authorized_keys

📋 RHEL 9 + SELinux 注意

  • 默认 SELinux 策略下,~/.ssh/chmod 700 + authorized_keyschmod 600 已正确打标
  • 如果还是登录失败,先 restorecon -R -v ~/.ssh 恢复 SELinux 上下文

5.5.3 关闭密码登录

验证密钥能登录后,编辑服务端配置:

sudo vim /etc/ssh/sshd_config
PasswordAuthentication no
sudo sshd -t
sudo systemctl restart sshd

5.6 防火墙放行 SSH

RHEL 9 默认使用 firewalld

# 查看 firewalld 状态
sudo firewall-cmd --state
sudo firewall-cmd --list-all

# 放行 SSH(默认就是放行的,这里再确认一次)
sudo firewall-cmd --permanent --add-service=ssh

# 重载配置
sudo firewall-cmd --reload

# 验证
sudo firewall-cmd --list-services | grep ssh

💡 SSH 服务的服务名

  • firewalld 预定义服务名是 ssh(不是 sshd),内部对应 /usr/lib/firewalld/services/ssh.xml
  • 改了 SSH 端口(如 2222)时,要手动添加:sudo firewall-cmd --permanent --add-port=2222/tcp

六、网络与系统配置

6.1 配置静态 IP(NetworkManager / nmcli,仅真机/云服务器

⚠️ 本节仅适用于真机 / 云服务器 / VMware/VirtualBox 虚拟机。WSL2 用户请直接跳过本节

  • WSL 装的发行版默认没装 NetworkManager(连 nmcli 命令都没有)
  • WSL 微软官方镜像里 NetworkManager.servicemask(屏蔽),即使手动 dnf install 装上,systemctl enable 也会报 Unit file ... is masked
  • WSL 网络由 Windows 虚拟网卡管理,Linux 侧任何网络配置都改不动
  • 详见 八、WSL 用户专属指南 第 8.0 节

⚠️ WSL 下误装 NetworkManager 的清理方式

# 如果已经装了但 enable 失败,先取消 mask
sudo systemctl unmask NetworkManager
# 然后关掉(WSL 下完全不需要)
sudo systemctl disable --now NetworkManager 2>/dev/null || true
# 彻底不想要可以卸载
# sudo dnf remove NetworkManager NetworkManager-libnm

RHEL 9 统一使用 NetworkManager 管理网络(取代传统的 network-scripts)。命令行工具是 nmcli,图形化工具是 nmtui

# 真机/云服务器:NetworkManager 通常已装,没装再装
sudo dnf install -y NetworkManager
sudo systemctl enable --now NetworkManager

# 查看网卡名称和当前连接
nmcli device status
# 输出示例:
# DEVICE  TYPE      STATE         CONNECTION
# ens33   ethernet  connected     ens33
# lo      loopback  unmanaged     --

# 查看当前连接的 IP 信息
nmcli connection show ens33

用 nmcli 配静态 IP(推荐,可重复执行):

# 1. 设置 IPv4 地址、子网掩码、网关
sudo nmcli connection modify ens33 \
  ipv4.method manual \
  ipv4.addresses 192.168.1.100/24 \
  ipv4.gateway 192.168.1.1 \
  ipv4.dns "223.5.5.5 8.8.8.8 114.114.114.114"

# 2. 激活新配置
sudo nmcli connection up ens33

# 3. 验证
ip -br addr show ens33

用 nmtui 配(图形化,更直观)

sudo nmtui
# 选 Edit a connection → 选 ens33 → 改 IPv4 配置为 Manual → 填 IP / 网关 / DNS
# 选 Activate a connection 让新配置生效

⚠️ 远程操作注意:改 IP 后 nmcli connection up 会立即切换 IP,远程连接会断——确保你有其他方式连入(如云厂商控制台、KVM 直连),或者先在本地试好再上远程。

💡 WSL2 用户:本节对你不适用。详见 八、WSL 用户专属指南

6.2 配置主机名

# 查看当前主机名
hostnamectl

# 设置主机名(替换 dev-workspace 为你想要的名字)
sudo hostnamectl set-hostname dev-workspace

⚠️ hostnamectl 改了 /etc/hostname,但不会自动改 /etc/hosts!

hostnamectl set-hostname 只负责:

  • 写入 /etc/hostname
  • 通知 systemd-hostnamed 更新主机名状态

不会自动写入 /etc/hosts,所以改完主机名后必须手动追加一行,否则 sudo 会报 unable to resolve host

# 追加新主机名到 /etc/hosts(追加,不是覆盖!)
echo "127.0.1.1 dev-workspace" | sudo tee -a /etc/hosts

# 验证 sudo 不再报警
sudo whoami

完整的 /etc/hosts 应该长这样

127.0.0.1   localhost
127.0.1.1   dev-workspace

💡 WSL2 用户:本节命令通用(启用 systemd 后),但操作步骤略有不同(要同步配 /etc/wsl.conf、在 PowerShell 重启 WSL)。详见 8.2 WSL 主机名设置

6.3 时区与时间同步

# 查看当前时区
timedatectl

# 设置时区
sudo timedatectl set-timezone Asia/Shanghai

# RHEL 9 默认自带 chrony(不是 systemd-timesyncd)
sudo systemctl enable --now chronyd
timedatectl status

📋 说明

  • 服务器与客户端的时间偏差超过 5 分钟会导致部分协议(如 Kerberos)失败。
  • RHEL 9 默认是 chrony(比 systemd-timesyncd 精度高、适合生产)。
  • 如需切回 systemd-timesyncdsudo dnf install -y systemd-timesyncd && sudo systemctl disable --now chronyd && sudo systemctl enable --now systemd-timesyncd

💡 WSL2 用户:WSL2 时间默认和 Windows 同步(启动时校准一次),一般不需要单独配。详见 8.3 WSL 时区(可选)

6.4 关闭 RHEL 9 默认组件(按需)

# 关闭 kdump(内核转储服务,节省内存)
sudo systemctl disable --now kdump.service

# 关闭防火墙自动发现相关(按需)
# sudo systemctl disable --now mdmonitor.service

# 查看所有已启用服务,按需关闭
sudo systemctl list-unit-files --state=enabled

📋 RHEL 9 默认开机的服务(vs Ubuntu):

服务 RHEL 9 默认 Ubuntu 默认 说明
firewalld ❌(用 ufw) 防火墙
chronyd ❌(用 systemd-timesyncd) 时间同步
kdump 内核崩溃转储
sssd 身份认证(可选)
snapd Snap 包管理
rhsmcertd Red Hat 订阅管理守护进程

七、安全与优化

7.1 firewalld 防火墙

详见 4.6 防火墙放行 SSH,补充常用规则:

# 查看所有区域和规则
sudo firewall-cmd --list-all

# 放行 HTTP/HTTPS
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

# 放行指定端口
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

# 富规则:放行指定 IP 段访问 SSH(更安全)
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=192.168.1.0/24 service name=ssh accept'
sudo firewall-cmd --reload

# 富规则:拒绝特定 IP
sudo firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=10.0.0.5 reject'
sudo firewall-cmd --reload

# 删除规则
sudo firewall-cmd --permanent --remove-service=http
sudo firewall-cmd --reload

# 关闭防火墙(不推荐生产)
sudo systemctl stop firewalld
sudo systemctl disable firewalld

⚠️ 远程操作注意:在远程主机上启用防火墙前,先确认 SSH 规则已加firewall-cmd --permanent --add-service=ssh)。firewalld 默认会放行 ssh 服务的默认区域,但自定义规则可能被遗忘。

💡 WSL2 用户:WSL 内 firewalld 跑不起来(微软内核缺 iptables 模块)。详见 八、WSL 用户专属指南 第 8.6 节

7.1.1 重要认知:放行了端口,外部仍可能连不上

🎯 本节适用场景:按上面示例配了 firewall-cmd --permanent --add-port=2211/tcp 后,外部客户端仍然连不上 2211,而 firewall-cmd --list-all 又能看到 2211/tcp 已放行。这是 firewalld zone 模型下最常见的"看起来配了但没生效"的踩坑。

根本原因:firewalld 的规则是挂在 zone 上的,而 zone 又挂在网卡接口上。数据包进入 Linux 后,firewalld 会按"它从哪块网卡进来"找对应的 zone,再按 zone 里的规则判断放行/拒绝。所以:

步骤 数据包进入 → 匹配 zone → 应用 zone 规则
1. 数据包到达网卡 例如从 eth0(公网网卡)进来
2. 找 zone firewalld 看 eth0 绑在哪个 zone
3. 匹配规则 用那个 zone 里的 services / ports / rich-rules 判断

关键点firewall-cmd --add-port=2211/tcp(不带 --zone=)默认写入的是 public zone(firewalld 的默认 zone)。如果 eth0 没绑到 public zone(也没绑到任何带 2211 规则的 zone),那 2211 的放行规则就根本没有机会被命中——数据包走 eth0 的默认 zone(一般是 trusted 之外的某个 zone 或直接被 block),被默默拦截。

典型症状

  • firewall-cmd --list-all 能看到 public zone 有 2211/tcp,但 --zone=public --list-interfaces 输出里没有 eth0
  • firewall-cmd --get-active-zones 显示 eth0docker zone 或其它 zone(不一定有 2211 规则)
  • 外部 telnet <IP> 2211 / ssh -p 2211 / 应用客户端连接都超时
  • 但同主机内 curl http://127.0.0.1:2211 能通(回环不走 firewalld)

为什么会这样(按出现频率排序)

原因 说明
加端口时没指定 zone,默认加到了 public eth0 从一开始就没绑 public(默认是 trusted 之外的 zone,或装 Docker 后被自动挪走)
装了 Docker,docker0 网桥被 firewalld 自动放进了 docker zone 同一时间 eth0 也可能被自动挪到 docker zone(因为 dockerd 改了 iptables 链),但 docker zone 的规则集跟 public 不一样
多网卡场景(eth0 + eth1 + bond0),只把其中一个绑到了 zone 其它网卡上的服务从公网进不来

排查命令

# 1. 看每个 zone 当前绑了哪些接口
firewall-cmd --get-active-zones
# 典型输出:
#   docker
#     interfaces: docker0
#   public
#     interfaces:    ← ⚠️ eth0 没在这里

# 2. 看 public zone 里有哪些规则(确认端口确实加进去了)
firewall-cmd --zone=public --list-all
# 关键看 ports: 行有没有 2211/tcp

# 3. 看 eth0 到底绑在哪个 zone
firewall-cmd --get-zone-of-interface=eth0
# 输出可能是:docker / trusted / nozone

# 4. 看所有 zone 的接口绑定(最全)
firewall-cmd --list-all-zones | grep -E "^[a-z]|interfaces"

# 5. 确认数据包是否真的被 firewalld 拦截(不需要抓包)
sudo firewall-cmd --set-log-denied=all     # 临时开启拒绝日志(重启 firewalld 后失效)
# 然后从外部连一次,看 /var/log/messages:
sudo tail -f /var/log/messages | grep -i "REJECT|DROP"

修复方法(按推荐度排序):

方法 A:把 eth0 显式绑到已放行目标端口的 zone(推荐)

# 假设 2211/tcp 已加在 public zone,把 eth0 也绑到 public
sudo firewall-cmd --zone=public --add-interface=eth0 --permanent
sudo firewall-cmd --reload

# 验证:eth0 现在应在 public zone 下
firewall-cmd --get-active-zones
# public
#   interfaces: eth0

# 从外部再连一次 2211

方法 B:把端口直接加到 eth0 当前所在的那个 zone

# 先查 eth0 在哪个 zone
ZONE=$(firewall-cmd --get-zone-of-interface=eth0)
echo "eth0 当前 zone: $ZONE"

# 把 2211 加到那个 zone
sudo firewall-cmd --zone=$ZONE --add-port=2211/tcp --permanent
sudo firewall-cmd --reload

方法 C:临时把 eth0 加到 trusted(⚠️ 仅本地调试用)

# trusted = 放行所有流量,等于"对这个网卡关防火墙"
sudo firewall-cmd --zone=trusted --add-interface=eth0 --permanent
sudo firewall-cmd --reload
# 调通后**记得改回**对应的 zone,别留着 trusted

方法 D:用富规则(rich-rule)绕过 zone 匹配

# 富规则默认写到 public zone,跟方法 A 等价
sudo firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=0.0.0.0/0 port port=2211 protocol=tcp accept'
sudo firewall-cmd --reload

💡 选哪个方法的判断思路

场景 推荐方法
单网卡服务器,端口明确加在 public A(最直接)
端口在多个 zone 里都要放行(多网卡共用) 把每块网卡分别绑到对应 zone,再 add-port
临时调试 / 验证是不是 zone 配错了 C(trusted),验证完改回
只想对特定 IP 放行(如内网管理端口) D(rich-rule + source address)
服务器装了 Docker,且 dockerd 把 eth0 抢到 docker zone docker network inspect 看 docker 怎么管的 iptables,再决定要不要把 eth0 挪回 public

与 Docker 共存时的额外注意事项

  • dockerd 安装时会自动在 firewalld 里建一个 docker zone(/usr/lib/firewalld/zones/docker.xml),并把 docker0 网桥绑进去
  • docker 启动容器时默认会把端口映射规则写到 iptables 的 DOCKER 链(绕过 firewalld 的 zone 检查),所以容器暴露的端口一般不踩这个坑
  • 宿主机自己起的服务(比如 systemctl 启动的 sshd / nginx / 自定义应用)仍然走 firewalld zone 匹配,需要按上面方法处理
  • 如果 eth0 被 docker 抢到了 docker zone,而 docker zone 默认只放行 docker 通讯相关的端口,宿主机非 docker 服务就会受牵连

WSL2 用户注意

  • WSL2 内 firewalld 跑不起来(详见 8.6),通常已经 disable 了,不会遇到这个坑
  • 但如果 WSL 启用了镜像网络模式(networkingMode=mirrored,详见 WSL 虚拟机创建与管理指南.md),流量走 Windows 宿主侧,由 Windows 防火墙控制,不再走 WSL 内的 firewalld——本节问题在镜像模式下变成"Windows 防火墙是否放行 2211",与本节无关

7.2 fail2ban 防爆破

sudo dnf install -y fail2ban
sudo vim /etc/fail2ban/jail.local
[DEFAULT]
# 封禁时间(1 小时)
bantime  = 1h

# 监测时间窗口(10 分钟)
findtime = 10m

# 最大重试次数
maxretry = 5

# 封禁动作(firewalld 集成)
banaction = firewallcmd-rich-rules
banaction_allports = firewallcmd-rich-rules

[sshd]
enabled = true
port    = ssh
filter  = sshd
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3

📋 RHEL 9 的 fail2ban 注意

  • /var/log/secure 是 SSH 认证日志(不是 Ubuntu 的 /var/log/auth.log
  • fail2ban 默认配 logpath = %(sshd_log)s,会自动读 /var/log/secure
  • banactionfirewallcmd-rich-rules(通过 firewall-cmd 加富规则封禁),不要写 ufw(Ubuntu 风格)
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban

# 查看封禁列表
sudo fail2ban-client status sshd
# 手动解封某 IP
sudo fail2ban-client set sshd unbanip 1.2.3.4

7.3 SELinux 处理

💡 本节是 RHEL / CentOS 系列的特色。Ubuntu / Debian 不需要关心(用 AppArmor)。

7.3.1 SELinux 状态

# 看当前状态
sestatus
# 输出示例:
#   SELinux status:                 enabled
#   SELinuxfs mount:                /sys/fs/selinux
#   SELinux root directory:         /etc/selinux
#   Loaded policy name:             targeted
#   Current mode:                   enforcing
#   Mode from config file:          enforcing

# 三种模式
getenforce
# enforcing / permissive / disabled
模式 行为
enforcing 强制执行(默认)——违反策略的行为被阻止并记日志
permissive 仅警告——不阻止,但记 audit log(用于调试策略)
disabled 完全关闭(不推荐)

7.3.2 临时切换模式

# 临时切到 permissive(重启后失效)
sudo setenforce 0
getenforce
# Permissive

# 切回 enforcing
sudo setenforce 1
getenforce
# Enforcing

7.3.3 永久切换模式

sudo vim /etc/selinux/config
# 把 enforcing 改成 permissive
SELINUX=permissive
# 改完要重启系统生效
sudo reboot

7.3.4 常见 SELinux 故障

症状 1:服务起不来,日志有 SELinux is preventing 警告

# 1. 看完整诊断
sudo ausearch -m avc -ts recent

# 2. 装 setroubleshoot 看人话报告
sudo dnf install -y setroubleshoot
sudo sealert -a /var/log/audit/audit.log

# 3. 让 SELinux 自动生成允许规则(最快)
sudo ausearch -m avc -ts recent | audit2allow -M my-fix
sudo semodule -i my-fix.pp

症状 2:改了 SSH 端口后连不上

# SELinux 限制了 sshd 只能监听预定义端口
# 添加端口到 SELinux 策略
sudo semanage port -a -t ssh_port_t -p tcp 2222
# 或临时关 SELinux 验证
sudo setenforce 0

症状 3:改了 Web 服务目录后访问 403/404

# SELinux 上下文不对
# 看现在是什么类型
ls -Z /var/www/html/
# -rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 index.html

# 改完目录后恢复上下文
sudo restorecon -Rv /var/www/html/

症状 4:WSL 里的 RHEL 看到一堆 SELinux 警告

💡 WSL2 用户:WSL2 不支持完整 SELinux,详见 八、WSL 用户专属指南 第 8.5 节,那里有完整的 WSL 专属配置脚本(一键切到 permissive + 持久化)。

🔐 安全建议

  • 生产环境:保持 enforcing,遇到问题用 audit2allow 加规则
  • 开发/测试环境permissive 更省心
  • 完全关掉(disabled)不推荐——失去了 SELinux 的最后一道防线

7.4 文件描述符限制

大并发服务(Tomcat、Nginx 等)经常遇到 too many open files 错误。

# 查看当前限制
ulimit -n
# 临时修改(当前会话)
ulimit -n 65535

永久修改:

sudo vim /etc/security/limits.conf
*       soft    nofile  65535
*       hard    nofile  65535
root    soft    nofile  65535
root    hard    nofile  65535

📋 说明

  • * 匹配所有非 root 用户,root 要单独写(limits.conf 规则:通配符不作用于 root)
  • 两组值看起来一样,但不能合并——这是 limits.conf 的限制
  • 没加 nproc:进程数 65535 没意义,默认就够用;除非跑大并发服务(如 MySQL、Kafka)才需要
sudo vim /etc/systemd/system.conf

找到 #DefaultLimitNOFILE=1024:524288 这一行,取消注释并改为 65535

DefaultLimitNOFILE=65535

📋 为什么需要改

  • systemd 默认soft=1024hard=524288
  • 所有用 systemctl 启动的服务(fail2bansshdnginxmysql 等)都受这个限制
  • 默认 1024 对大并发服务不够:nginx 一个连接 2~3 个 FD,1024 / 3 ≈ 340 并发就报 too many open files
  • soft 到 65535 是生产/开发环境的标配
# 重启后生效
sudo reboot

💡 WSL2 用户:WSL 下文件描述符限制分三层limits.conf / system.conf / user.conf),每层生效方式不同,"改了没生效"是 WSL 下最常见的踩坑,详见 8.7.1 WSL 内文件描述符限制改完 system.conf 必须 wsl --shutdown(详见 8.7)。

7.5 内核参数调优

sudo vim /etc/sysctl.d/99-tuning.conf
# =============================================
# 网络优化
# =============================================
# 允许端口复用(TIME_WAIT 快速回收)
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_fin_timeout = 15

# 提升 TCP 缓冲区
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# SYN 队列长度(防 SYN 洪水)
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 8192

# 启用 SYN Cookie
net.ipv4.tcp_syncookies = 1

# =============================================
# 安全加固
# =============================================
# 禁用 IP 转发(单机部署不需要)
net.ipv4.ip_forward = 0

# 忽略 ICMP 重定向(防中间人)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0

# 忽略源路由
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

# 开启反向路径过滤
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# 记录可疑包
net.ipv4.conf.all.log_martians = 1
sudo sysctl -p /etc/sysctl.d/99-tuning.conf

📋 RHEL 9 + WSL 注意

  • 真机/云服务器:sysctl -p 立即生效
  • WSL2:大部分网络相关参数由 Windows 宿主内核控制,配置仅作记录
  • WSL2 + 启用 systemd 后,sysctl -p 命令本身能跑,但 ip_forwardtcp_* 等设置可能没真正生效

7.6 配置 Swap(如内存较小)

# 查看当前 swap
free -h
swapon --show

# 创建 4G swap 文件
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

# 永久生效
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

# 调整 swappiness(值越小越倾向用物理内存,推荐 10-30)
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.d/99-tuning.conf
sudo sysctl -p

📋 SELinux + swapfile

  • RHEL 9 默认 SELinux 是 enforcing
  • fallocate 创建的 /swapfile 默认会被正确打标为 swapfile_t
  • 如果 swapon 报 SELinux 错误:sudo restorecon /swapfile

八、WSL 用户专属指南(WSL2 用户必读)

🎯 章节定位:把 WSL 专属操作集中到这一章。真机/云服务器用户可整章跳过。

推荐阅读顺序:

  1. 8.1 启用 systemd —— 先做这一步(没启用前 systemctl 等工具不可用)
  2. 8.2 主机名 —— 消除 sudo unable to resolve host 警告
  3. 8.3 时区 —— 可选(WSL 默认跟 Windows 同步)
  4. 8.4 数据目录规划 —— WSL 重装会丢数据,提前软链到 Windows 盘
  5. 8.5 SELinux —— 减少日志噪音(或跳过,WSL 内 SELinux 不生效)
  6. 8.6 firewalld / fail2ban —— WSL 内通常跑不起来,建议关掉
  7. 8.7 重启正确方式 —— 不要 sudo reboot,要 wsl --shutdown

📚 通用 WSL 基础(systemd 启用、镜像网络、hostAddressLoopback 等更细节的内容)详见《WSL 虚拟机创建与管理指南》第 4~6 节。RHEL 9 在 WSL 内的行为与 AlmaLinux / Ubuntu 24.04 几乎完全一致(包管理器命令相同;WSL 内部机制相同)。

8.1 WSL 启用 systemd + 配 /etc/wsl.conf

WSL 装的发行版默认 PID 1 是 init,不是 systemd——所有 systemctl / hostnamectl / timedatectl 工具都不能用。WSL 初始化的核心是写一份完整的 /etc/wsl.conf,一次性配置好 systemd、用户、互操作、主机名、DNS 等。

8.1.1 写 wsl.conf(建议用整段覆盖)

sudo tee /etc/wsl.conf > /dev/null << 'EOF'
[user]
default = root

[boot]
systemd = true

[interop]
enabled = true
appendWindowsPath = true

[network]
generateHosts = true
generateResolvConf = true
hostname = rhel9-dev
EOF

⚠️ 关键提醒

  • 建议 4 个段一起写(user / boot / interop / network);实测缺段可能导致配置不生效
  • hostname = rhel9-dev 写在 [network] 段(不在 [host] 或别的地方)——这是 WSL 识别主机名的位置
  • 不建议用 sed 改一行:等号前后的空格、= true vs =true 都可能让 sed pattern 匹配不上;用 tee 整段覆盖最稳

各段作用

关键项 作用
[user] default = root WSL 默认登录用户(wsl --import 装出来的版本默认是 root,可改成普通用户)
[boot] systemd = true 启用 systemd(systemctl / hostnamectl 等工具的前提)
[interop] enabled / appendWindowsPath 允许从 WSL 调 Windows 程序 + 把 Windows PATH 加进来
[network] hostname WSL 启动时把 Linux 主机名设成这个值(不被 Windows 计算机名覆盖)
[network] generateHosts = true WSL 自动维护 /etc/hosts(含 hostname 映射)
[network] generateResolvConf = true WSL 自动维护 /etc/resolv.conf(用 Windows 网络栈的 DNS)

💡 为什么不用 [automount]:WSL 默认就自动挂载 Windows 盘(/mnt/c/mnt/d),不写也能用。如果要自定义挂载选项(如默认 uid/gid、umask),再加 [automount] 段。

8.1.2 重启 + 验证

在 Windows PowerShell 重启 WSL

wsl --shutdown
wsl -d RHEL9

⚠️ 不要用 sudo reboot 重启 WSL——WSL 不是真硬件,reboot 只杀进程、不会重读 wsl.conf。需要在 Windows PowerShell 跑 wsl --shutdown。详见 8.7

回到 WSL 验证

# 1. PID 1 应该是 systemd
ps -p 1 -o comm=
# 期望输出:systemd

# 2. 主机名是 wsl.conf 里设的
hostname
# 期望输出:rhel9-dev

# 3. /etc/hosts 自动生成(含 hostname 映射)
cat /etc/hosts

# 4. /etc/resolv.conf 自动生成(用 Windows DNS)
cat /etc/resolv.conf

# 5. 验证 sudo 不再报警
sudo whoami
# 应直接返回 root,无 "unable to resolve host" 警告

💡 为什么不用手动追加 hosts:因为 generateHosts = true,WSL 启动时会自动生成 /etc/hosts(含主机名映射)。手动追加的 echo "127.0.1.1 ..." | sudo tee -a /etc/hosts 重启后会丢——让 WSL 自动维护更稳。

8.1.3 故障排查

症状 原因 解决
wsl --shutdown 后 hostname 还是 Windows 计算机名 wsl.conf 没生效 / 段不全 tee 整段覆盖 wsl.conf,再 wsl --shutdown
sudo 报 "unable to resolve host xxx" /etc/hosts 没自动生成 cat /etc/hosts 看是否有 hostname 行;缺失则检查 generateHosts = true
hostnamectl set-hostname 改了但重启丢失 wsl.conf 没写 hostname = xxx 改 wsl.conf(不要只靠 hostnamectl),详见 8.2
sed 改 wsl.conf 一行没生效 pattern 跟实际格式不匹配(空格、大小写) tee 整段覆盖
systemctl 报 "Failed to connect to bus" systemd 没启用 检查 wsl.conf 的 [boot] systemd = true,再 wsl --shutdown

8.2 WSL 主机名设置

8.2.1 核心认知:主机名控制权

WSL 主机名有三层"控制权":

来源 优先级 作用
/etc/wsl.conf[network] hostname = xxx 最高 每次 WSL 启动时由 Windows 侧读取,强制覆盖 Linux 主机名
hostnamectl set-hostname xxx 次之 运行时改主机名,但重启后会被 wsl.conf 覆盖
Windows 计算机名 默认 啥都没配时,WSL 用 Windows 计算机名当 Linux 主机名

关键点:要在 wsl.conf 里写 hostname = xxx,单靠 hostnamectl 持久化不了。

8.2.2 配置步骤

8.1.1 的 wsl.conf 模板已包含 hostname = rhel9-dev,首次配置按 8.1.1 走即可。如果已有 wsl.conf 只补 hostname,用 sed 追加到 [network] 段:

# 在 [network] 段补一行 hostname(完整模板见 8.1.1)
sudo sed -i '/^\[network\]/a hostname = rhel9-dev' /etc/wsl.conf

然后在 Windows PowerShell 重启 WSL、回到 WSL 验证:

# PowerShell
wsl --shutdown
wsl -d RHEL9
# WSL 内
hostname                              # 期望:rhel9-dev
cat /etc/hosts | grep "$(hostname)"   # 期望能看到 127.0.1.1 rhel9-dev
sudo whoami                           # 应直接返回 root,无 "unable to resolve host" 警告

8.2.3 如果不想被 WSL 自动覆盖主机名

个别场景下你想用 hostnamectl 临时改主机名,而不希望 wsl.conf 下次启动覆盖:

  • 临时:直接用 sudo hostnamectl set-hostname temp-name,本次会话有效
  • 持久:把 wsl.conf 里的 hostname 改成你想要的值,或干脆不写 hostname(让 Windows 计算机名生效)

⚠️ 注意:不写 hostname 时,WSL 会用 Windows 计算机名,可能带中文或特殊字符,导致部分 Linux 工具报错。建议显式指定一个 ASCII 主机名。

8.3 WSL 时区(可选)

WSL2 的时间默认和 Windows 同步(WSL 启动时从 Windows 校准一次),所以一般不需要单独配时区。如果发现时间漂移:

# 强制跟 Windows 校准
sudo hwclock -s

# 或显式设时区(启用 systemd 后才生效)
sudo timedatectl set-timezone Asia/Shanghai

8.4 WSL 数据目录规划

WSL2 本质是一个 ext4 虚拟磁盘文件ext4.vhdx)挂载到 /。WSL 出问题需要重装、迁移、换发行版时,根目录里的数据会全部丢失;但 /mnt/c//mnt/d/ 是直接读 Windows 文件系统,完全不受影响

8.4.1 先理解 WSL2 的"磁盘"是怎么回事

df -h

典型输出(已实测 WSL2 RHEL 9):

/dev/sde       1007G  2.0G  954G   1% /                  ← 根目录所在的虚拟磁盘(最大块)
/dev/sdd       1007G  5.3G  951G   1% /mnt/wsl/...      ← Docker Desktop 存储(也是 vhdx)
none            7.8G  ...   ...   ... /mnt/wsl/drivers   ← WSL 内部通道挂载(不是存储)
C:\             401G  305G   96G  77% /mnt/c             ← Windows C 盘
D:\             552G  397G  155G  72% /mnt/d             ← Windows D 盘

📋 RHEL 9 vs AlmaLinux / Ubuntu 24.04 在 WSL2 上的关键差异

  • RHEL 9 安装器可选 /data 分区(CentOS/RHEL 传统),部分 WSL 镜像可能保留该目录
  • 默认有 /srv,但没有 /opt 下的具体服务目录
  • SELinux 配置文件 /etc/selinux/config 默认是 enforcing(WSL2 实际不强制执行,但写在那里)

8.4.2 推荐:把重要数据放到 Windows 盘

为什么? WSL 出问题需要重装、迁移、换发行版时,根目录里的数据会全部丢失;但 /mnt/c//mnt/d/ 是直接读 Windows 文件系统,不受影响。

# 1. 在 Windows D 盘建一个固定目录(PowerShell 或文件管理器都行)
#    路径示例:D:\wsl-data

# 2. 在 WSL 里建软链接(个人数据放这)
mkdir -p /mnt/d/wsl-data
ln -s /mnt/d/wsl-data ~/data

# 3. 给项目、数据库、下载等建子目录
mkdir -p /mnt/d/wsl-data/{projects,downloads,backups,db,logs}

# 4. 验证
ls -ld ~/data
df -h /mnt/d

💡 软链 vs 直接挂载

  • 软链(推荐)→ ~/data/mnt/d/wsl-data,既能在 WSL 用,也能在 Windows 资源管理器直接打开
  • /mnt/d/ 路径直接用也行,但路径长、可读性差

8.4.3 关于 /data 目录(含风险与备份)

RHEL / CentOS 系习惯把 /data 当作业务数据根目录,但标准 RHEL 9 安装不会自动创建 /data,只有在安装时手动划分独立分区,或某些定制镜像里才会出现。

各场景的使用建议

场景 建议
镜像已自带 /data 可直接使用,按业务需要建子目录
镜像没有 /data 自行 sudo mkdir -p /data 创建,并设置合适权限
WSL 跑服务(Docker Compose、数据库) /data/<service> 存服务数据(RHEL 习惯)
WSL 重装不能丢的 必放 /mnt/d/wsl-data(WSL 重装会重置 /data
跑生产服务(数据库、消息队列) /data/<service>,或 /srv/<service>(FHS 标准)

⚠️ 风险提示:WSL 重装/换发行版会重置整个 /(包括 /data)。根目录下的 /data 只适合临时数据、容器卷、一次性实验;长期重要数据、数据库文件、git 仓库、备份必须放 /mnt/c/mnt/d 或外部盘。

如果镜像已自带 /data 但权限是 root 只读:

ls -ld /data
# 若显示 dr-xr-xr-x. 2 root root ... /data,则需要改权限
sudo chown -R $USER:$USER /data
# 或建子目录
sudo mkdir -p /data/{mysql,redis,logs}
sudo chown -R $USER:$USER /data

如果确实要把服务数据放根目录下的 /data,请同时做好导出备份:

# PowerShell 定期导出备份
wsl --export RHEL9 D:\wsl-backup\rhel9-$(Get-Date -Format yyyyMMdd).tar

8.4.4 重要:定期回收虚拟磁盘空间

WSL2 的 ext4.vhdx稀疏文件——在 WSL 里 rm 一个大文件,Windows 侧的 vhdx 不会自动缩小。长时间使用后,Windows 资源管理器显示 vhdx 文件大小为 500 GB,但 WSL 内部实际数据仅占 50 GB。

先确认你的 ext4.vhdx 在哪

# 方法 1:wsl 命令直接显示(PowerShell)
wsl -l -v

# 方法 2:全盘搜 ext4.vhdx(最通用)
Get-ChildItem C:\,D:\,E:\ -Recurse -Filter ext4.vhdx -ErrorAction SilentlyContinue | Select-Object FullName, Length

# 方法 3:按安装方式找
# - Microsoft Store 安装: %LOCALAPPDATA%\Packages\...\LocalState\ext4.vhdx
# - 手动 wsl --import:  你 import 时指定的路径,如 D:\wsl\RHEL9\ext4.vhdx

手动回收(PowerShell 管理员)

# 1. 关闭所有 WSL 实例
wsl --shutdown

# 2. 用 diskpart 压缩
diskpart

进入 diskpart 后输入:

select vdisk file="D:\wsl\RHEL9\ext4.vhdx"
compact vdisk
exit

⚠️ 注意

  • 用管理员 PowerShell 运行(否则 diskpart 报"权限不足")
  • WSL 要已关(否则 vhdx 被占用会报"文件正被另一个进程使用")
  • compact vdisk 期间会临时再占用 1~2 倍当前 vhdx 大小的空闲空间(D 盘至少留 50%)

一键脚本(推荐每月跑一次)——把脚本放在 D:\wsl\compact-rhel9.ps1

# =============================================
# D:\wsl\compact-rhel9.ps1
# 用途:回收 RHEL 9 WSL2 虚拟磁盘空间
# 用法:右键 → 使用 PowerShell 管理员运行
# =============================================

# 1. 关闭所有 WSL 实例
wsl --shutdown
Start-Sleep -Seconds 3

# 2. 配置 vhdx 路径(手动 import 时的路径)
$vhdxPath = "D:\wsl\RHEL9\ext4.vhdx"

# 如果用 Microsoft Store 版,把上面换成:
# $vhdxPath = "$env:LOCALAPPDATA\Packages\*RHEL*9*\LocalState\ext4.vhdx" | Get-ChildItem | Select-Object -First 1 -ExpandProperty FullName

# 3. 校验
if (-not (Test-Path $vhdxPath)) {
    Write-Error "❌ 找不到 vhdx:$vhdxPath"
    Write-Host "请用 Get-ChildItem 搜索 vhdx 位置后修改脚本"
    exit 1
}

$beforeGB = [math]::Round((Get-Item $vhdxPath).Length / 1GB, 2)
Write-Host "📦 压缩前:$beforeGB GB" -ForegroundColor Cyan

# 4. 写临时 diskpart 脚本
$tmp = Join-Path $env:TEMP "compact-wsl-$PID.txt"
"select vdisk file=`"$vhdxPath`"" | Out-File -Encoding ascii $tmp
"compact vdisk" | Out-File -Append -Encoding ascii $tmp

# 5. 执行(会跑几十秒到几分钟)
diskpart /s $tmp
Remove-Item $tmp

# 6. 报告
$afterGB = [math]::Round((Get-Item $vhdxPath).Length / 1GB, 2)
$saved = [math]::Round($beforeGB - $afterGB, 2)
Write-Host "📦 压缩后:$afterGB GB(回收 $saved GB)" -ForegroundColor Green

定时自动回收(每月 1 号凌晨 2 点):

# 在管理员 PowerShell 里执行一次
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File D:\wsl\compact-rhel9.ps1"
$trigger = New-ScheduledTaskTrigger -Monthly -DaysOfMonth 1 -At "02:00"
Register-ScheduledTask -TaskName "WSL Compact RHEL9 vhdx" -Action $action -Trigger $trigger -RunLevel Highest -Description "每月压缩 RHEL 9 WSL 虚拟磁盘"

8.4.5 常见误区

误区 正确做法
"在 / 建文件夹要分配空间" mkdir 只创建目录项,不预分配磁盘空间;写入文件时才实际占用
"none 7.8G 满了怎么办" none 是 WSL 内部 9P 通道(内存映射),不是磁盘存储;占用高时重启 WSL(wsl --shutdown)即可释放
"根目录可以无限写" ext4.vhdx 配置上限限制(默认 256 GB,可手动扩展)
"重装 WSL 还能找到根目录数据" 不能!WSL 重装会重置根目录;重要数据务必放 /mnt/c/mnt/d 或外部盘
"WSL 里删了大文件,Windows 磁盘空间没释放" WSL2 的 ext4.vhdx 是稀疏文件不会自动缩小;详见 8.4.4 手动压缩

8.4.6 WSL 常用运维命令

# 查看 WSL 版本和状态(Windows PowerShell)
wsl -l -v

# 关闭所有 WSL 实例
wsl --shutdown

# 导出当前发行版做备份(PowerShell)
wsl --export RHEL9 D:\wsl-backup\rhel9-$(Get-Date -Format yyyyMMdd).tar

# 从备份恢复(PowerShell)
wsl --import RHEL9 D:\wsl\RHEL9 D:\wsl-backup\rhel9-xxx.tar

# 设置默认登录用户(在 /etc/wsl.conf 加):
# [user]
# default = youruser

💡 RHEL 9 WSL 默认用户提示

  • wsl --import 装出来的版本默认是 root
  • 第一次进入需用 sudo passwd 给 root 设密码(详见 2.1 节)
  • wsl.conf[user] default = xxx 不会自动写——要手动配

8.5 WSL 内 SELinux 处理

📋 详细说明7.3 SELinux 处理(命令通用)。本节只讲 WSL 下的特殊处理。

RHEL 9 安装时默认开启 SELinux,配置文件 /etc/selinux/configSELINUX=enforcingWSL2 的微软内核不支持完整 SELinux 强制执行(缺 LSM 模块)——在 WSL 里 SELinux 不会真正生效,但服务会刷一些 SELinux 警告日志。

WSL 下推荐处理

# 临时切到 permissive(重启后失效)
sudo setenforce 0

# 永久切到 permissive(编辑配置文件)
sudo sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config

# 验证
getenforce
# 期望输出:Permissive

💡 WSL 下切到 permissive 是安全的

  • WSL 不是生产环境,缺 LSM 模块本来就不能强制 SELinux
  • permissive 模式下 SELinux 仍然记录违反策略的事件(audit log),但不阻止
  • 完全关掉(disabled)反而可能让某些依赖 SELinux 的服务启动失败

8.6 WSL 内 firewalld / fail2ban 关掉

📋 详细说明7.1 firewalld 防火墙7.2 fail2ban 防爆破(命令通用)。本节只讲 WSL 下的特殊处理。

WSL2 的微软内核没编译老 iptables 模块——firewalld 会报 iptables 错误;fail2ban 依赖 iptables 做封禁动作,也跑不动。WSL 内直接关掉这两个

# 关 firewalld
sudo systemctl disable --now firewalld

# 关 fail2ban
sudo systemctl disable --now fail2ban

💡 WSL 下不需要 Linux 防火墙

  • Windows 防火墙是 WSL 流量的真正边界
  • WSL 服务要被 Windows 访问,开 hostAddressLoopback(参考 WSL 文档第 6 节)
  • 容器间网络(Docker bridge)由 Docker 自己管 iptables,跟 NM/firewalld 无关

8.7 WSL 重启正确方式 + 配置生效规则

💡 基本运维命令wsl -l -vwsl --shutdownwsl --export/--import)见 8.4.6 WSL 常用运维命令。本节只讲"重启"和"哪些配置改了必须重启"。

WSL 重启的正确方式在 Windows PowerShell):

# 正确:完全关闭 + 重新打开(重读 wsl.conf / .wslconfig / system.conf)
wsl --shutdown
wsl -d RHEL9
行为 sudo reboot(WSL 内部) wsl --shutdown(PowerShell)
杀进程
重启 WSL 服务 不会
重新读 wsl.conf
重新读 system.conf
重新读 .wslconfig
重新分配内存/CPU 限制

❌ 不要用 sudo reboot

  • WSL 不是真硬件,reboot 找不到 BIOS/固件去调用
  • 微软的 WSL 进程管理由 Windows 侧的 LxssManager 服务控制
  • 必须在 Windows 侧让 LxssManager 重新加载所有发行版,wsl --shutdown 就是干这个的
  • 改了 wsl.conf / system.conf 后,没有 wsl --shutdown 就不会重新读

哪些配置改了必须重启 WSL

配置文件 / 操作 是否需要 wsl --shutdown
/etc/wsl.conf(所有段) 必须
~/.wslconfig(Windows 侧) 必须
/etc/systemd/system.conf 必须(systemd 启动时读)
/etc/security/limits.conf ⚠️ 仅 PAM 登录的进程生效(重开终端就够)
普通 systemd 服务配置 ❌ 用 systemctl restart xxx 即可
软件包安装 / 升级 ❌ 直接生效

🔗 通用 WSL 基础(systemd 启用细节、镜像网络、hostAddressLoopback 实验特性等)详见 WSL 虚拟机创建与管理指南.md 第 4~6 节。

8.7.1 WSL 内文件描述符限制(limits.conf + system.conf)

📋 主内容7.4 文件描述符限制。本节只讲 WSL 下"改了配置但 ulimit -n 还是旧值"这类典型踩坑。

WSL 下的文件描述符限制分三层,每层生效方式不同,混在一起改就会出现"配置看起来对、但实际不生效"的情况。

三层限制与生效方式

配置层 文件 影响范围 生效方式
PAM 限制(用户登录 shell) /etc/security/limits.conf 当前用户 shell 及子进程 重新登录(exit 后重连)
systemd 系统服务限制 /etc/systemd/system.conf 所有 systemd 启动的 service wsl --shutdown + 重启 WSL
systemd 用户服务限制 /etc/systemd/user.conf systemctl --user 启动的服务 wsl --shutdown + 重启 WSL

为什么 WSL 下"改了没生效"特别常见

  • limits.conf 是 PAM 在登录时读取——当前已开的 shell 不会自动重读
  • system.conf 是 systemd 在启动时读取——只有 wsl --shutdown 才能让 PID 1 重新读
  • sudo reboot 在 WSL 下既不能重读 wsl.conf 也不能重读 system.conf,详见 8.7

典型症状与排查

# 1. /etc/security/limits.d/ 有没有"后加载的覆盖"文件
#    limits.d 里按字母序加载,后加载的覆盖前面的
ls -la /etc/security/limits.d/
cat /etc/security/limits.d/*.conf 2>/dev/null

# 2. 当前 shell 的 soft / hard 限制分别是多少
ulimit -Sn   # 软限制
ulimit -Hn   # 硬限制

# 3. systemd service 实际生效的限制(验证 system.conf 是否生效)
systemctl show sshd | grep -i nofile
# 输出类似 LimitNOFILE=65535  ← 说明 system.conf 那条生效了

修复流程

# 步骤 1:处理 limits.d 覆盖(如果有冲突文件)
#   选项 A:删除冲突文件
#   选项 B:编辑冲突文件,把 nofile 改成 65535

# 步骤 2:limits.conf 改动 —— 重新登录(不是 wsl --shutdown)
exit
# 重新连进来(wsl -d RHEL9 或 SSH)

# 步骤 3:system.conf 改动 —— 在 Windows PowerShell
wsl --shutdown
wsl -d RHEL9

# 步骤 4:验证
ulimit -n   # 期望 65535

💡 10240 这个值是哪来的10240 看起来是 shell 启动时的默认值,可能来自 systemd user.conflimits.d/ 里的覆盖、或 WSL 宿主侧。按上面排查命令依次看 soft / hard / systemd 实际值,定位到具体哪一层。

8.7.2 WSL2 特殊:wsl -d 起的 shell 不走 PAM(最常见的踩坑)

上面 8.7.1 三层都假设 shell 走 PAM 登录流程。但 WSL2 下用 wsl -d RHEL9 直接打开的 shell 是非登录 shell,根本不走 PAM、pam_limits.so 不会被加载——/etc/security/limits.conf/etc/security/limits.d/ 完全被绕过。

怎么确认自己中招了

# 这两个命令能 100% 判断
who am i       # 输出非空(如 root pts/0 ...)= 走 PAM
               # 输出空 = 不走 PAM(就是 WSL -d 起的 shell)
logname        # 有输出 = 走 PAM
               # 报 "no login name" = 不走 PAM

典型表现

  • limits.conf 怎么改都无效
  • systemctl show sshd 显示 65535(systemd 服务侧正常)
  • 但当前 shell ulimit -n 始终是 10240 或某个 WSL2 默认值

三种修复方案(按推荐度排序):

方案 A:profile.d 全局补刀(推荐)
sudo tee /etc/profile.d/99-nofile.sh > /dev/null << 'EOF'
# WSL2 下 wsl -d 起的 shell 不走 PAM,limits.conf 不生效
# 在 /etc/profile.d/ 里补一刀,所有交互 shell 都会执行
ulimit -n 65535
EOF
sudo chmod +x /etc/profile.d/99-nofile.sh

# 立即验证(新开会话 / 重新 source)
source /etc/profile.d/99-nofile.sh
ulimit -n   # 期望 65535
方案 B:只对当前用户
echo "ulimit -n 65535" >> ~/.bashrc
source ~/.bashrc
ulimit -n
方案 C:绕开 WSL init 限制,用 SSH 登自己

WSL 内启动 sshd 后(按 5.1 节),从 Windows PowerShell ssh root@localhost —— 这条会走完整 PAM 流程,limits.conf 立即生效。但 Windows → WSL 的 SSH 通常会受 WSL 宿主网络限制,不如方案 A 通用。

方案选择建议

场景 推荐方案
个人开发机,想一劳永逸 方案 A(profile.d)
只想临时试一下 方案 B(~/.bashrc)
已经在用 SSH 登自己 方案 C(天然就走 PAM)
用 systemd unit 跑服务 5.3 sshd_configLimitNOFILE 或加 [Service] LimitNOFILE= drop-in

🔬 验证小技巧:方案 A 装完后,关掉 WSL 窗口、重新 wsl -d RHEL9 打开新窗口(不是新开 tab),执行 who am i + ulimit -n 同时验证。如果新窗口里 ulimit -n 是 65535 且 who am i 还是空——说明 profile.d 生效了,但确实是非登录 shell,符合预期


📖 跳转链接


九、验证检查清单

完成所有配置后,建议逐项验证。

9.1 通用检查(真机 / 云服务器 / WSL 通用)

✅ 检查项 🔧 命令 🎯 预期
系统版本 cat /etc/redhat-release Red Hat Enterprise Linux release 9.x ...
订阅已注册 sudo subscription-manager status Overall Status: Registered
仓库可用 sudo dnf repolist rhel-9-for-x86_64-baseos-rpms
SSH 远程可连 ssh youruser@IP 成功登录(最好密钥免密)
密码登录已关闭 ssh -o PasswordAuthentication=no youruser@IP 仍可登录 = OK
SSH 服务自启 sudo systemctl is-enabled sshd enabled
防火墙已启用 sudo firewall-cmd --state running
防火墙放行 SSH sudo firewall-cmd --list-services ssh
网络连通 ping -c 3 baidu.comcurl -v http://baidu.com 有响应(注意:WSL 默认禁 ICMP,ping 可能不通
DNS 解析 dig baidu.com 返回 IP
时区正确 timedatectl Asia/Shanghai
时间同步 timedatectl show NTPSynchronized=yes
系统已更新 dnf check-update 无输出 = 已最新
FD 限制 ulimit -n 65535(已配置时)
工具齐全 which htop tmux jq tree vim 全部有路径
fail2ban 运行 sudo systemctl status fail2ban active (running)
SELinux 状态 getenforce Enforcing / Permissive(看需求)

9.2 真机 / 云服务器额外检查(WSL 跳过)

✅ 检查项 🔧 命令 🎯 预期
静态 IP 生效 ip addr show <网卡名> IP 与配置一致(网卡名因硬件/虚拟化平台而异:常见如 ens33ens160enp0s3eth0 等,需按实际填写)
默认路由 ip route show default 显示网关 IP
远程可 ping 通 ping <服务器IP>(从另一台机器) 有响应(服务器防火墙可能挡 ICMP)

📋 怎么知道自己的网卡名

# 列所有网卡(找带 inet IP 的那个)
ip -br addr
# 输出示例:
# lo               UNKNOWN        127.0.0.1/8
# ens33            UP             192.168.1.100/24   ← 这就是
# docker0          DOWN           172.17.0.1/16

9.3 WSL2 用户额外检查

💡 配套阅读八、WSL 用户专属指南——所有 WSL 专属配置(systemd 启用、主机名、firewalld 关掉、SELinux 切 permissive 等)都集中在那里。本表只是验证"配置生效了没"。

✅ 检查项 🔧 命令 🎯 预期
WSL 发行版 wsl -l -v(PowerShell 跑) 看到自己的发行版,版本是 2
systemd 已启用 ps -p 1 -o comm= systemd(不是 init
WSL 主机名 hostname 你设的名字(如 rhel9-workspace
主机名持久化 cat /etc/wsl.conf [network] hostname = xxx
hosts 同步 cat /etc/hosts | grep $(hostname) 127.0.1.1 <主机名>
sudo 无警告 sudo whoami 直接返回 root,无 "unable to resolve host"
根目录所在虚拟磁盘 df -h / 容量正常(默认 256G,可手动扩)
根目录使用率 df -h / | tail -1 | awk '{print $5}' < 80%
Windows 盘已挂载 ls /mnt/c /mnt/d 能列出文件
数据软链就绪 ls -ld ~/data 指向 /mnt/d/wsl-data(如已建)
默认 /data 在 ls -ld /data 存在
WSL 发行版 cat /etc/os-release Red Hat Enterprise Linux 9
WSL 重启生效 wsl --shutdownwsl(PowerShell 跑) 配置变更生效
WSL 镜像模式(按需) ip addr show eth0 inet 是 192.168.x 或 10.x(不是 172.x)

9.4 一键自检脚本(通用 + WSL)

保存为 ~/check-env.shchmod +x ~/check-env.sh 后跑:

#!/bin/bash
# =============================================
# RHEL 9 初始环境一键自检脚本
# =============================================
echo "================================"
echo "RHEL 9 初始环境自检"
echo "================================"

echo -e "\n[1] 系统信息"
echo "版本:$(grep PRETTY_NAME /etc/os-release | cut -d= -f2- | tr -d '"')"
echo "内核:$(uname -r)"
echo "当前用户:$(whoami)"

echo -e "\n[2] Red Hat 订阅"
if command -v subscription-manager &>/dev/null; then
  if subscription-manager status 2>/dev/null | grep -q "Overall Status: Registered"; then
    echo "✅ 订阅已注册"
  else
    echo "❌ 订阅未注册(dnf install 装不上东西)"
  fi
else
  echo "⚠️ subscription-manager 未装(不太可能在 RHEL 9)"
fi

echo -e "\n[3] 仓库可用"
dnf repolist 2>/dev/null | grep -c "rhel-9\|epel" | xargs -I{} echo "已启用仓库数(含 rhel-9 / epel): {}"

echo -e "\n[4] SSH 服务"
systemctl is-enabled sshd &>/dev/null && echo "✅ sshd 自启" || echo "❌ sshd 未自启"
systemctl is-active sshd &>/dev/null && echo "✅ sshd 运行中" || echo "❌ sshd 未运行"

echo -e "\n[5] 防火墙"
if command -v firewall-cmd &> /dev/null; then
  if systemctl is-active firewalld &>/dev/null; then
    firewall-cmd --list-services | head -3
  else
    echo "⚠️ firewalld 已装但未运行(WSL 下可能需关掉)"
  fi
else
  echo "⚠️ firewalld 未装"
fi

echo -e "\n[6] SELinux"
echo "当前模式:$(getenforce 2>/dev/null || echo 'N/A')"

echo -e "\n[7] 时区与时间"
timedatectl | grep -E "Time zone|System clock"

echo -e "\n[8] 网络"
ip -br addr | grep -v "DOWN" | head -5

echo -e "\n[9] DNS"
cat /etc/resolv.conf 2>/dev/null | grep nameserver | head -2

echo -e "\n[10] 资源限制"
echo "ulimit -n: $(ulimit -n)"
echo "ulimit -u: $(ulimit -u)"

echo -e "\n[11] 工具检查"
for tool in htop tmux jq tree vim git curl wget dnf subscription-manager; do
  if command -v $tool &> /dev/null; then
    echo "✅ $tool"
  else
    echo "❌ $tool 缺失"
  fi
done

echo -e "\n[12] systemd 状态(WSL 关注)"
ps -p 1 -o comm= | grep -q systemd && echo "✅ systemd 已启用" || echo "❌ PID 1 不是 systemd(WSL 需配置 wsl.conf)"

echo -e "\n[13] 主机名"
echo "当前:$(hostname)"
grep -q "^127.0.1.1 $(hostname)" /etc/hosts && echo "✅ /etc/hosts 同步" || echo "⚠️ /etc/hosts 未同步(sudo 可能报 unable to resolve host)"

echo -e "\n[14] WSL 特定(如适用)"
if grep -qi "microsoft" /proc/version 2>/dev/null; then
  echo "✅ WSL 环境"
  grep -q "^\[network\]" /etc/wsl.conf && echo "✅ wsl.conf 有 [network] 段" || echo "⚠️ wsl.conf 缺 [network] 段(主机名可能不持久)"
else
  echo "非 WSL 环境(真机/云服务器)"
fi

echo -e "\n================================"
echo "自检完成"
echo "================================"

十、常用命令速查

10.1 服务管理

🔧 操作 🔧 RHEL 9 / AlmaLinux 🔧 Ubuntu 24.04
启动 sudo systemctl start sshd sudo systemctl start ssh
停止 sudo systemctl stop sshd sudo systemctl stop ssh
状态 sudo systemctl status sshd sudo systemctl status ssh
开机自启 sudo systemctl enable sshd sudo systemctl enable ssh
重载配置 sudo systemctl reload sshd sudo systemctl reload ssh
服务名 sshd ssh
配置文件 /etc/ssh/sshd_config /etc/ssh/sshd_config(路径相同)

📋 关键差异:Ubuntu 把 SSH 服务名从 RHEL 的 sshd 改成了 ssh,其他都一致。

10.2 包管理对照

🔧 操作 🔧 RHEL 9 (dnf) 🔧 Ubuntu 24.04 (apt)
更新源索引 sudo dnf makecache sudo apt update
安装包 sudo dnf install -y pkg sudo apt install -y pkg
删除包 sudo dnf remove pkg sudo apt remove pkg
搜索包 dnf search keyword apt search keyword
包信息 dnf info pkg apt show pkg
列出已装 dnf list installed apt list --installed
升级所有 sudo dnf upgrade -y sudo apt upgrade -y
升级发行版 sudo dnf upgrade --releasever=10 sudo do-release-upgrade
清理缓存 sudo dnf clean all sudo apt clean
自动清理无用包 sudo dnf autoremove sudo apt autoremove
包组 dnf groupinstall "Development Tools" apt install build-essential
查看仓库 sudo dnf repolist apt-cache policy

10.3 网络命令对照

🔧 用途 🔧 RHEL 9 🔧 Ubuntu 24.04
查看 IP ip addr / ifconfig ip addrifconfig 需装 net-tools
查看端口 netstat -tlnp netstat -tlnp(需装 net-tools) / ss -tlnp
测试连通 ping / traceroute ping / traceroute(需装)
DNS 查询 nslookup / dig dig(需装 dnsutils
防火墙 firewalld / firewall-cmd ufw
网络配置 nmcli / nmtui(NetworkManager) netplan/etc/netplan/*.yaml
主机名 hostnamectl set-hostname x hostnamectl set-hostname x(相同)
安全模块 setenforce 0 / getenforce 无(Ubuntu 用 AppArmor)

10.4 时间与日志对照

🔧 用途 🔧 RHEL 9 🔧 Ubuntu 24.04
时区设置 timedatectl set-timezone ... 相同
时间同步 chrony(默认) systemd-timesyncd(默认) / chrony
系统日志 /var/log/messages /var/log/syslog
认证日志 /var/log/secure /var/log/auth.log
查看日志 journalctl -u sshd journalctl -u ssh
SELinux 审计 /var/log/audit/audit.log
Red Hat 订阅日志 /var/log/rhsm/rhsm.log

10.5 防火墙命令对照

🔧 操作 🟢 firewalld(RHEL 9 默认) 🟡 iptables(底层通用) 🔵 nftables(现代替代) 🔴 ufw(Ubuntu 默认)
查看状态 sudo firewall-cmd --state sudo iptables -L -n -v sudo nft list ruleset sudo ufw status verbose
查看规则 sudo firewall-cmd --list-all sudo iptables -L -n -v --line-numbers sudo nft list ruleset sudo ufw status numbered
启动/启用 sudo systemctl enable --now firewalld 开机自启需 iptables-persistent 开机自启需 nftables.service sudo ufw enable
放行 22 端口 sudo firewall-cmd --permanent --add-service=ssh sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT sudo nft add rule inet filter input tcp dport 22 accept sudo ufw allow 22/tcp
放行服务名 sudo firewall-cmd --permanent --add-service=http sudo ufw allow http
放行 IP 段 sudo firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=192.168.1.0/24 accept' sudo iptables -A INPUT -s 192.168.1.0/24 -j ACCEPT sudo nft add rule inet filter input ip saddr 192.168.1.0/24 accept sudo ufw allow from 192.168.1.0/24
拒绝某 IP sudo firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=10.0.0.5 reject' sudo iptables -A INPUT -s 10.0.0.5 -j DROP sudo nft add rule inet filter input ip saddr 10.0.0.5 drop sudo ufw deny from 10.0.0.5
重新加载 sudo firewall-cmd --reload sudo iptables-restore < /etc/iptables/rules.v4 sudo nft -f /etc/nftables.conf 自动
关闭防火墙 sudo systemctl stop firewalld sudo iptables -F(清空规则,慎用) sudo nft flush ruleset sudo ufw disable
命令缺失 sudo dnf install -y firewalld RHEL 自带 sudo dnf install -y nftables sudo apt install -y ufw

📋 选择建议

  • 标准 RHEL 9 优先用 firewalld(本教程主推),zone 概念比 ufw 略复杂但集成度高。
  • 需要复杂规则(端口转发、NAT、限速)用 iptables / nftables
  • 跨平台脚本或 Ubuntu 兼容场景用 ufw(RHEL EPEL 里有)。

10.6 订阅管理速查

🔧 操作 🔧 命令
注册系统 sudo subscription-manager register --username X --password 'Y'
刷新订阅 sudo subscription-manager refresh
查看状态 sudo subscription-manager status
查看身份 sudo subscription-manager identity
列出可用仓库 sudo subscription-manager repos --list
启用仓库 sudo subscription-manager repos --enable=rhel-9-for-x86_64-baseos-rpms
禁用仓库 sudo subscription-manager repos --disable=rhel-9-for-x86_64-baseos-rpms
注销系统 sudo subscription-manager unregister
清理缓存 sudo subscription-manager clean
查看已注册系统列表 Red Hat Subscription Manager

📋 常用仓库 id

  • rhel-9-for-x86_64-baseos-rpms —— BaseOS
  • rhel-9-for-x86_64-appstream-rpms —— AppStream
  • codeready-builder-for-rhel-9-x86_64-rpms —— CRB(开发工具链)
  • EPEL 仓库由 epel-release 包提供,不用 subscription-manager

10.7 一键脚本(新建服务器跑一次)

📋 脚本定位

  • 本脚本对应本文档 第二章(初始环境检查)、第三章(订阅与仓库)、第四章(SSH 服务)、第五章(常用工具)、第七章(安全与优化)的最小可用集
  • 不包含的内容:6.1(静态 IP,按网络环境差异较大)、6.2(主机名,个性化)、4.5(密钥免密,建议手动执行以免覆盖既有密钥)、7.3(SELinux 高级配置,生产环境按需)、第八章 WSL 用户专属指南(WSL 用户单独走那一章)。
  • 默认按"真机 / 云服务器"编写,脚本会自动识别 WSL2 环境并跳过不适用项(firewalldrebootsysctl 等)。
#!/bin/bash
# =============================================
# RHEL 9 初始环境一键配置脚本
# 对应文档:RHEL-9与WSL2初始环境配置与常用工具安装教程.md 第 10.7 节
# 用法:sudo bash init-rhel9.sh
# 前置:已按 3.1 节注册 Red Hat 订阅
# 幂等:重复运行不会破坏既有配置(已存在配置会跳过)
# =============================================
set -euo pipefail

# ---------- 颜色输出 ----------
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
log()  { echo -e "${GREEN}[✓]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
err()  { echo -e "${RED}[✗]${NC} $*"; }

# ---------- 0. 权限与环境探测 ----------
if [[ $EUID -ne 0 ]]; then
  err "请使用 root 或 sudo 运行本脚本"
  exit 1
fi

# WSL2 探测:/proc/version 含 "microsoft" 或 "WSL"
IS_WSL=0
grep -qiE 'microsoft|wsl' /proc/version 2>/dev/null && IS_WSL=1

# 当前登录用户(sudo 场景下 SUDO_USER 非空;root 直登时退到默认 youruser)
REAL_USER="${SUDO_USER:-${USER:-youruser}}"
[[ "$REAL_USER" == "root" ]] && REAL_USER="youruser"

log "运行环境:$( [[ $IS_WSL -eq 1 ]] && echo 'WSL2' || echo '真机/云服务器' )"
log "操作用户:$REAL_USER"

# ---------- 0.5 订阅检查(对应 3.1 / 3.2) ----------
if ! subscription-manager status 2>/dev/null | grep -q "Overall Status: Registered"; then
  err "系统未注册 Red Hat 订阅,请先按 3.1 节执行 subscription-manager register"
  err "命令:sudo subscription-manager register --username <账号> --password <密码>"
  exit 1
fi
log "订阅状态:已注册"

# 启用 CRB(按需;EPEL 装包时用得到)
dnf config-manager --enable crb 2>/dev/null || true

# ---------- 1. 更新系统(对应 3.2) ----------
log "[1/9] 更新 dnf 源与软件包"
dnf makecache
# upgrade 升级所有包;如不希望升级可注释掉下一行
dnf upgrade -y

# ---------- 2. 常用工具(对应第五章) ----------
log "[2/9] 安装常用工具集"
dnf install -y \
  openssh-server openssh-clients \
  vim nano curl wget git \
  --allowerasing \
  net-tools bind-utils iputils traceroute mtr \
  unzip p7zip p7zip-plugins lrzsz \
  bash-completion tmux \
  htop iotop iftop sysstat lsof strace ncdu \
  ripgrep fd-find jq tree \
  fail2ban firewalld \
  subscription-manager

# 装 EPEL(如已装会跳过)
if ! rpm -q epel-release &>/dev/null; then
  dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm
  log "EPEL 已装"
fi

# fd-find 在 RHEL 上是 /usr/bin/fd-find,alias 一下
if ! grep -q "alias fd=" /home/$REAL_USER/.bashrc 2>/dev/null; then
  echo "alias fd='fd-find'" >> /home/$REAL_USER/.bashrc
fi

# ---------- 3. SSH 服务(对应 4.1 / 4.3) ----------
log "[3/9] 配置 SSH 服务"
# 备份原配置(仅首次备份)
[[ -f /etc/ssh/sshd_config ]] && [[ ! -f /etc/ssh/sshd_config.bak ]] && \
  cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak

# 用 sed 调整关键项(保留其它已有配置)
SSHD_CFG=/etc/ssh/sshd_config
sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/'              "$SSHD_CFG"
sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' "$SSHD_CFG"
sed -i 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/'    "$SSHD_CFG"
sed -i 's/^#\?X11Forwarding.*/X11Forwarding no/'                    "$SSHD_CFG"
sed -i 's/^#\?MaxAuthTries.*/MaxAuthTries 3/'                       "$SSHD_CFG"

# 启用并启动 SSH(WSL2 启用 systemd 后 systemctl 才会生效;未启用时为 no-op)
if [[ $IS_WSL -eq 0 ]]; then
  systemctl enable --now sshd >/dev/null 2>&1 || warn "sshd 服务启动失败,请检查 systemd"
else
  warn "WSL2 环境:未启用 systemd 时 systemctl 不可用,请按 WSL 文档启用后再重启"
fi

# 校验配置(错误则不重启,避免失联)
if command -v sshd >/dev/null 2>&1; then
  sshd -t && log "sshd_config 语法校验通过" || err "sshd_config 语法错误,请检查 $SSHD_CFG"
fi

# ---------- 4. 防火墙(对应 7.1 / 第八章 8.6) ----------
log "[4/9] 配置 firewalld 防火墙"
if [[ $IS_WSL -eq 0 ]]; then
  systemctl enable --now firewalld >/dev/null 2>&1 || warn "firewalld 启动失败"
  firewall-cmd --permanent --add-service=ssh >/dev/null 2>&1 || true
  firewall-cmd --reload >/dev/null 2>&1 || true
  log "firewalld 已启用(默认放行 ssh)"
else
  warn "WSL2 环境:firewalld 微软内核不支持,建议 disable,由 Windows 防火墙控端口"
  systemctl disable --now firewalld >/dev/null 2>&1 || true
fi

# ---------- 5. fail2ban(对应 7.2 / 第八章 8.6) ----------
log "[5/9] 配置 fail2ban"
JAIL_LOCAL=/etc/fail2ban/jail.local
if [[ ! -f $JAIL_LOCAL ]]; then
  cat > "$JAIL_LOCAL" <<'EOF'
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5
banaction = firewallcmd-rich-rules
banaction_allports = firewallcmd-rich-rules

[sshd]
enabled = true
port    = ssh
filter  = sshd
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
EOF
  log "已生成 $JAIL_LOCAL"
else
  warn "$JAIL_LOCAL 已存在,跳过覆盖(手动调整请编辑该文件)"
fi

if [[ $IS_WSL -eq 0 ]]; then
  systemctl enable --now fail2ban >/dev/null 2>&1 || warn "fail2ban 启动失败"
else
  warn "WSL2 环境:未启用 systemd 时 fail2ban 无法后台常驻"
fi

# ---------- 6. 时区与时间同步(对应 6.3) ----------
log "[6/9] 配置时区与时间同步"
timedatectl set-timezone Asia/Shanghai
if [[ $IS_WSL -eq 0 ]]; then
  systemctl enable --now chronyd >/dev/null 2>&1 || true
fi
log "当前时区:$(timedatectl | grep 'Time zone' | awk '{print $3}')"

# ---------- 7. 文件描述符限制(对应 7.4) ----------
log "[7/9] 配置文件描述符限制"
LIMITS_CONF=/etc/security/limits.conf
# 幂等:仅当 nofile 65535 不存在时追加
if ! grep -qE '^\*\s+soft\s+nofile\s+65535' "$LIMITS_CONF" 2>/dev/null; then
  cat >> "$LIMITS_CONF" <<'EOF'

# === Added by init-rhel9.sh ===
*   soft    nofile  65535
*   hard    nofile  65535
root soft    nofile  65535
root hard    nofile  65535
EOF
  log "已追加 nofile 限制到 $LIMITS_CONF"
else
  warn "$LIMITS_CONF 已包含 nofile 65535,跳过"
fi

# systemd 服务级 FD 限制
SYSTEMD_CONF=/etc/systemd/system.conf
if ! grep -qE '^DefaultLimitNOFILE=65535' "$SYSTEMD_CONF" 2>/dev/null; then
  sed -i 's/^#\?DefaultLimitNOFILE=.*/DefaultLimitNOFILE=65535/' "$SYSTEMD_CONF" || \
    echo "DefaultLimitNOFILE=65535" >> "$SYSTEMD_CONF"
  log "已设置 DefaultLimitNOFILE=65535"
else
  warn "$SYSTEMD_CONF 已包含 DefaultLimitNOFILE=65535,跳过"
fi

# ---------- 8. 内核参数调优(对应 7.5) ----------
log "[8/9] 配置内核参数调优"
SYSCTL_FILE=/etc/sysctl.d/99-tuning.conf
if [[ ! -f $SYSCTL_FILE ]]; then
  cat > "$SYSCTL_FILE" <<'EOF'
# === Added by init-rhel9.sh ===
# 网络优化
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_fin_timeout = 15
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 8192
net.ipv4.tcp_syncookies = 1

# 安全加固
net.ipv4.ip_forward = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.log_martians = 1

# Swap 使用倾向(值越小越倾向用物理内存)
vm.swappiness = 10
EOF
  log "已生成 $SYSCTL_FILE"
else
  warn "$SYSCTL_FILE 已存在,跳过覆盖(如需重置请手动删除)"
fi

# 应用 sysctl(非 WSL 立即生效;WSL 大部分参数会被宿主内核忽略)
if [[ $IS_WSL -eq 0 ]]; then
  sysctl -p "$SYSCTL_FILE" >/dev/null 2>&1 || warn "sysctl 部分参数应用失败(多因模块未加载,可忽略)"
else
  warn "WSL2 环境:sysctl 大部分参数由 Windows 宿主内核控制,配置仅作记录"
fi

# ---------- 9. 完成提示 ----------
echo
log "初始化完成"
echo "------------------------------------------------"
echo "  下一步:"
echo "  1) 在本机执行:ssh-copy-id ${REAL_USER}@\$(hostname -I | awk '{print \$1}')"
echo "  2) 验证密钥登录成功后,再把 PasswordAuthentication 设为 no 永久生效"
echo "  3) 真机/云服务器需 reboot;WSL2 在 Windows PowerShell 执行 wsl --shutdown"
echo "  4) SELinux 状态:$(getenforce 2>/dev/null || echo 'N/A')(生产建议保持 enforcing)"
if [[ $IS_WSL -eq 1 ]]; then
  echo "  5) WSL 用户专属配置:见第八章 WSL 用户专属指南(systemd 启用、主机名、vhdx 回收等)"
fi
echo "------------------------------------------------"

💡 文档维护提示

  • 上述所有配置均在 RHEL 9.x 实测通过。
  • RHEL 8 大部分兼容,仅仓库 id 命名(base / AppStream vs rhel-9-for-x86_64-baseos-rpms)、CRB 名字(powertools vs crb)、部分 SELinux 策略有差异。
  • 订阅模式:SCA(Simple Content Access)下注册即获仓库访问权,无需手动 attach;如切换到经典模式需 subscription-manager attach --auto
  • SELinux 行为取决于具体启用模式,调试时优先用 ausearch + audit2allow 生成策略,而非全局 permissive。
  • WSL 行为(systemd 启用、虚拟磁盘回收、/etc/wsl.conf 配置等)详见 WSL 虚拟机创建与管理指南.md
  • RHEL 与 AlmaLinux 的差异详见本文档 3.1 / 3.2 / 3.3 / 3.4 节,二者在大多数运维场景下命令完全一致dnfsystemctlfirewall-cmdhostnamectl 等)。
posted @ 2026-08-18 11:03  RK5123153  阅读(13)  评论(0)    收藏  举报