《服务器运维》模块一 · Rocky Linux 9 / 阿里云 ECS 听课笔记
目标:把一台"裸奔"的阿里云 ECS(Rocky Linux 9.8,2核2G)加固成只认密钥、守得住、还会自动反击的服务器。本章不涉及修改 SSH 端口。
服务器接入公网后会被自动化程序昼夜扫描,发现 22 端口开放就用海量用户名/密码组合尝试登录(暴力破解)。先调证据看看:
# journalctl -u sshd:查看 SSH 服务的系统日志 # grep -i "failed":筛出登录失败记录(-i 忽略大小写);tail -30:只看最后 30 条 sudo journalctl -u sshd | grep -i "failed" | tail -30
大概率能看到大量陌生 IP 的失败记录,绝大多数在尝试 root —— 这就是攻击者。
| 通道 | 路径 | 特点 |
|---|---|---|
| SSH(含 Workbench) | 走公网 → 安全组 22 端口 → sshd | 改崩 SSH 配置或封了 22 端口就断,不是后路 |
| VNC | 阿里云带外管理通道,模拟物理显示器 | 不经过安全组也不经过 sshd,改坏任何东西都能进去自救 |
tty 显示 /dev/tty1(本地控制台),SSH 登录显示 /dev/pts/0(网络伪终端),正好印证两条通道独立。sudo adduser study # 新建普通用户 sudo passwd study # 设置登录密码 sudo usermod -aG wheel study # 加入 wheel 组,才能用 sudo
控制台 → ECS 实例 → 远程连接 → VNC 登录,验证 whoami 和 tty。密码不显示字符是正常现象。忘记密码可在控制台"重置实例密码"。
# -t ed25519:现代、短小而安全的算法;-C:备注方便辨认
ssh-keygen -t ed25519 -C "你的名字-服务器加固"
查看公钥(.pub 结尾):type $env:USERPROFILE\.ssh\id_ed25519.pub(Windows)或 cat ~/.ssh/id_ed25519.pub(Mac/Linux)。
sudo su - study # 切换到 study 用户 mkdir -p ~/.ssh # 创建 .ssh 目录 chmod 700 ~/.ssh # 目录 700:仅本人可访问 vi ~/.ssh/authorized_keys # 粘贴公钥(一整行):i 粘贴 → Esc → :wq chmod 600 ~/.ssh/authorized_keys # 文件 600:仅本人可读写 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 # sudo 建的目录属主要改回 teacher # teacher 没有密码、进不了 wheel 组的 sudo,所以单独配免密规则(巡检/自动化账号的通行做法): echo "teacher ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/teacher sudo chmod 440 /etc/sudoers.d/teacher # sudoers 规则文件要求 440 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 # 不加 --permanent 重启就失效! sudo firewall-cmd --list-all # 核对结果
ICMP 是网络层诊断协议(ping、traceroute),本身不使用端口,控制台里端口显示 -1/"任何"只是占位符,不代表打开所有端口。放行 ICMP 只能让别人 ping 通你,不能登录、不能传数据。保留它还有好处:SSH 连不上时先 ping 一下,就能区分是网络问题还是 SSH 配置问题。
sudo ss -tlnp # -t TCP -l 监听中 -n 数字显示 -p 显示进程,与安全组对照
sudo dnf install -y epel-release # Rocky 默认源没有 fail2ban,先装 EPEL
sudo dnf install -y fail2ban
规范做法:在 jail.d/ 下写 .local 文件,不直接改 jail.conf(升级不被覆盖)。Rocky 9 必须写 backend = systemd(日志由 systemd 管理,读 /var/log/secure 的旧教程写法在这里不生效):
sudo vi /etc/fail2ban/jail.d/sshd.local
[sshd] enabled = true backend = systemd # Rocky 9 的正确方式 maxretry = 3 # 10 分钟内失败 3 次 findtime = 600 # 统计窗口 600 秒 bantime = 3600 # 封禁 1 小时
sudo systemctl enable --now fail2ban # 启动并设开机自启 sudo fail2ban-client status sshd # 看 Banned IP list
sshd -t 验证后 reload;.ssh 是 700、authorized_keys 是 600、属主是本人;/etc/sudoers.d/teacher;--permanent 或 --reload。| 攻击者想做的事 | 你部署的加固 |
|---|---|
| 猜密码登进来 | SSH 密钥登录 + 禁用密码 + 禁止 root |
| 从别的端口钻进来 | firewalld + 安全组,两层最小化放行 |
| 反复爆破 SSH | fail2ban 自动封禁 |
| 进来了不被发现 | journal 日志审计 |
目标:为已备案域名配置解析,安装 Nginx,发布带备案号的个人主页。从本章起统一用自己的 SSH 客户端登录(Workbench 需上传私钥不推荐;VNC 留作救急)。
echo "teacher ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/teacher 等命令)。三个字段:记录类型 A 记录 = 域名指向 IPv4 地址;主机记录 @ = 域名本身,www = www 子域名;TTL = 各地 DNS 缓存时长(默认 10 分钟)。
在云解析 DNS 里添加两条 A 记录:@ 和 www,记录值都是服务器公网 IP。
nslookup 你的域名 # 验证解析,Address 应为服务器公网 IP
nslookup www.你的域名
安全组:入方向添加规则,协议 TCP、端口 80、授权对象 0.0.0.0/0(任何来源)。
sudo firewall-cmd --permanent --add-service=http # 放行 HTTP(TCP 80) sudo firewall-cmd --reload sudo firewall-cmd --list-services # 应包含 ssh 和 http
sudo dnf install -y nginx # 安装 Nginx sudo systemctl enable --now nginx # 启动并设开机自启 systemctl status nginx # 应显示 active (running),按 q 退出 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 # 属主可读写,其他人(含 Nginx)可进入 getenforce # SELinux 状态;阿里云 Rocky 镜像默认 Disabled
sudo vi /etc/nginx/conf.d/mysite.conf
server {
listen 80;
server_name example.com www.example.com; # 靠 Host 请求头匹配站点,务必写对
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;
}
}
sudo nginx -t # 先测语法(同 sshd -t 的习惯) sudo systemctl reload nginx # reload 不中断正在进行的访问
上传方式二选一:
# 方式一:本地写好,scp 上传(用同一把密钥,不需要密码) scp index.html study@你的服务器公网IP:/var/www/mysite/ # 方式二:直接在服务器上编辑(目录已属 study,不需要 sudo) vi /var/www/mysite/index.html
用 IP 访问时请求的 Host 不是你的域名,匹配不上你的站点,被交给系统默认站点处理。可亲自验证:
curl -H "Host: 你的域名" http://127.0.0.1 # Host 是你的域名 → 返回你的主页 curl http://127.0.0.1 | head -5 # 不带域名 → 返回默认测试页
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 Forbidden ps -o user= -C nginx | sort -u # 查 Nginx 进程身份:root 主进程 + nginx 工作进程 sudo tail -3 /var/log/nginx/mysite_error.log # 日志里有 (13: Permission denied) chmod 644 /var/www/mysite/test.html # 放宽为 644 curl -I -H "Host: 你的域名" http://127.0.0.1/test.html # 200 OK rm /var/www/mysite/test.html
sudo tail -f /var/log/nginx/mysite_access.log # 实时查看访问记录,Ctrl+C 退出
日志记录来访 IP、时间、页面、状态码和浏览器。日志是运维最重要的信息来源,网站出问题第一件事就是看日志。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 域名打不开,IP 能打开 | 解析未生效或记录填错 | nslookup 查看;等 TTL 后再试 |
| 连接超时 | 80 端口没开 | 检查安全组 + firewall-cmd --list-services |
| 403 Forbidden | 文件/目录权限不足 | 先看错误日志;文件 644、目录 755 |
| 404 Not Found | 文件名或路径不对 | 确认 index.html 和 root 路径 |
| 域名访问仍是默认页 | server_name 写错或漏 www | 检查配置,nginx -t 后 reload |
| Nginx 启动/重载失败 | 配置语法错误 | nginx -t 看行号;journalctl -u nginx |
| 环节 | 本章做了什么 | 出问题时的表现 |
|---|---|---|
| DNS | 添加 A 记录,域名指向服务器 IP | 域名打不开,IP 能打开 |
| 开门 | 安全组 + firewalld 放行 80 端口 | 连接超时 |
| Nginx | 安装、配置站点、发布主页 | 403、404 或看到默认页 |
目标:为个人主页申请免费 HTTPS 证书,配置到期自动续期,完成第一个完整作品——带安全锁的 HTTPS 个人主页。前提:上一章 http://域名 和 http://www.域名 都能访问(申请证书缺一不可)。
HTTP 全部明文传输,中间的每一台网络设备都能看到你访问的页面和提交的内容:
sudo dnf install -y tcpdump # 安装抓包工具 # -i any 所有网卡 -A 文本显示 -nn 数字显示,只抓 80 端口流量 sudo tcpdump -i any -A -nn 'tcp port 80' | grep -a -E "GET |Host:|User-Agent|<title>"
别人用手机访问时,屏幕上能看到请求的页面(GET /)、域名(Host:)、浏览器(User-Agent)甚至网页标题。若有登录表单,账号密码也看得一清二楚。
证书由证书颁发机构(CA)签发;浏览器和操作系统内置受信任 CA 名单。本章用 Let's Encrypt(免费、全自动的公益 CA)。
http://域名/.well-known/acme-challenge/随机文件名 放一串随机内容;安全组:TCP 443,授权对象 0.0.0.0/0。主机防火墙:
sudo 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 讲装 fail2ban 时已装 EPEL)
certbot --version
--dry-run 走完整流程但用测试环境,不占正式次数——和前两章"先用 -t 测试"是同一个习惯。# certonly:只申请证书,不自动改 Nginx 配置(配置自己写才看得懂) # --webroot:用网站目录方式验证;-w 指定网站目录;-d 指定域名(带 www 和不带各一个) 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 正式申请: sudo certbot certonly --webroot -w /var/www/mysite \ -d example.com -d www.example.com \ -m 你的邮箱 --agree-tos --no-eff-email
看到 Successfully received certificate. 即签发成功。
sudo certbot certificates # 域名、到期时间、剩余天数、位置 sudo ls -l /etc/letsencrypt/live/example.com/ # 目录名取自第一个 -d 域名 sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \ -noout -issuer -dates -ext subjectAltName # 颁发者/有效期/域名列表
| 文件 | 内容 | 用途 |
|---|---|---|
| fullchain.pem | 你的证书 + 中间证书 | 发给访问者,证明网站身份 |
| privkey.pem | 证书私钥 | 留在服务器解密用,绝不能泄露(与 SSH 私钥同理:谁拿到谁就能冒充你的网站) |
| cert.pem | 仅你的证书 | 本章不直接使用 |
| chain.pem | 仅中间证书 | 本章不直接使用 |
# 先备份;备份文件名不能以 .conf 结尾,否则会被 Nginx 一并加载
sudo cp /etc/nginx/conf.d/mysite.conf /etc/nginx/conf.d/mysite.conf.bak
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; # 续期时 Let's Encrypt 仍通过 HTTP 读验证文件 } location / { return 301 https://$host$request_uri; # 其余请求永久跳转到同一地址的 HTTPS } } # ② 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; } }
cannot load certificate 多半是证书路径域名写错。sudo nginx -t sudo systemctl reload nginx
curl.exe -I http://example.com # 应返回 301,Location 指向 https:// curl.exe -I https://example.com # 应返回 200 OK curl.exe -I https://www.example.com # 同样 200 OK
curl.exe,因为 PowerShell 的 curl 是另一个命令的别名。浏览器访问 http://域名 应自动变 https:// 且地址栏有锁图标。certbot 自带 systemd 定时器 certbot-renew.timer,每天检查、到期前约 30 天自动续期:
sudo systemctl enable --now certbot-renew.timer # 启用定时器 systemctl list-timers 'certbot*' # NEXT 列是下次检查时间
续期只更新磁盘上的证书文件,Nginx 必须重载才生效——用 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 # 应显示 active sudo certbot renew --dry-run # 演练一次续期
Congratulations, all simulated renewals succeeded 即流程通畅。sudo tcpdump -i any -A -nn -c 40 'tcp port 443' # -c 40:抓 40 个包自动停
对比:看不到 GET、User-Agent、网页标题,全是乱码——内容已加密。但仍可能看到域名:建立加密连接前浏览器要先明文告诉服务器访问哪个网站,服务器才能拿出对应证书。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| certbot 报 DNS problem: NXDOMAIN | 域名或 www 没有解析记录 | nslookup 核对,补 A 记录 |
| certbot 报 Timeout during connect | 80 端口不通 | 检查安全组和 firewalld |
| certbot 报 unauthorized 且 404 | 验证文件没被访问到 | 核对 -w 与 root 一致、server_name 含该域名 |
| certbot 报 too many failed authorizations | 一小时内失败过多 | 等一小时;先 --dry-run 演练 |
| nginx -t 报 cannot load certificate | 证书路径写错 | 对照 certbot certificates 修改 |
| https 访问超时 | 443 端口没开 | 检查安全组和 firewalld |
| 访问 http 没有跳转 | 配置未生效或浏览器缓存 | nginx -t 后 reload;curl.exe -I 查看 |
| 锁图标带警告 | 页面引用了 http:// 资源 | 资源地址改为 https:// 或相对路径 |
| 配置改坏了想恢复 | — | 用 .bak 覆盖回去,nginx -t 后 reload |
| 步骤 | 做了什么 | 关键点 |
|---|---|---|
| 开门 | 放行 443 端口 | 80 端口不能关:跳转和续期都要用 |
| 申请证书 | certbot 以 webroot 方式申请 | 先 --dry-run 演练,再正式申请 |
| 配置 HTTPS | 80 负责跳转,443 提供内容 | 私钥 privkey.pem 绝不外泄 |
| 自动续期 | 定时器 + deploy 钩子 | 90 天有效期,靠自动化不靠人记 |