目标:在阿里云 ECS(Rocky Linux 9.8,2核2G)上,把一台"裸奔"的服务器加固成只认密钥、守得住、还会自动反击的服务器(不涉及修改 SSH 端口)。
服务器接入公网的那一刻就会被自动化扫描程序盯上:发现 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——这就是正在试探服务器的攻击者。
验证:VNC 登录后 whoami 应显示用户名;tty 显示 /dev/tty1(本地控制台),而 SSH 登录显示 /dev/pts/0(网络伪终端),印证两条通道独立。
登录时服务器用公钥出一道只有对应私钥才能解开的"数学难题",攻击者拿到公钥也无从下手。
sudo adduser study # 新建普通用户 study sudo passwd study # 设置登录密码 sudo usermod -aG wheel study # 加入 wheel 组,才能使用 sudo
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 的是私钥,切勿外传
公钥写入服务器账号的 ~/.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------- 且属主是本人
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 必须输本人密码,因此单独配免密: 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 / ClientAliveCountMax 2 | 5 分钟探测一次,连续 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 # 核对结果 sudo ss -tlnp # 查看正在监听的端口:-t TCP -l 监听 -n 数字显示 -p 进程
--permanent 再 --reload,否则重启后失效。为什么 ICMP 规则的"端口"显示"任何"?安全组按"协议+端口"两个维度控制:TCP/UDP 才有端口概念;ICMP 是网络层诊断协议(ping 用它),本身不使用端口,-1 只是占位符。放行 ICMP 只代表别人能 ping 通你,不能登录、不能传数据,不是安全漏洞。保留它还有好处:SSH 连不上时先 ping 一下,可区分是网络问题还是 SSH 配置问题。
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)
名单暂时为空不代表操作有误,只是还没被扫描器盯上。
| 问题 | 处理 |
|---|---|
| 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 + 安全组,两层最小化放行 |
| 反复爆破 SSH | fail2ban 自动封禁 |
| 进来了不被发现 | journal 日志审计 |
目标:为已备案域名配置解析,安装 Nginx,发布带备案号的个人主页,让任何人输入域名都能访问。
IP 难记,域名负责解决记忆问题;DNS(域名系统)是互联网的"通讯录",把域名翻译成 IP。网站上线需打通三个环节:
解析要全网生效需要时间,所以第一步先做解析,让它在后台慢慢生效——"把需要等待的事情提前做"是常用运维思路。
@ 代表域名本身,www 代表 www.域名;添加两条 A 记录(@ 和 www,记录值都填公网 IP)后验证:
nslookup 你的域名 nslookup www.你的域名 # Address 一栏应显示服务器公网 IP
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 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;
}
}
Host 字段与 server_name 匹配来区分站点。下一章申请证书也靠这一行识别域名,务必写对。sudo nginx -t # 先测语法 sudo systemctl reload nginx # reload 不中断正在进行的访问
硬性要求:首页底部必须显示 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 # 不带 → 返回默认测试页
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。
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 全部明文传输:中间经过的每一台网络设备都能原样看到你访问了什么页面、提交了什么内容。用 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>),甚至登录表单的账号密码。
HTTPS = HTTP + TLS 加密,解决两个问题:
证书由证书颁发机构(CA)签发,浏览器只认受信任 CA 名单里的签发者。本章使用 Let's Encrypt:免费、全自动的公益 CA。
http://你的域名/.well-known/acme-challenge/…;由此推出申请证书的三个前提(也是失败的三个排查方向):域名正确解析到服务器、80 端口对外开放、Nginx 能把验证文件发出去(server_name 和 root 都要正确)。
sudo firewall-cmd --permanent --add-service=https # 放行 HTTPS(TCP 443) sudo firewall-cmd --reload sudo firewall-cmd --list-services # 应包含 ssh、http、https
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 私钥是同一个道理:谁拿到它谁就能冒充你的网站。
# 备份;注意备份文件名不能以 .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
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 钩子,属正常
sudo tcpdump -i any -A -nn -c 40 'tcp port 443' # -c 40:抓够 40 个包自动停
这次看不到 GET、User-Agent、网页标题,全是乱码——内容已加密。但仔细看乱码里仍能找到你的域名:浏览器建立加密连接前要先明文告诉服务器"我要访问哪个网站",服务器才能拿出对应证书。所以:HTTPS 隐藏的是你看了什么内容,而不是你访问了哪个网站。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| certbot 报 DNS problem: NXDOMAIN | 域名或 www 子域没解析 | nslookup 补上缺失的 A 记录 |
| certbot 报 Timeout during connect | 80 端口不通 | 检查安全组和 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 演练,再正式申请 |
| 配置 HTTPS | 80 负责跳转,443 提供内容 | 证书私钥 privkey.pem 绝不外泄 |
| 自动续期 | 定时器 + deploy 钩子 | 90 天有效期,靠自动化而不是靠人记 |
至此个人主页拥有域名、备案号和 HTTPS 安全锁,是一个完整、规范的网站了。