《服务器运维》课程笔记 · 阿里云 ECS · Rocky Linux 9.8 · 2核2G
服务器刚买来接入公网,就开始被扫描器暴力猜密码。我先看了登录日志确认确实有人在爆破,然后沿着"攻击者会从哪里进来"这条线索,一步步把门堵死:新建普通用户、配置 SSH 密钥登录、禁用密码登录和 root 登录、配置安全组和 firewalld 两层防火墙、装 fail2ban 自动封禁爆破 IP,还给老师配了一个巡检账号。
先看证据——查 SSH 登录失败记录:
sudo journalctl -u sshd | grep -i "failed" | tail -30
journalctl -u sshd 查看 sshd 服务的系统日志,grep -i "failed" 筛出登录失败行(-i 忽略大小写),tail -30 只看最后 30 条。
新建日常用的普通用户并加入 wheel 组(wheel 组成员才能用 sudo):
sudo adduser study sudo passwd study sudo usermod -aG wheel study
在自己电脑上生成密钥对(私钥留在本机,公钥放到服务器):
ssh-keygen -t ed25519 -C "我的名字-服务器加固"
把公钥装到服务器上,关键是权限:
mkdir -p ~/.ssh chmod 700 ~/.ssh vi ~/.ssh/authorized_keys # 粘贴公钥(一整行) chmod 600 ~/.ssh/authorized_keys ls -l ~/.ssh # 核对:-rw-------,属主是 study
验证密钥能登录后,加固 sshd 配置 /etc/ssh/sshd_config:
PubkeyAuthentication yes # 允许密钥登录 PasswordAuthentication no # 禁止密码登录 PermitRootLogin no # 禁止 root 远程登录 MaxAuthTries 3 # 单次连接最多 3 次认证尝试 AllowUsers study teacher # 登录白名单 sudo sshd -t # 先测语法,没输出才继续 sudo systemctl reload sshd # reload 不会断开当前连接
主机防火墙只放行 SSH(Rocky 上必须带 --permanent,否则重启就丢):
sudo firewall-cmd --permanent --add-service=ssh sudo firewall-cmd --reload sudo firewall-cmd --list-all sudo ss -tlnp # 看机器实际监听了哪些端口
安装并配置 fail2ban(10 分钟内失败 3 次就封 1 小时):
sudo dnf install -y epel-release sudo dnf install -y fail2ban sudo vi /etc/fail2ban/jail.d/sshd.local # [sshd] # enabled = true # backend = systemd # maxretry = 3 # findtime = 600 # bantime = 3600 sudo systemctl enable --now fail2ban sudo fail2ban-client status sshd # 看 Banned IP list
~/.ssh 是 700、authorized_keys 是 600 且属主是本人,权限过松会直接拒绝使用,而且不报错。改对权限后立刻成功。backend = systemd,网上旧教程读 /var/log/secure 的写法在 Rocky 9 上不生效。/etc/sudoers.d/teacher,改完必须 sudo visudo -c 检查语法,防止所有账号的 sudo 失效。这一讲把网站真正发布出去,打通三个环节:① 域名解析(DNS)——让域名指向服务器 IP;② 开门——安全组和防火墙放行 80 端口;③ Nginx——安装、写站点配置、发布带备案号的个人主页,还做了一次 403 排障实验。
在阿里云云解析里添加两条 A 记录(主机记录分别为 @ 和 www,记录值填服务器公网 IP,TTL 默认 10 分钟),然后验证解析是否生效:
nslookup 我的域名 nslookup www.我的域名
为网页开门(两层门都要开):
# 安全组:入方向添加规则 TCP 80,授权对象 0.0.0.0/0 sudo firewall-cmd --permanent --add-service=http sudo firewall-cmd --reload sudo firewall-cmd --list-services
安装并启动 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
sudo chmod 755 /var/www/mysite
sudo vi /etc/nginx/conf.d/mysite.conf
# server {
# listen 80;
# server_name example.com www.example.com;
# 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 # 先测语法
sudo systemctl reload nginx # 再生效
上传主页并验证 server_name 的作用:
scp index.html study@服务器公网IP:/var/www/mysite/ curl -H "Host: 我的域名" http://127.0.0.1 # 带 Host 头 → 返回我的主页 curl http://127.0.0.1 | head -5 # 不带 → 返回 Nginx 默认页
做 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 Forbidden sudo tail -3 /var/log/nginx/mysite_error.log # (13: Permission denied) chmod 644 /var/www/mysite/test.html curl -I -H "Host: 我的域名" http://127.0.0.1/test.html # 200 OK
看第一位访客:
sudo tail -f /var/log/nginx/mysite_access.log # Ctrl+C 退出
server_name 在起作用——Nginx 靠请求头里的 Host 字段区分同一台服务器上的多个站点,用 IP 访问时 Host 匹配不上我的站点,就被交给默认站点处理。ls -l 核对权限——文件至少 644,目录至少 755。index.html、root 路径写对没有。先用 tcpdump 亲手"偷看"了一次自己网站的 HTTP 明文流量,证实了网站在"裸奔";然后给网站申请 Let's Encrypt 免费证书,改写 Nginx 配置启用 HTTPS(80 只负责跳转,443 提供内容),最后配置了证书自动续期。再抓一次包对比,流量已经全是密文了。
抓包看 HTTP 明文的裸奔现场:
sudo dnf install -y tcpdump sudo tcpdump -i any -A -nn 'tcp port 80' | grep -a -E "GET |Host:|User-Agent|<title>"
-i any 所有网卡抓包,-A 以文本显示内容,-nn 数字显示 IP 和端口。
开放 443 端口(安全组加 TCP 443 规则 + 防火墙,80 端口不能关:跳转和证书续期都要用):
sudo firewall-cmd --permanent --add-service=https sudo firewall-cmd --reload sudo firewall-cmd --list-services
安装 certbot 并先演练再正式申请(--webroot 方式,-w 是网站目录,-d 是域名):
sudo dnf install -y certbot sudo certbot certonly --webroot -w /var/www/mysite \ -d example.com -d www.example.com \ -m 我的邮箱 --agree-tos --no-eff-email --dry-run # 先演练 sudo certbot certonly --webroot -w /var/www/mysite \ -d example.com -d www.example.com \ -m 我的邮箱 --agree-tos --no-eff-email # 去掉 --dry-run 正式申请 sudo certbot certificates # 查看证书:域名、到期时间、文件位置 sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \ -noout -issuer -dates -ext subjectAltName
改写 Nginx 配置启用 HTTPS(80 跳转,443 提供内容):
sudo cp /etc/nginx/conf.d/mysite.conf /etc/nginx/conf.d/mysite.conf.bak # 先备份
# ① 80 端口:提供续期验证文件 + 其余请求 301 跳转到 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;
# }
sudo nginx -t
sudo systemctl reload nginx
curl.exe -I http://example.com # 应返回 301
curl.exe -I https://example.com # 应返回 200
配置自动续期(证书只有 90 天有效期,必须靠自动化):
sudo systemctl enable --now certbot-renew.timer # 启用续期定时器 systemctl list-timers 'certbot*' # 续期后自动重载 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 certbot renew --dry-run # 演练一次完整续期流程
再抓一次包对比 HTTPS:
sudo tcpdump -i any -A -nn -c 40 'tcp port 443'
--dry-run 在测试环境演练,成功后才正式申请,不浪费正式次数。sudo certbot certificates 显示的路径改正即可。这也是为什么改完配置必须先 nginx -t 再 reload。privkey.pem 和第 1 讲的 SSH 私钥是同一个道理——谁拿到它谁就能冒充我的网站,绝不外传,目录只有 root 能访问。