Rocky Linux 9 · 服务器运维课程 · 第 1—3 讲听课笔记
目标:把一台"裸奔"的阿里云 ECS(Rocky Linux 9.8,2核2G)加固成只认密钥、守得住、会自动反击的服务器。贯穿全章的三个习惯:私钥永不外传、改配置先留后路、先验证再收口。
服务器接入公网就会被自动化扫描程序爆破 22 端口。先看证据——查看 SSH 登录失败日志:
sudo journalctl -u sshd | grep -i "failed" | tail -30
会看到大量来自陌生 IP 的失败记录,绝大多数在尝试 root 用户。
验证通道:VNC 登录后 whoami 应显示用户名,tty 显示 /dev/tty1(本地控制台);SSH 登录则显示 /dev/pts/0(网络伪终端)。
生成一对配对钥匙:私钥留在自己电脑上绝不外传,是身份的唯一凭证;公钥放到服务器上可公开。服务器用公钥出"数学难题",只有持私钥者能解开。
sudo adduser study # 新建普通用户
sudo passwd study # 设置登录密码
sudo usermod -aG wheel study # 加入 wheel 组才能用 sudo
ssh-keygen -t ed25519 -C "你的名字-服务器加固"
一路回车即可。查看公钥(.pub 结尾的是公钥,另一个是私钥切勿外传):
type $env:USERPROFILE\.ssh\id_ed25519.pub # Windows PowerShell
cat ~/.ssh/id_ed25519.pub # Mac / Linux
sudo su - study
mkdir -p ~/.ssh
chmod 700 ~/.ssh # 目录仅本人可进入
vi ~/.ssh/authorized_keys # 粘贴公钥(一整行):i 粘贴 → Esc → :wq
chmod 600 ~/.ssh/authorized_keys # 文件仅本人可读写
ls -l ~/.ssh # 核对:-rw-------,属主是本人
ssh study@服务器公网IP # 不需要输入密码即为成功
sudo adduser teacher
sudo mkdir -p /home/teacher/.ssh
sudo vi /home/teacher/.ssh/authorized_keys # 贴入老师的公钥
sudo chmod 700 /home/teacher/.ssh
sudo chmod 600 /home/teacher/.ssh/authorized_keys
sudo chown -R teacher:teacher /home/teacher/.ssh
# teacher 无密码,wheel 组 sudo 要输密码,所以单独配免密 sudo:
echo "teacher ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/teacher
sudo chmod 440 /etc/sudoers.d/teacher
sudo visudo -c # 语法检查,全部 parsed OK 才行
sudo -u teacher sudo -n true && echo "teacher 免密 sudo 可用"
sudo vi /etc/ssh/sshd_config
PubkeyAuthentication yes # 允许密钥登录
PasswordAuthentication no # 禁止密码登录(确认密钥可用后再改!)
PermitRootLogin no # 禁止 root 直接远程登录
MaxAuthTries 3 # 单次连接最多 3 次认证尝试
ClientAliveInterval 300 # 每 5 分钟探测一次客户端
ClientAliveCountMax 2 # 连续 2 次无响应就断开空闲会话
AllowUsers study teacher # 登录白名单,漏写会挡人
sudo sshd -t # 先测语法,无输出即正确
sudo systemctl reload sshd # reload 不断开当前连接,比 restart 安全
两层相互独立都要配置,原则是最小化:只开必须的门。
sudo firewall-cmd --permanent --add-service=ssh # 永久放行 SSH
sudo firewall-cmd --reload # 重新加载生效
sudo firewall-cmd --list-all # 核对放行结果
--permanent + --reload,否则重启后失效。安全组按"协议+端口"两个维度控制。TCP/UDP 才有端口概念;ICMP 是网络层诊断协议(ping),本身不使用端口,所以端口栏显示 -1/任何只是占位符,并非打开所有端口。保留 ICMP 规则的好处:SSH 连不上时可先 ping 判断问题出在网络还是 SSH 配置。
sudo ss -tlnp # -t TCP -l 监听 -n 数字显示 -p 进程;与安全组规则对照
持续盯登录日志,发现某 IP 反复失败登录就自动封禁。
sudo dnf install -y epel-release # Rocky 默认源没有 fail2ban,先装 EPEL
sudo dnf install -y fail2ban
sudo vi /etc/fail2ban/jail.d/sshd.local # 写自己的规则,不改 jail.conf
[sshd]
enabled = true
backend = systemd # Rocky 9 关键项:从 systemd 日志读取,别照抄旧教程
maxretry = 3 # 最大失败次数
findtime = 600 # 统计窗口 10 分钟
bantime = 3600 # 封禁 1 小时
连起来就是:10 分钟内失败 3 次,封禁一小时。
sudo systemctl enable --now fail2ban # 启动并开机自启
sudo fail2ban-client status sshd # 看 Banned IP list
sudo sshd -t 无报错sshd -t 验证后 reload。.ssh 700、authorized_keys 600、属主是本人。| 攻击者想做的事 | 部署的加固 |
|---|---|
| 猜密码登进来 | SSH 密钥登录 + 禁用密码 + 禁止 root |
| 从别的端口钻进来 | firewalld + 安全组,两层最小化放行 |
| 反复爆破 SSH | fail2ban 自动封禁 |
| 进来了不被发现 | journal 日志审计 |
目标:为已备案域名配置解析,安装 Nginx,发布带备案号的个人主页。打通三个环节:① DNS 解析 → ② 开放 80 端口 → ③ Nginx 发布网页。
DNS(域名系统)是互联网的"通讯录",把域名翻译成 IP 地址。先做解析是因为 DNS 记录需要时间全网生效——把需要等待的事提前做,是运维常用思路。
| 字段 | 含义 |
|---|---|
| 记录类型 | A 记录:把域名指向一个 IPv4 地址 |
| 主机记录 | @ 代表域名本身;www 代表 www.域名 |
| TTL | 记录在各地 DNS 的缓存时长,默认 10 分钟 |
操作:云解析 DNS → 添加两条 A 记录(@ 和 www,记录值都填服务器公网 IP)。验证:
nslookup 你的域名
nslookup www.你的域名 # Address 应为服务器公网 IP
"最小化"不是什么都不开,而是服务上线时才开对应的门。本章只开 80,443 留到下一章。两层门都要开:
0.0.0.0/0sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
sudo firewall-cmd --list-services # 应包含 ssh 和 http
sudo dnf install -y nginx
sudo systemctl enable --now nginx # 立即启动并开机自启
systemctl status nginx # 应显示 active (running)
nginx -v
浏览器访问 http://服务器公网IP,看到 Nginx 默认测试页即安装成功。
sudo mkdir -p /var/www/mysite
sudo chown -R study:study /var/www/mysite # 交给 study,之后不用 sudo
sudo chmod 755 /var/www/mysite
ls -ld /var/www/mysite # 应显示 drwxr-xr-x study study
getenforce 显示 Disabled),本章不涉及;但其他环境遇到 403 时记得 SELinux 也是排查方向。sudo vi /etc/nginx/conf.d/mysite.conf
server {
listen 80;
server_name example.com www.example.com; # 带 www 和不带 www 都要写
root /var/www/mysite;
index index.html;
access_log /var/log/nginx/mysite_access.log;
error_log /var/log/nginx/mysite_error.log;
location / {
try_files $uri $uri/ =404;
}
}
server_name 最关键:一台服务器可跑多个网站,Nginx 靠 HTTP 请求头里的 Host 字段与 server_name 匹配来区分站点。下一章申请证书也靠这一行识别域名。
sudo nginx -t # 先测语法:syntax is ok
sudo systemctl reload nginx # 再 reload 生效,不中断访问
https://beian.miit.gov.cn/(新窗口打开)。这是合规要求,不是可选项。上传方式二选一:
scp index.html study@服务器公网IP:/var/www/mysite/ # 方式一:scp 上传,用同一把密钥免密
vi /var/www/mysite/index.html # 方式二:直接在服务器编辑
用 IP 访问时请求的 Host 不是你的域名,匹配不上你的站点,被交给默认站点。可亲手验证:
curl -H "Host: 你的域名" http://127.0.0.1 # 返回你的主页
curl http://127.0.0.1 | head -5 # 返回默认测试页
亲手制造 403 再修复:
echo "<h1>test</h1>" > /var/www/mysite/test.html
chmod 600 /var/www/mysite/test.html
curl -I -H "Host: 你的域名" http://127.0.0.1/test.html # 403
ps -o user= -C nginx | sort -u # nginx 工作进程以 nginx 用户运行
sudo tail -3 /var/log/nginx/mysite_error.log # Permission denied
chmod 644 /var/www/mysite/test.html
curl -I -H "Host: 你的域名" http://127.0.0.1/test.html # 200 OK
rm /var/www/mysite/test.html
原因:读网页文件的是 Nginx 工作进程(nginx 用户),600 权限不允许"其他人"读取。排查思路:先看错误日志,再用 ls -l 核对权限——文件至少 644,目录至少 755。
sudo tail -f /var/log/nginx/mysite_access.log # 实时看访问记录,Ctrl+C 退出
每条记录含来访者 IP、时间、页面、状态码、浏览器。网站出问题第一件事就是看日志。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 域名打不开,IP 能打开 | 解析未生效或记录填错 | nslookup 查解析,等 TTL 后再试 |
| 连接超时 | 80 端口没开 | 检查安全组和 firewall-cmd --list-services |
| 403 Forbidden | 文件或目录权限不足 | 看错误日志,ls -l 核对(文件 644 / 目录 755) |
| 404 Not Found | 文件名或路径不对 | 确认 index.html 文件名和 root 路径 |
| 用域名仍是默认页 | server_name 写错或漏 www | 检查配置,nginx -t 后 reload |
| Nginx 启动/重载失败 | 配置语法错误 | sudo nginx -t 看行号,journalctl -u nginx |
| 环节 | 做了什么 | 出问题时的表现 |
|---|---|---|
| DNS | 添加 A 记录指向服务器 IP | 域名打不开,IP 能打开 |
| 开门 | 安全组 + firewalld 放行 80 | 连接超时 |
| Nginx | 安装、配置站点、发布主页 | 403、404 或默认页 |
目标:为个人主页申请免费 HTTPS 证书,配置自动续期,完成第一个完整作品——带 HTTPS 安全锁的个人主页。
HTTP 全部内容明文传输,中间每一台网络设备都能看到。亲手"偷看"自己网站的流量:
sudo dnf install -y tcpdump
sudo tcpdump -i any -A -nn 'tcp port 80' | grep -a -E "GET |Host:|User-Agent|<title>"
用手机访问 http 站点后,能看到请求了哪个页面(GET /)、哪个域名(Host:)、什么浏览器(User-Agent),甚至网页标题——表单里的账号密码也一样看得一清二楚。
HTTPS = HTTP + TLS 加密,解决两个问题:
身份认证靠数字证书(网站的"身份证"),由受信任的证书颁发机构(CA)签发。本章用 Let's Encrypt——免费、全自动的公益 CA。
http://域名/.well-known/acme-challenge/;由此推出申请证书的三个前提(也是失败的三个排查方向):①域名正确解析到服务器;②80 端口对外开放;③Nginx 能把验证文件发出去(server_name 和 root 正确)。
0.0.0.0/0sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-services # 应包含 ssh、http、https
sudo dnf install -y certbot # EPEL 源(第 1 讲已装)
certbot --version
Let's Encrypt 对失败申请有次数限制(同域名一小时失败太多次会被暂时拒绝),所以先用测试环境演练,不占正式次数。和"改配置先 -t 测试"是同一个习惯:先演练,再正式。
sudo certbot certonly --webroot -w /var/www/mysite \
-d example.com -d www.example.com \
-m 你的邮箱 --agree-tos --no-eff-email --dry-run
看到 The dry run was successful. 后,去掉 --dry-run 正式申请,看到 Successfully received certificate. 即签发成功。
sudo certbot certificates # 域名、到期时间、剩余天数、文件位置
sudo ls -l /etc/letsencrypt/live/example.com/
| 文件 | 内容 | 用途 |
|---|---|---|
| fullchain.pem | 你的证书 + 中间证书 | 发给访问者,证明网站身份 |
| privkey.pem | 证书私钥 | 留在服务器用于解密,绝不能泄露 |
| cert.pem / chain.pem | 单独的证书/中间证书 | 本章不直接使用 |
privkey.pem 与第 1 讲的 SSH 私钥同理:谁拿到它谁就能冒充你的网站。
sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \
-noout -issuer -dates -ext subjectAltName
# 核对:到期时间约 90 天后;两个域名都在列表中
sudo cp /etc/nginx/conf.d/mysite.conf /etc/nginx/conf.d/mysite.conf.bak
# 注意备份文件名不能以 .conf 结尾,否则会被 Nginx 一并加载
sudo vi /etc/nginx/conf.d/mysite.conf
# ① 80 端口:只做两件事——供证书续期验证;其余请求跳转 HTTPS
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /var/www/mysite;
}
location / {
return 301 https://$host$request_uri;
}
}
# ② 443 端口:真正提供网站内容
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3; # 只允许较新的安全协议
root /var/www/mysite;
index index.html;
access_log /var/log/nginx/mysite_access.log;
error_log /var/log/nginx/mysite_error.log;
location / {
try_files $uri $uri/ =404;
}
}
$host 和 $request_uri 是 Nginx 变量(访问域名和路径),保证 http://www.example.com/about.html 准确跳到对应的 https 地址,而不是一律跳回首页。
sudo nginx -t
sudo systemctl reload nginx
若 nginx -t 报 cannot load certificate,多半是证书路径中域名写错了。
curl.exe -I http://example.com # 应 301,Location 指向 https
curl.exe -I https://example.com # 应 200 OK
curl.exe -I https://www.example.com
浏览器访问 http://域名 会自动变为 https:// 并出现锁图标。PowerShell 里要写 curl.exe,因为 curl 在 PowerShell 中是另一个命令的别名。
sudo systemctl enable --now certbot-renew.timer # 每天检查,到期前约 30 天自动续期
systemctl list-timers 'certbot*' # NEXT 列是下次检查时间
续期只更新磁盘上的证书文件,Nginx 不会自动读取,须重载生效——用 certbot 的 deploy 钩子:
sudo vi /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
# 写入两行:
#!/bin/bash
systemctl reload nginx
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
sudo /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh # 手动跑一次确认没问题
systemctl is-active nginx
sudo certbot renew --dry-run # 演练续期,成功即可
sudo tcpdump -i any -A -nn -c 40 'tcp port 443' # 抓够 40 个包自动停止
对比 HTTP:这次看不到 GET、User-Agent 和网页标题,全是乱码——内容已加密。但仍能看到你的域名:浏览器建立加密连接前要先告知访问哪个网站(服务器才能拿出对应证书),这一步是明文的。结论:HTTPS 隐藏的是你看了什么内容,而不是你访问了哪个网站。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| DNS problem: NXDOMAIN | 域名或 www 子域名没有解析记录 | nslookup 核对,补 A 记录 |
| Timeout during connect | 80 端口不通 | 检查安全组和 firewall-cmd |
| unauthorized 且 404 | 验证文件没被访问到 | 核对 -w 与 root 一致、server_name 包含该域名 |
| too many failed authorizations | 一小时内失败过多 | 等一小时;先 --dry-run 演练再正式申请 |
| cannot load certificate | 证书路径写错 | 对照 certbot certificates 的路径修改 |
| https 访问超时 | 443 端口没开 | 检查安全组和 firewall-cmd |
| 访问 http 没跳转 | 配置未生效或浏览器缓存 | nginx -t 后 reload;curl.exe -I 直接看响应 |
| 锁图标带警告 | 页面引用了 http:// 资源 | 资源地址改为 https:// 或相对路径 |
| 改坏配置想恢复 | — | cp 备份文件回去,nginx -t 后 reload |
| 步骤 | 做了什么 | 关键点 |
|---|---|---|
| 开门 | 放行 443 端口 | 80 端口不能关:跳转和续期都要用 |
| 申请证书 | certbot 以 webroot 方式申请 | 先 --dry-run 演练,再正式申请 |
| 配置 HTTPS | 80 负责跳转,443 提供内容 | 证书私钥 privkey.pem 绝不外泄 |
| 自动续期 | 定时器 + deploy 钩子 | 90 天有效期,靠自动化不靠人记 |