我的运维笔记

《服务器运维》模块一 · Rocky Linux 9 / 阿里云 ECS · 整理:听课笔记

第 1 讲服务器安全加固:让服务器学会自我防护

目标:在阿里云 ECS(Rocky Linux 9.8,2核2G)上,把一台"裸奔"的服务器加固成只认密钥、守得住、还会自动反击的服务器(不涉及修改 SSH 端口)。

贯穿全章的三个习惯:私钥永不外传、改配置先留后路、先验证再收口。

1. 为什么要加固

服务器接入公网的那一刻就会被自动化扫描程序盯上:发现 22 端口(SSH 默认端口)开放后,会用海量用户名/密码组合反复尝试登录,即暴力破解。用日志可以直接看到证据:

sudo journalctl -u sshd | grep -i "failed" | tail -30
# journalctl -u sshd   查看 SSH 服务(sshd)的系统日志
# grep -i "failed"     筛出登录失败记录(-i 忽略大小写)
# tail -30             只显示最后 30 条

大量来自陌生 IP 的失败记录、绝大多数在尝试 root——这就是正在试探服务器的攻击者。

2. 登录服务器的两条通道

验证:VNC 登录后 whoami 应显示用户名;tty 显示 /dev/tty1(本地控制台),而 SSH 登录显示 /dev/pts/0(网络伪终端),印证两条通道独立。

3. 用密钥代替密码登录

3.1 原理

登录时服务器用公钥出一道只有对应私钥才能解开的"数学难题",攻击者拿到公钥也无从下手。

3.2 准备普通用户

sudo adduser study            # 新建普通用户 study
sudo passwd study             # 设置登录密码
sudo usermod -aG wheel study  # 加入 wheel 组,才能使用 sudo

3.3 生成密钥对(在自己电脑上)

ssh-keygen -t ed25519 -C "你的名字-服务器加固"
# -t ed25519   现代算法,短小而安全
# -C "..."     给密钥加备注
# 查看公钥:Windows 用 type $env:USERPROFILE\.ssh\id_ed25519.pub
#          Mac/Linux 用 cat ~/.ssh/id_ed25519.pub
# .pub 结尾的是公钥;不带 .pub 的是私钥,切勿外传

3.4 把公钥安装到服务器

公钥写入服务器账号的 ~/.ssh/authorized_keys。最容易踩的坑是文件权限:权限过松时 SSH 会直接拒绝使用它登录,且不报错。

sudo su - study
mkdir -p ~/.ssh          # 创建 .ssh 目录
chmod 700 ~/.ssh         # 目录权限 700:仅本人可进
vi ~/.ssh/authorized_keys  # 粘贴公钥(一整行)
chmod 600 ~/.ssh/authorized_keys  # 文件权限 600:SSH 强制要求
ls -l ~/.ssh             # 核对应显示 -rw------- 且属主是本人

3.5 铁律:先验证密钥能登录,再禁密码

ssh study@服务器公网IP   # 能免密登入说明配置成功,失败则绝对不要进行下一步

3.6 给老师配巡检账号

把私钥发给老师是绝对错误的做法——私钥一旦外传,任何人都能完全控制机器。正确思路:老师拥有自己的密钥对,我们只把老师的公钥装到服务器上。
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 必须输本人密码,因此单独配免密:
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 可用"

3.7 加固 sshd 配置

sudo vi /etc/ssh/sshd_config
配置项含义
PubkeyAuthentication yes允许密钥登录(加固基础)
PasswordAuthentication no禁止密码登录(务必先验证密钥可用)
PermitRootLogin no禁止 root 直接远程登录,缩小攻击面
MaxAuthTries 3单次连接最多 3 次认证尝试
ClientAliveInterval 300 / ClientAliveCountMax 25 分钟探测一次,连续 2 次无响应断开空闲会话
AllowUsers study teacher登录白名单,其余用户一律拒绝
sudo sshd -t             # 先测语法:无输出即正确
sudo systemctl reload sshd  # reload 不断开当前连接,比 restart 安全

4. 两道门:安全组与主机防火墙

两层相互独立都要配,原则是最小化:只开必须的门。

sudo firewall-cmd --permanent --add-service=ssh  # 永久放行 SSH
sudo firewall-cmd --reload                       # 重新加载生效
sudo firewall-cmd --list-all                     # 核对结果
sudo ss -tlnp   # 查看正在监听的端口:-t TCP -l 监听 -n 数字显示 -p 进程
Rocky 防火墙改规则必须加 --permanent 再 --reload,否则重启后失效。

为什么 ICMP 规则的"端口"显示"任何"?安全组按"协议+端口"两个维度控制:TCP/UDP 才有端口概念;ICMP 是网络层诊断协议(ping 用它),本身不使用端口,-1 只是占位符。放行 ICMP 只代表别人能 ping 通你,不能登录、不能传数据,不是安全漏洞。保留它还有好处:SSH 连不上时先 ping 一下,可区分是网络问题还是 SSH 配置问题。

5. fail2ban 自动封禁爆破

sudo dnf install -y epel-release  # Rocky 默认源没有 fail2ban,先装 EPEL
sudo dnf install -y fail2ban
sudo vi /etc/fail2ban/jail.d/sshd.local   # 规则写在 jail.d/*.local,不直接改 jail.conf
[sshd]
enabled = true
backend = systemd   # Rocky 9 日志由 systemd 统一管理,旧教程读 /var/log/secure 的写法不生效
maxretry = 3        # 最大失败次数
findtime = 600      # 统计窗口 10 分钟
bantime = 3600      # 封禁 1 小时

一句话:10 分钟内失败 3 次,就把这个 IP 封禁一小时。

sudo systemctl enable --now fail2ban   # 启动并设开机自启
sudo fail2ban-client status sshd       # 查看 Banned IP list(已封禁的攻击者 IP)

名单暂时为空不代表操作有误,只是还没被扫描器盯上。

6. 自救指南

问题处理
SSH 连不上VNC 登录(不走 SSH),改回 sshd_config,再 sshd -t 验证、reload 生效
密钥登录总要求输密码99% 是权限问题:.ssh 须 700、authorized_keys 须 600、属主须是本人
sudoers 写错、sudo 全挂VNC 以 root 登录修复后 visudo -c 检查
firewalld 改了不生效检查是否漏了 --permanent 或 --reload

本章小结

攻击者想做的事部署的加固
猜密码登进来SSH 密钥登录 + 禁用密码 + 禁止 root
从别的端口钻进来firewalld + 安全组,两层最小化放行
反复爆破 SSHfail2ban 自动封禁
进来了不被发现journal 日志审计

第 2 讲网站上线:域名解析与 Nginx 个人主页

目标:为已备案域名配置解析,安装 Nginx,发布带备案号的个人主页,让任何人输入域名都能访问。

从本章起统一用自己电脑的 SSH 客户端登录(上一章已禁用密码登录);VNC 留作救急后路。另外先补 teacher 的免密 sudo 规则(同第 1 讲 3.6 节)。

1. 从 IP 到域名

IP 难记,域名负责解决记忆问题;DNS(域名系统)是互联网的"通讯录",把域名翻译成 IP。网站上线需打通三个环节:

  1. DNS:让域名指向服务器 IP;
  2. 开门:安全组和防火墙放行 80 端口;
  3. Nginx:服务器上接收请求并返回网页。

2. 域名解析

解析要全网生效需要时间,所以第一步先做解析,让它在后台慢慢生效——"把需要等待的事情提前做"是常用运维思路。

添加两条 A 记录(@ 和 www,记录值都填公网 IP)后验证:

nslookup 你的域名
nslookup www.你的域名   # Address 一栏应显示服务器公网 IP

3. 为网站开门(80 端口)

"最小化"不是"什么都不开",而是服务上线时才开对应的门。本章只开 80,443 留到下一章。
sudo firewall-cmd --permanent --add-service=http  # 放行 HTTP(TCP 80)
sudo firewall-cmd --reload
sudo firewall-cmd --list-services                 # 应包含 ssh 和 http

4. 安装 Nginx 并配置站点

sudo dnf install -y nginx
sudo systemctl enable --now nginx   # 立即启动 + 开机自启
systemctl status nginx              # 应显示 active (running)
nginx -v

准备独立网站目录(不占用系统默认页面,以后加第二个站点互不干扰):

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,本地环境遇 403 记得查它

站点配置(Nginx 自动加载 /etc/nginx/conf.d/*.conf):

sudo vi /etc/nginx/conf.d/mysite.conf
server {
    listen 80;
    server_name example.com www.example.com;  # 响应哪些域名,带不带 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 靠请求头里的 Host 字段与 server_name 匹配来区分站点。下一章申请证书也靠这一行识别域名,务必写对。
sudo nginx -t              # 先测语法
sudo systemctl reload nginx  # reload 不中断正在进行的访问

5. 发布个人主页

硬性要求:首页底部必须显示 ICP 备案号,并链接到 https://beian.miit.gov.cn/(工信部备案查询网站),新窗口打开。AI 生成的主页也要自己检查:备案号是不是自己的、链接是否正确、页面能否显示。

# 方式一(推荐):本地写好用 scp 上传,用的是与 ssh 同一把密钥,不需密码
scp index.html study@你的服务器公网IP:/var/www/mysite/
# 方式二:直接在服务器上编辑
vi /var/www/mysite/index.html

为什么用 IP 访问看到的是默认页?用 IP 访问时请求头里的 Host 不是你的域名,匹配不上你的站点,于是交给系统默认站点。可以自己验证:

curl -H "Host: 你的域名" http://127.0.0.1   # 手动指定 Host → 返回你的主页
curl http://127.0.0.1 | head -5             # 不带 → 返回默认测试页

6. 排障实验:亲手制造一次 403

echo "<h1>test</h1>" > /var/www/mysite/test.html
chmod 600 /var/www/mysite/test.html                     # 只有 study 能读
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                      # 放宽后:200 OK

原理:真正读网页的是 Nginx 工作进程(身份是 nginx 用户),文件属 study 而 600 不允许"其他人"读取,所以 403。排查思路:先看错误日志找原因,再用 ls -l 核对权限——网页文件至少 644,目录至少 755。

7. 看见你的第一位访客

sudo tail -f /var/log/nginx/mysite_access.log   # 持续滚动显示访问记录,Ctrl+C 退出

每次刷新多一行:来访者 IP、时间、页面、状态码、浏览器。日志是运维最重要的信息来源——网站出问题第一件事就是看日志。

8. 排障速查表

现象可能原因排查方法
域名打不开、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 或看到默认页
合规提醒:除 ICP 备案外,网站开通后 30 日内还需在"全国互联网安全管理服务平台"办理公安联网备案,备案号同样显示在网站底部。

第 3 讲给网站加上安全锁:HTTPS 与证书自动续期

目标:为个人主页申请免费 HTTPS 证书,配置自动续期,得到第一个完整作品——带 HTTPS 安全锁的个人主页。前提:上一章的 http://域名 和 http://www.域名 都能正常访问。

1. 你的网站正在"裸奔"

HTTP 全部明文传输:中间经过的每一台网络设备都能原样看到你访问了什么页面、提交了什么内容。用 tcpdump 亲手"偷看"一次:

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>"

同学用手机访问 http://你的域名,屏幕上能看到请求的页面(GET /)、域名(Host)、浏览器(User-Agent)、网页标题(<title>),甚至登录表单的账号密码。

2. HTTPS 与证书的原理

HTTPS = HTTP + TLS 加密,解决两个问题:

  1. 加密:内容只有浏览器和服务器能解开;
  2. 身份认证:证明连接的是该域名的真实服务器,而非冒充者——靠数字证书(网站的"身份证")。

证书由证书颁发机构(CA)签发,浏览器只认受信任 CA 名单里的签发者。本章使用 Let's Encrypt:免费、全自动的公益 CA。

HTTP-01 验证流程(CA 怎么确认域名是你的)

  1. 服务器上运行 certbot 向 Let's Encrypt 申请证书;
  2. Let's Encrypt 要求把一串随机内容放到 http://你的域名/.well-known/acme-challenge/…;
  3. certbot 把验证文件写进网站目录 /var/www/mysite;
  4. Let's Encrypt 从互联网访问该地址,读到正确内容即签发证书。

由此推出申请证书的三个前提(也是失败的三个排查方向):域名正确解析到服务器、80 端口对外开放、Nginx 能把验证文件发出去(server_name 和 root 都要正确)。

证书有效期只有 90 天(2028 年 2 月前将逐步缩到 45 天),且 2025 年起已停止到期提醒邮件——自动续期是必须完成的步骤,不是可选项。

3. 开放 443 端口

sudo firewall-cmd --permanent --add-service=https  # 放行 HTTPS(TCP 443)
sudo firewall-cmd --reload
sudo firewall-cmd --list-services                  # 应包含 ssh、http、https
80 端口不能关:① 有人直接输 http:// 需要它引导跳转;② 证书每次续期 Let's Encrypt 仍走 80 端口验证,关了 80 三个月后证书就续不了期。

4. 申请证书(certbot)

sudo dnf install -y certbot   # 位于 EPEL 源(装 fail2ban 时已装过 EPEL)
certbot --version

先演练:同一域名一小时内验证失败太多次会被暂时拒绝,所以先 --dry-run 走测试环境(不真签发、不占正式次数)——与前两章"先用 -t 测试"是同一个习惯。

sudo certbot certonly --webroot -w /var/www/mysite \
  -d example.com -d www.example.com \
  -m 你的邮箱 --agree-tos --no-eff-email --dry-run
# certonly     只申请证书,不自动改 Nginx 配置(配置自己写才看得懂)
# --webroot    用网站目录方式验证
# -w           网站目录(Nginx 配置里的 root)
# -d           证书要包含的域名,带 www 与不带各写一个
# 行尾反斜杠 \ 表示命令换行继续,三行是一条命令

看到 The dry run was successful. 后去掉 --dry-run 正式申请,看到 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证书私钥留在服务器上用于解密,绝不能泄露
cert.pem / chain.pem仅你的证书 / 仅中间证书本章不直接使用

privkey.pem 与第 1 讲的 SSH 私钥是同一个道理:谁拿到它谁就能冒充你的网站。

5. 配置 Nginx 启用 HTTPS

# 备份;注意备份文件名不能以 .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;                # 续期验证文件仍走 HTTP
    }
    location / {
        return 301 https://$host$request_uri;  # 其余请求 301 跳转
    }
}
# ② 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
# 报 cannot load certificate 多半是证书路径里域名写错,对照 certbot certificates 核对

验证(PowerShell 里要写 curl.exe,因为 PowerShell 的 curl 是另一个命令的别名):

curl.exe -I http://example.com    # 应返回 301 Moved Permanently,Location 指向 https
curl.exe -I https://example.com   # 应返回 200 OK
curl.exe -I https://www.example.com  # 带 www 的同样 200 OK

6. 配置自动续期

sudo systemctl enable --now certbot-renew.timer  # 每天定时检查,到期前约 30 天自动续期
systemctl list-timers 'certbot*'                 # NEXT 列 = 下次检查时间

续期只更新磁盘上的证书文件,Nginx 不会自动读取,必须重载。用 deploy 钩子:放在 /etc/letsencrypt/renewal-hooks/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   # 演练续期;演练会提示跳过 deploy 钩子,属正常

7. 再抓一次包:HTTPS 的流量长什么样

sudo tcpdump -i any -A -nn -c 40 'tcp port 443'   # -c 40:抓够 40 个包自动停

这次看不到 GET、User-Agent、网页标题,全是乱码——内容已加密。但仔细看乱码里仍能找到你的域名:浏览器建立加密连接前要先明文告诉服务器"我要访问哪个网站",服务器才能拿出对应证书。所以:HTTPS 隐藏的是你看了什么内容,而不是你访问了哪个网站。

8. 排障速查表

现象可能原因排查方法
certbot 报 DNS problem: NXDOMAIN域名或 www 子域没解析nslookup 补上缺失的 A 记录
certbot 报 Timeout during connect80 端口不通检查安全组和 firewall-cmd --list-services
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:// 或相对路径

本章小结

步骤做了什么关键点
开门放行 443 端口80 端口不能关:跳转和续期都要用到
申请证书certbot 以 webroot 方式申请先 --dry-run 演练,再正式申请
配置 HTTPS80 负责跳转,443 提供内容证书私钥 privkey.pem 绝不外泄
自动续期定时器 + deploy 钩子90 天有效期,靠自动化而不是靠人记

至此个人主页拥有域名、备案号和 HTTPS 安全锁,是一个完整、规范的网站了。