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 镜像
目录
- 一、文档总览
- 二、初始环境检查
- 三、Red Hat 订阅与仓库(核心)
- 四、常用工具集
- 五、SSH 远程服务
- 六、网络与系统配置
- 七、安全与优化
- 八、WSL 用户专属指南(WSL2 用户必读)
- 九、验证检查清单
- 十、常用命令速查
一、文档总览
章节索引:
| 序号 | 章节 | 说明 |
|---|---|---|
| 一 | 文档总览 | 流程图 + 索引 |
| 二 | 初始环境检查 | 确认系统版本 / 内核 / 当前用户 / 订阅状态 |
| 三 | 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 默认
PermitRootLogin是prohibit-password(允许密钥但禁止密码登录 root),生产环境建议显式设为no(详见 4.3)。- WSL 环境下:首次
wsl --import装出来的版本默认以 root 登录,没有 root 密码;sudo passwd root设上之后更方便维护。
🔐 安全建议:
- root 密码应使用强密码(长度 12 位以上,包含大小写字母、数字、特殊字符)。
- 日常操作优先使用
sudo,仅在必要时切换到 root。
三、Red Hat 订阅与仓库(核心)
🎯 章节定位:RHEL 9 装完后必须完成本章才能
dnf install。AlmaLinux 不需要这一步(默认仓库就开),这是两者最大的差异。推荐阅读顺序:
- 3.1 注册开发者订阅 —— 必做(免费 16 个实例授权)
- 3.2 验证仓库可用 —— 确认注册成功
- 3.3 启用 EPEL / CRB —— 装第三方包(按需)
- 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'
🔐 安全注意:
- 密码含特殊字符(
!&*$)必须用单引号包裹。- 真实账号不要写入文档、截图或代码仓库;推荐用环境变量或交互式输入:
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-rpmsAlmaLinux 9 baseos/appstreamRocky Linux 9 baseos/appstreamCentOS Stream 9 baseos/appstream写脚本时不要假设
baseos是 RHEL 的仓库 id(RHEL 的 baseurl 默认指向 Red Hat CDN,不是国内镜像)。
3.3 启用 EPEL 与 CRB(按需)
📋 用途说明:
- EPEL(Extra Packages for Enterprise Linux):社区维护的扩展包,很多常用包在这里(
htop、fail2ban、p7zip、fd-find等)- CRB(CodeReady Builder):相当于 RHEL 8 的 PowerTools,包含开发工具链的元数据
3.3.1 启用 CRB(开发工具链元数据)
📋 本节场景:装
*-devel编译包、源码编译时才需要 CRB。日常用包装 EPEL 即可(参见 3.3.2 节),可以跳过本节。
⚠️ 关键提醒:
dnf config-manager --enable crb在 WSL 装的官方 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——根因有两层:
-
仓库 id 命名差异:裸机/CDN 默认 rootfs 的 redhat.repo 段 id 叫
crb(短名),WSL EUS rootfs 是codeready-builder-for-rhel-9-x86_64-rpms(长名)。dnf config-manager找的是仓库 id 字面值,匹配不到长名。 -
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-release。EPEL 不依赖 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 速度可接受时跳过本节。
⚠️ 换源前:
- 必须已经注册(3.1 节),否则换了源也访问不到内容
- 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/...),跟早期版本的rhui或pulp路径不同。换源时匹配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.com → mirrors.aliyun.com/rhel |
RHEL / EPEL 都覆盖 |
| 华为云 | cdn.redhat.com → mirrors.huaweicloud.com/rhel |
通用 |
| 腾讯云 | cdn.redhat.com → mirrors.cloud.tencent.com/rhel |
通用 |
| 清华 TUNA | 仅 EPEL:download.fedoraproject.org → mirrors.tuna.tsinghua.edu.cn/epel |
学术网友好 |
📋 注意:上述镜像只镜像了 BaseOS / AppStream / EPEL。CodeReady 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 -tlnp、ss -s |
ip |
网络配置 | ip addr、ip 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_keys的chmod 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.service被 mask(屏蔽),即使手动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-timesyncd:sudo 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能看到publiczone 有2211/tcp,但--zone=public --list-interfaces输出里没有eth0firewall-cmd --get-active-zones显示eth0在dockerzone 或其它 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 里建一个
dockerzone(/usr/lib/firewalld/zones/docker.xml),并把docker0网桥绑进去 - docker 启动容器时默认会把端口映射规则写到 iptables 的
DOCKER链(绕过 firewalld 的 zone 检查),所以容器暴露的端口一般不踩这个坑 - 但宿主机自己起的服务(比如 systemctl 启动的 sshd / nginx / 自定义应用)仍然走 firewalld zone 匹配,需要按上面方法处理
- 如果
eth0被 docker 抢到了dockerzone,而dockerzone 默认只放行 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/securebanaction用firewallcmd-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=1024,hard=524288- 所有用
systemctl启动的服务(fail2ban、sshd、nginx、mysql等)都受这个限制- 默认 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_forward、tcp_*等设置可能没真正生效
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 专属操作集中到这一章。真机/云服务器用户可整章跳过。
推荐阅读顺序:
- 8.1 启用 systemd —— 先做这一步(没启用前 systemctl 等工具不可用)
- 8.2 主机名 —— 消除
sudo unable to resolve host警告- 8.3 时区 —— 可选(WSL 默认跟 Windows 同步)
- 8.4 数据目录规划 —— WSL 重装会丢数据,提前软链到 Windows 盘
- 8.5 SELinux —— 减少日志噪音(或跳过,WSL 内 SELinux 不生效)
- 8.6 firewalld / fail2ban —— WSL 内通常跑不起来,建议关掉
- 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 改一行:等号前后的空格、
= truevs=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/config 里 SELINUX=enforcing。WSL2 的微软内核不支持完整 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 -v、wsl --shutdown、wsl --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.conf、limits.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_config 的 LimitNOFILE 或加 [Service] LimitNOFILE= drop-in |
🔬 验证小技巧:方案 A 装完后,关掉 WSL 窗口、重新
wsl -d RHEL9打开新窗口(不是新开 tab),执行who am i+ulimit -n同时验证。如果新窗口里ulimit -n是 65535 且who am i还是空——说明 profile.d 生效了,但确实是非登录 shell,符合预期。
📖 跳转链接:
- 前置章节:8.7.1 WSL 内文件描述符限制(讲三层限制与生效方式)
- 配套:8.7 WSL 重启正确方式(
wsl --shutdownvssudo reboot)
九、验证检查清单
完成所有配置后,建议逐项验证。
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.com 或 curl -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 与配置一致(网卡名因硬件/虚拟化平台而异:常见如 ens33、ens160、enp0s3、eth0 等,需按实际填写) |
| 默认路由 | 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 --shutdown 后 wsl(PowerShell 跑) |
配置变更生效 |
| WSL 镜像模式(按需) | ip addr show eth0 |
inet 是 192.168.x 或 10.x(不是 172.x) |
9.4 一键自检脚本(通用 + WSL)
保存为 ~/check-env.sh,chmod +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 addr(ifconfig 需装 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—— BaseOSrhel-9-for-x86_64-appstream-rpms—— AppStreamcodeready-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 环境并跳过不适用项(
firewalld、reboot、sysctl等)。
#!/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/AppStreamvsrhel-9-for-x86_64-baseos-rpms)、CRB 名字(powertoolsvscrb)、部分 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 节,二者在大多数运维场景下命令完全一致(
dnf、systemctl、firewall-cmd、hostnamectl等)。

浙公网安备 33010602011771号