我的运维笔记

服务器运维 · 模块一 · 听课笔记

我的运维笔记

三讲课,一台服务器:从"裸奔"的阿里云 ECS,到只认密钥的加固主机,再到能被人访问、带安全锁的个人主页。 这里保留课件里的讲法、完整命令和最容易踩的坑,命令块可直接复制。

环境 Rocky Linux 9.8 规格 2 核 2G · 阿里云 ECS 共 3 讲 主线 从 IP 到 HTTPS

第 1 讲 Lecture 01 · Hardening

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

在自己的阿里云 ECS 上,把一台"谁都能来猜密码"的服务器,加固成"只认密钥、守得住、还会自动反击"的服务器。 本讲不修改 SSH 端口。

01说清 SSH 密钥认证的原理,配置密钥登录并安全地禁用密码登录
02理解"安全组 + 主机防火墙"的两层防护,读懂安全组规则
03用 fail2ban 让服务器自动封禁暴力破解者
04在自己被锁在门外时,用 VNC 完成自救

贯穿全章的三个职业习惯:私钥永不外传 · 改配置先留后路 · 先验证再收口。后面每一个操作,都是在练习这三句话。

1.1为什么要加固:你的服务器正在被扫描

服务器接入公网的那一刻,就不再是无人问津的角落。互联网上大量自动化扫描程序昼夜遍历 IP,一旦发现 22 端口(SSH 默认端口)开着,就用海量常见用户名和密码反复尝试登录——这就是暴力破解。

不必凭空想象,先把证据调出来。登录服务器执行:

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

你多半会看到大量来自陌生 IP 的登录失败记录,而且绝大多数在尝试 root。这些就是正在试探你服务器的攻击者。本章的主线,就是沿着"攻击者会从哪里进来"这条线索,一处处把门堵死。

1.2先搞清楚:登录服务器的两条通道

动手之前必须建立一个关键认知:登录云服务器有两条机制完全不同的通道。理解它,你才敢放手改配置——因为你知道万一改坏了,还有一条路能进去补救。

通道走的是什么受谁管辖配置改崩之后
SSH(含控制台 Workbench) 公网 → 安全组 22 端口 → 服务器 sshd 安全组规则 + sshd 配置 一起断开,不是后路
VNC 阿里云带外管理通道,相当于坐在物理显示器前 系统账号与密码 依然能进,能救命

真正能救命的后路是 VNC。它既不经过安全组,也不经过 sshd,哪怕你禁用了密码登录、封了 22 端口,照样能登录系统。这也是我们敢于大胆加固的底气。

两条通道的差异,可以从系统内部印证。在 VNC 里执行:

bash
whoami   # 显示当前登录的用户名,应为 study
tty      # VNC 登录显示 /dev/tty1;SSH 登录显示 /dev/pts/0 之类的伪终端

/dev/tty1 是机器本地的控制台终端,相当于直接插在服务器上的显示器和键盘;/dev/pts/0 则代表一条网络连接。两者不同,正好证明了"这是两条独立的通道"。

1.3用密钥代替密码登录

原理:一对相互配对的钥匙

密码有两个天生的弱点:可能被猜中,也可能被泄露。密钥认证换了一套思路,生成一对钥匙:

  • 私钥:留在自己电脑上,绝不外传,是身份的唯一凭证。
  • 公钥:放到服务器上,可以公开,谁都能看。

登录时,服务器用你的公钥出一道只有对应私钥才能解开的"数学难题"。持有私钥的人轻松解开、顺利登录;没有私钥的攻击者,即使把公钥拿到手也无从下手。

准备一个普通用户

加固完成后会禁止 root 直接远程登录,所以先准备一个日常账号(把 study 换成你自己的用户名,后续所有 study 一并替换):

bash
sudo adduser study              # 新建普通用户 study
sudo passwd study              # 设置登录密码(按提示输入两次)
sudo usermod -aG wheel study    # 加入 wheel 组;只有 wheel 组成员才能用 sudo

先走一遍 VNC,确认后路可用

后路只有亲自走通过一次才靠得住。等到真出事才第一次打开 VNC,很可能因为找不到入口、记不住密码而手忙脚乱。阿里云控制台里:ECS → 实例 → 远程连接 → VNC 远程连接,出现黑色 login 提示后输入用户名和密码(输密码时屏幕不显示任何字符,属正常现象)。

VNC 的粘贴支持较差,长命令建议手动输入;忘了系统密码可在控制台 重置实例密码,重启后生效。VNC 适合救急,不适合日常操作。

在自己的电脑上生成密钥对

密钥必须在自己电脑上生成,这样私钥从诞生起就只存在于你的机器里,从不经过网络。Windows 用 PowerShell,Mac / Linux 用终端:

bash
# -t ed25519 指定算法(现代、短小而安全);-C 加备注,方便日后辨认
ssh-keygen -t ed25519 -C "你的名字-服务器加固"    # 一路回车,可不设密码短语

# 查看公钥内容,复制整行(以 ssh-ed25519 AAAA... 开头,中间不能换行)
type $env:USERPROFILE\.ssh\id_ed25519.pub    # Windows PowerShell
cat ~/.ssh/id_ed25519.pub                   # macOS / Linux

把公钥安装到服务器上

"安装公钥"就是把公钥写进该账号的 ~/.ssh/authorized_keys(已授权的公钥清单)。这里有一个最容易出错、又最难排查的陷阱——文件权限:权限过松时 SSH 会直接拒绝使用它登录,而且不给任何报错。

bash
sudo su - study                    # 切到 study(- 加载它的环境,~ 即 /home/study)
mkdir -p ~/.ssh                   # 创建 .ssh 目录;-p 表示已存在也不报错
chmod 700 ~/.ssh                  # 700:仅本人可读写进入
vi ~/.ssh/authorized_keys         # i 进入编辑 → 粘贴公钥(一整行)→ Esc → :wq 保存

chmod 600 ~/.ssh/authorized_keys   # 600:仅本人可读写;SSH 强制要求,过松会拒绝登录
ls -l ~/.ssh                      # 核对:应显示 -rw-------,属主是你本人

一条铁律:必须先确认新方式(密钥)确实可用,才能禁用旧方式(密码)。顺序一旦反了,就会出现"密码已禁、密钥没好"、把自己关在门外的尴尬。尽管有 VNC 兜底,正确的习惯永远是:先验证,再收口。

bash
# 公钥已装好,SSH 会自动用本机私钥认证,正常情况下不需要输入密码
ssh study@服务器公网IP

能直接登入且不要求密码,说明密钥配置成功。失败就回控制台查三件事:公钥有没有贴错、权限对不对、用户名对不对。验证成功之前,绝对不要进行下一步。

为老师配置一个巡检账号

很多同学的第一反应是"把我的私钥发给老师"——这是绝对错误的。私钥是身份的唯一凭证,发出去,任何拿到它的人都能完全控制这台机器。正确思路恰恰相反:让老师拥有自己的一对钥匙,你只把老师的公钥装到服务器上。

bash
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 可用"

务必执行 visudo -c。sudo 配置一旦写错,所有账号的 sudo 都会失效,包括你自己的 study;万一发生,用 VNC 以 root 登录修复。另外:这里装的是老师的公钥,不是你的——你的私钥自始至终只留在自己电脑上。

收口:加固 sshd 配置

密钥验证通过后,修改 SSH 服务端主配置文件,把不安全的登录方式逐项关闭。

bash
sudo vi /etc/ssh/sshd_config
配置项作用防的是什么
PubkeyAuthentication yes允许密钥登录,加固的基础—(保持开启)
PasswordAuthentication no禁止密码登录被人猜中密码(确认密钥可用后再改)
PermitRootLogin no禁止 root 直接远程登录缩小攻击面,管理先登普通用户再 sudo
MaxAuthTries 3单次连接最多 3 次认证超过即断开,拖慢暴力破解
ClientAliveInterval 300每 300 秒探测一次客户端发现掉线
ClientAliveCountMax 2连续 2 次无响应即断开自动清理挂着的空闲会话
AllowUsers study teacher登录白名单其余用户一律拒绝(写错就进不来)
bash
sudo sshd -t                     # 测语法:无输出即正确,有错会给出具体行号
sudo systemctl reload sshd       # 重载生效;reload 不会断开当前连接

至此,服务器已经从"谁都能来猜密码",变成"只有持有私钥的白名单用户才能进入"。

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

云服务器的防护有两层门,这是它与普通物理机最大的不同。安全组是平台层防火墙,管"公网流量能不能进入实例";firewalld 是操作系统内部的防火墙,管"流量到达实例后放不放行到具体服务"。两层相互独立、都要配置,共同遵循一个词——最小化:只开必须的门。

bash
sudo firewall-cmd --permanent --add-service=ssh   # 永久放行 SSH:--permanent 写入永久规则
sudo firewall-cmd --reload                        # 重新加载,让永久规则真正生效
sudo firewall-cmd --list-all                      # 列出当前区域放行的服务/端口,核对结果

Rocky 上最容易被忽略的细节:改规则必须加 --permanent 再执行 --reload,否则规则重启后就失效了。

接着到控制台配置安全组(实例 → 安全组 → 配置规则,看入方向),把握三点:

  • 必须保留 SSH(TCP 22):本机 SSH 客户端和控制台 Workbench 都走它,删掉两条通道同时中断(只剩 VNC)。
  • 删除或收紧用不到的开放端口。
  • ICMP-IPv4 那条可以保留:它是让别人能 ping 通你、做连通性诊断用的。

为什么有的规则"端口"写着"任何"?

很多人疑惑:ICMP 规则端口栏写 -1 或"任何",是不是把所有端口都打开了?答案是否。安全组按协议 + 端口两个维度控制,并不是只看端口:

  • TCP / UDP 才有"端口"概念,所以 SSH 写作 TCP 22,HTTP 写作 TCP 80。放行 TCP 22,等于开了一扇能进来做事的门。
  • ICMP 是网络层诊断协议(ping、traceroute),本身不使用端口,所以端口栏只能填 -1/-1 或"任何",这只是一个占位符。放行 ICMP 只是让别人知道你在线,既不能登录也不能传数据。

保留它还有实际好处:日后 SSH 连不上时,先 ping 一下,就能判断问题出在网络/机器本身,还是仅仅出在 SSH 配置上。

bash
# -t 只看 TCP  -l 只看监听中  -n 数字显示不做解析  -p 显示占用端口的进程
sudo ss -tlnp      # 与安全组规则相互对照,看服务器实际在监听哪些端口

1.5让服务器自动反击:fail2ban

前面的加固都是"被动"防守——把门锁牢。fail2ban 让服务器学会"主动反击":它持续盯登录日志,发现某个 IP 反复失败尝试,就自动封禁一段时间。

bash
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——这样软件升级时你的配置不会被覆盖。

/etc/fail2ban/jail.d/sshd.local
[sshd]
enabled  = true          # 启用本规则
backend  = systemd       # 从 systemd 日志读失败记录(Rocky 9 的正确方式)
maxretry = 3             # 允许的最大失败次数
findtime = 600           # 统计窗口:600 秒内累计
bantime  = 3600          # 触发后的封禁时长:3600 秒(1 小时)

这几行连起来读就是一句话:10 分钟内失败 3 次,把这个 IP 封禁一小时。

backend = systemd 是 Rocky 9 的关键:新系统的日志由 systemd 统一管理,网上许多旧教程仍在读 /var/log/secure,那种写法在这里不生效,切勿照抄。

bash
sudo systemctl enable --now fail2ban     # --now 立即启动,enable 设为开机自启
sudo fail2ban-client status sshd         # 看运行状态,重点看 Banned IP list

Banned IP list 里列出的就是被你的服务器自动封禁的攻击者。和本章开头的爆破日志对照一下:几分钟前还在猜你密码的 IP,如今已被关进"小黑屋"。如果名单暂时是空的,并不代表操作有误——只是说明这台机器还没被扫描器盯上,规则已经生效,挂一段时间自然会出现记录。

1.6验证、提交与自救

"能跑起来"不等于"做对了"。运维讲究验证到底,逐项确认:

  • VNC 能以 study 登录,tty 显示 /dev/tty1
  • 本机 ssh study@公网IP 能登录,且不需要密码
  • 用密码登录会被拒绝,root 无法直接登录
  • sudo sshd -t 没有报错
  • firewall-cmd --list-all 里能看到 ssh,且没有多余端口
  • fail2ban-client status sshd 显示规则正在运行
  • teacher 账号已装好老师的公钥;sudo -u teacher sudo -n true 成功

提交时只需要给老师主机的公网 IP 一样东西。老师会用自己的私钥、通过你安装的 teacher 公钥登录检查。再次强调:任何时候都不要把自己的私钥发给任何人。

遇到问题怎么办

现象原因与处理
SSH 连不上了用控制台 VNC 登录(不走 SSH),把 sshd_config 改回来,再 sshd -t 验证、systemctl reload sshd 生效
密钥登录仍要求输密码99% 是权限问题:~/.ssh 是否 700、authorized_keys 是否 600、属主是否你本人(而不是 root)
老师连不上多半是 AllowUsers 漏了 teacher,或 teacher 的 .ssh 属主没改回 teacher
所有账号都用不了 sudosudoers 语法错误。VNC 以 root 登录,修正或删除 /etc/sudoers.d/teacher,再 visudo -c;忘了 root 密码可重置实例密码
firewalld 改了不生效检查是不是忘了 --permanent 或忘了 --reload

本讲小结

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

第 2 讲 Lecture 02 · Go Live

让你的网站上线:域名解析与 Nginx 个人主页

为自己已备案的域名配置解析,在服务器上安装 Nginx,发布一个带备案号的个人主页, 让任何人输入你的域名都能访问到它。

01说清 DNS 的作用,为域名添加解析记录并验证生效
02按需在安全组和防火墙中开放网页访问端口
03安装 Nginx,编写站点配置,并安全地让配置生效
04发布个人主页,排查 403、404、连接超时等常见故障

开始之前:上一讲已禁用 SSH 密码登录,从本讲起统一用自己电脑上的 SSH 客户端登录。控制台 Workbench 需要上传私钥,不推荐;VNC 留作救急的后路。

2.1补做一步,再理解三个环节

上一讲建的 teacher 没有密码,而 Rocky 规定 wheel 组用 sudo 必须输入本人密码,所以 teacher 实际上用不了 sudo。先用 study 为它补一条免密规则:

bash
echo "teacher ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/teacher
sudo chmod 440 /etc/sudoers.d/teacher
sudo visudo -c
sudo -u teacher sudo -n true && echo "teacher 免密 sudo 可用"

上一讲用 ssh study@47.xx.xx.xx 登录,一串数字既难记也不适合告诉别人。域名就是为了解决这个问题,而负责把域名翻译成 IP 的系统叫 DNS——互联网的"通讯录"。要让别人通过域名看到主页,需要打通三个环节,本章就按这个顺序走:

环节本章做了什么出问题时的表现
DNS添加 A 记录,域名指向服务器 IP域名打不开,IP 能打开
开门安全组 + firewalld 放行 80 端口连接超时
Nginx安装、配置站点、发布主页403、404 或看到默认页

今后排查网站故障,就按这三个环节从前往后逐一检查。

2.2域名解析

DNS 记录添加后需要一段时间才在全网生效,所以第一步就做解析,让它在后台慢慢生效,同时去做后面的步骤。等发布主页时,解析基本就好了——把需要等待的事情提前做,是运维里很常用的思路。

三个字段

字段含义本章取值
记录类型A 记录 = 把域名指向一个 IPv4 地址A
主机记录域名前缀。@ 代表域名本身,www 代表 www 子域名@ 和 www 各一条
TTL各地 DNS 缓存时长,默认 10 分钟保持默认

控制台操作:云解析 DNS → 域名解析 → 解析设置 → 添加记录,类型选 A,主机记录填 @,记录值填服务器公网 IP,保存;再添加一条主机记录为 www 的。这样 你的域名 和 www.你的域名 都指向了这台服务器。

bash
# 看 Address 一栏是不是你的服务器公网 IP
nslookup 你的域名
nslookup www.你的域名

查不到或 IP 不对先别急,可能仍在生效中。继续做后面的步骤,几分钟后再查。

2.3为网站开门

上一讲遵循"最小化"只开了 SSH。现在服务器要对外提供网页,就开放 HTTP 的 80 端口。"最小化"不是什么都不开,而是只在服务真正上线时才开对应的门:本章只开 80,HTTPS 的 443 等下一讲配证书时再开。两层门都要打开。

安全组:实例 → 安全组 → 配置规则 → 入方向 → 添加规则,协议 TCP、端口 80、授权对象 0.0.0.0/0(控制台通常也有 "HTTP(80)" 快速添加)。

bash
sudo firewall-cmd --permanent --add-service=http   # 永久放行 HTTP(即 TCP 80)
sudo firewall-cmd --reload
sudo firewall-cmd --list-services                 # 应包含 ssh 和 http

2.4安装 Nginx 并配置站点

Nginx 的工作很直接:守在 80 端口,收到浏览器请求后找到对应的网页文件发回去。后续还会用它配 HTTPS、做反向代理、充当 AI 服务的入口网关,它将贯穿整门课程。

bash
sudo dnf install -y nginx              # 从系统源安装
sudo systemctl enable --now nginx     # 立即启动并设为开机自启
systemctl status nginx               # 应显示 active (running),按 q 退出
nginx -v                             # 查看版本号

浏览器访问 http://你的服务器公网IP,看到 Nginx 默认测试页,说明安装成功、80 端口也已打通。

准备网站目录

不改系统默认页面,为自己的站点单独建目录:结构清晰,以后加第二个站点也不互相干扰。

bash
sudo mkdir -p /var/www/mysite                    # -p:父目录不存在时一并创建
sudo chown -R study:study /var/www/mysite         # 交给 study,之后改网页不必用 sudo
sudo chmod 755 /var/www/mysite                    # 属主可读写,其他人(含 Nginx)可进入
ls -ld /var/www/mysite                           # 应显示 drwxr-xr-x,属主为 study

网上很多"Rocky 因 SELinux 导致 403"的讨论。阿里云提供的 Rocky 公共镜像默认关闭了 SELinux,用 getenforce 确认(应显示 Disabled),因此本章不涉及 SELinux。但在本地自装的 Rocky 上遇到 403,仍要把它列为排查方向。

编写站点配置

Nginx 会自动加载 /etc/nginx/conf.d/ 下所有 .conf 文件,为本站新建一个(把 example.com 换成你自己的域名):

bash
sudo vi /etc/nginx/conf.d/mysite.conf
nginx · /etc/nginx/conf.d/mysite.conf
server {
    listen 80;                                   # 监听 80 端口,接收 HTTP 请求
    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;               # 按路径找文件或目录,找不到返回 404
    }
}

其中 server_name 一行最关键。一台服务器可以同时跑很多网站,Nginx 靠浏览器请求头里的 Host 字段区分:请求域名和哪个站点的 server_name 匹配,就交给哪个站点处理。下一讲申请证书时,证书工具也会依据这一行识别你的域名,现在务必写对。

bash
sudo nginx -t                    # 显示 syntax is ok / test is successful 才算通过
sudo systemctl reload nginx      # 重载生效;reload 不中断正在进行的访问

这和上一讲的 sshd -t 是同一个道理:改完配置先测试,通过了再生效。配置有错却直接重载,可能导致网站中断。

2.5发布个人主页

主页内容自由发挥,但有一条硬性要求:网站首页底部必须显示 ICP 备案号,并链接到工信部备案查询网站 https://beian.miit.gov.cn/。这是备案网站的合规要求,不是可选项。

可以借助 AI 生成主页,例如这样提要求:

"帮我写一个单文件的个人主页 index.html,包含姓名、班级、自我介绍和兴趣爱好;样式写在同一个文件里,不引用任何外部资源;页面底部显示备案号'浙ICP备XXXXXXXX号-1',并链接到 https://beian.miit.gov.cn/,在新窗口打开。"

但 AI 的结果必须自己检查:备案号是不是你自己的、链接地址是否正确、页面能否正常显示。能把页面的大致结构讲清楚,才算真正完成。

上传到服务器(两种方式任选)

bash
# 方式一:本地写好,用 scp 上传(用的和 ssh 同一把密钥,不需要密码)
scp index.html study@你的服务器公网IP:/var/www/mysite/

# 方式二:直接在服务器上编辑(目录已交给 study,不需要 sudo)
vi /var/www/mysite/index.html

完成后访问 http://你的域名 和 http://www.你的域名,应该都能看到主页。

为什么用 IP 访问看到的还是默认页

这正是 server_name 在起作用:用 IP 访问时,请求里的 Host 不是你的域名,匹配不上你的站点,于是交给系统默认站点处理。可以在服务器上用 curl 模拟不同 Host 亲眼验证:

bash
# -H 手动指定请求头中的 Host 字段;127.0.0.1 表示访问本机
curl -H "Host: 你的域名" http://127.0.0.1      # 返回你的主页内容
curl http://127.0.0.1 | head -5               # 不带域名 → 默认测试页

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

部署时最常见的故障之一是 403 Forbidden,而最常见的原因是文件权限不对。我们自己造一次,再顺着日志找到原因、修好它。

bash
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   # -I 只看响应头:403

文件明明属于 study,为什么 Nginx 读不了?因为 Nginx 不是以 study 的身份运行的:

bash
ps -o user= -C nginx | sort -u          # 主进程 root,工作进程 nginx
ls -l /var/www/mysite/                 # test.html 显示 -rw-------,只有 study 可读
sudo tail -3 /var/log/nginx/mysite_error.log   # 里面有 (13: Permission denied)

真正读取网页文件的是 Nginx 的工作进程,身份是 nginx 用户。对 nginx 用户来说 test.html 属于"其他人的文件",而 600 不允许其他人读取,于是 403。修复就是放宽权限:

bash
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           # 实验结束,删掉测试文件

排查 403 的思路:先看错误日志找原因,再用 ls -l 核对权限。网页文件至少 644、目录至少 755,Nginx 才能读到。

看见你的第一位访客

bash
# tail -f 持续显示文件末尾新增的内容,按 Ctrl+C 退出
sudo tail -f /var/log/nginx/mysite_access.log

请同学用手机访问你的域名,对方每刷新一次,屏幕上就多一行:来访者 IP、时间、访问的页面、状态码、浏览器。日志是运维人员最重要的信息来源——网站出问题,第一件事就是看日志。

2.7验证、排障与合规

  • nslookup 你的域名 返回服务器公网 IP,带 www 的也一样
  • 安全组已放行 TCP 80,firewall-cmd --list-services 包含 http
  • systemctl is-enabled nginx 显示 enabled,nginx -t 通过
  • 浏览器访问 http://你的域名 与 http://www.你的域名 都显示你的主页
  • 页面底部显示你的备案号,点击能跳转到工信部备案查询网站
  • 访问日志中能看到访问记录
现象可能原因排查方法
域名打不开,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 启动/重载失败配置语法错误nginx -t 看报错行号;journalctl -u nginx

合规提醒:除 ICP 备案外,按规定网站开通后 30 日内还需在"全国互联网安全管理服务平台"办理公安联网备案,取得公安备案号后同样要显示在网站底部。具体要求以当地规定为准,请及时办理。

提交方式:本讲需要向老师提交你的域名,与上一讲的 IP 一起填入收集表。

第 3 讲 Lecture 03 · HTTPS

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

为上一章发布的主页申请免费 HTTPS 证书,让网站从"不安全"变成带安全锁的加密网站, 并配置证书到期前自动续期。完成这一讲,你就有了第一个完整作品。

01说清明文传输的风险,以及 HTTPS 加密了什么、没加密什么
02理解数字证书的作用,以及 CA 如何确认域名属于你
03用 certbot 申请证书,编写 Nginx 的 HTTPS 配置
04配置证书自动续期,并验证它确实可用

开始之前:确认 http://你的域名 和 http://www.你的域名 都能看到主页。本章申请证书要用到这两个域名,缺一不可。

3.1你的网站正在"裸奔"

访问 http:// 时浏览器会标注"不安全",这并非危言耸听:HTTP 传输的所有内容都是明文,你和服务器之间经过的每一台网络设备,都能原样看到你访问了什么页面、提交了什么内容。

在服务器上亲手"偷看"一次自己网站的流量。先装抓包工具:

bash
sudo dnf install -y tcpdump      # tcpdump:抓取并显示网络数据包
bash
# -i any:在所有网卡上抓   -A:以文本形式显示内容
# -nn:IP 和端口都用数字,不做名称解析
# grep -a -E:只显示请求行、域名、浏览器信息、网页标题(-a 把二进制当文本)
sudo tcpdump -i any -A -nn 'tcp port 80' | grep -a -E "GET |Host:|User-Agent|<title>"

请同学用手机访问你的 http 站点,屏幕上就会出现:请求了哪个页面(GET /)、哪个域名(Host:)、什么手机和浏览器(User-Agent),甚至网页标题(<title>)。如果页面里有登录表单,账号密码也会被看得一清二楚。按 Ctrl+C 停止抓包——本章最后我们会再抓一次做对比。

3.2HTTPS 与证书的原理

HTTPS 就是 HTTP 加上 TLS 加密,它解决两个问题:

  • 加密:传输内容变成只有浏览器和服务器才能解开的密文。
  • 身份认证:证明你连接的确实是这个域名的真实服务器,而不是中间的冒充者。

身份认证依靠数字证书——网站的"身份证":写明这张证书属于哪些域名、由谁颁发、什么时候到期。身份证由公安机关签发才可信,证书则要由 CA(证书颁发机构)签发。浏览器和操作系统内置了一份受信任的 CA 名单,只有名单上的 CA 签发的证书才会显示安全锁。本章使用 Let's Encrypt:免费、全自动的公益 CA。

CA 怎么确认域名是你的:HTTP-01 验证

  • 你在服务器上运行 certbot,向 Let's Encrypt 申请证书;
  • Let's Encrypt 交给 certbot 一串随机内容,要求放到 http://你的域名/.well-known/acme-challenge/一串随机文件名;
  • certbot 把这个文件写进网站目录 /var/www/mysite;
  • Let's Encrypt 从互联网访问该地址,读到正确内容,就证明你控制着这个域名和这台服务器,签发证书。

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

证书的有效期

Let's Encrypt 证书有效期只有 90 天,并已宣布在 2028 年 2 月前逐步缩短到 45 天;同时已于 2025 年停止发送到期提醒邮件。也就是说,靠人记着续期迟早会忘——自动续期是必须完成的步骤,不是可选项。

3.3开放 443 端口

HTTPS 使用 443 端口。按照"服务上线时才开门"的原则现在开放它,两层门都要开。安全组里:入方向 → 添加规则,TCP、443、0.0.0.0/0。

bash
sudo firewall-cmd --permanent --add-service=https   # 永久放行 HTTPS(即 TCP 443)
sudo firewall-cmd --reload
sudo firewall-cmd --list-services                 # 应包含 ssh、http、https

80 端口不能关。一是有人直接输入 http:// 访问时,需要 80 把他引导到 HTTPS;二是证书每次续期,Let's Encrypt 仍要通过 80 端口验证。关掉 80,三个月后证书就无法续期了。

3.4申请证书

certbot 是 Let's Encrypt 官方推荐的申请工具,位于 EPEL 源中(第 1 章装 fail2ban 时已装过 EPEL):

bash
sudo dnf install -y certbot
certbot --version

先演练:--dry-run

Let's Encrypt 对失败有次数限制:同一域名一小时内验证失败太多次会被暂时拒绝。所以先用 --dry-run 走完整个流程,但连接测试环境,既不真签发也不占用正式次数。这和前两讲"改配置先用 -t 测试"是同一个习惯:先演练,再正式。

bash
# certonly      只申请证书,不改 Nginx 配置(配置自己写,才能看懂每一行)
# --webroot      用"网站目录"方式验证:把验证文件放进目录,由 Nginx 发出去
# -w             网站目录,就是 Nginx 配置里的 root
# -d             证书要包含的域名,带 www 和不带各一个
# -m             联系邮箱;--agree-tos 同意条款;--no-eff-email 不订阅 EFF 邮件
# --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

行尾的反斜杠 \ 表示"命令还没写完,下一行接着",三行合起来是一条命令。看到 The dry run was successful. 说明演练成功;失败就按"解析、80 端口、Nginx"三个方向逐一检查,修好后再演练一次。

正式申请

bash
sudo certbot certonly --webroot -w /var/www/mysite \
     -d example.com -d www.example.com \
     -m 你的邮箱 --agree-tos --no-eff-email

看到 Successfully received certificate. 就说明证书已签发。

看看你的证书

bash
sudo certbot certificates                          # 域名、到期时间、剩余天数、文件位置
sudo ls -l /etc/letsencrypt/live/example.com/      # 目录名取自第一个 -d 域名
文件内容用途
fullchain.pem你的证书 + 中间证书发给访问者,证明网站身份
privkey.pem证书私钥留在服务器上解密,绝不能泄露
cert.pem仅你的证书本章不直接使用
chain.pem仅中间证书本章不直接使用

privkey.pem 与第 1 讲的 SSH 私钥是同一个道理:谁拿到它,谁就能冒充你的网站。它所在的目录只有 root 能访问,这是 certbot 有意设置的保护。

bash
# -noout 不输出原始编码;-issuer 颁发者;-dates 生效与到期;-ext 包含的域名
sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \
     -noout -issuer -dates -ext subjectAltName

输出可以交给 AI 逐项解释,但解释之后请自己核对两件事:到期时间是否大约在 90 天之后?域名列表里两个域名是否都在?

3.5配置 Nginx 启用 HTTPS

先备份。注意备份文件名不能以 .conf 结尾,否则会被 Nginx 一并加载,造成重复配置。

bash
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;443 端口才真正提供内容。把文件全部内容替换为下面这段,并把所有 example.com 换成你的域名(共 6 处:两行 server_name 各 2 处,两行证书路径各 1 处):

nginx · mysite.conf
# ① 80 端口:只做两件事——续期验证文件;其余请求一律跳转 HTTPS
server {
    listen 80;
    server_name example.com www.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/mysite;              # 续期时 CA 仍通过 HTTP 读这里的验证文件
    }

    location / {
        return 301 https://$host$request_uri;   # 其余请求永久跳到同一地址的 HTTPS 版
    }
}

# ② 443 端口:真正提供网站内容
server {
    listen 443 ssl;                       # 监听 443 并启用 SSL/TLS 加密
    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://www.example.com/about.html,而不是一律跳回首页。

bash
sudo nginx -t                    # syntax is ok + test is successful 才算通过
sudo systemctl reload nginx

如果 nginx -t 报 cannot load certificate,多半是证书路径里的域名写错了,对照 ls 的结果核对目录名。

验证

浏览器访问 http://你的域名,地址会自动变成 https:// 并出现锁图标;点击可查看颁发者和有效期,应与上面查到的一致。也可以在本机用 curl 精确验证——Windows PowerShell 里要写成 curl.exe,因为 PowerShell 中的 curl 是另一个命令的别名:

powershell
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

3.6配置自动续期

certbot 自带一个 systemd 定时器 certbot-renew.timer,每天定时检查,在到期前约 30 天自动续期。安装后它不一定已启用,所以主动启用并确认:

bash
sudo systemctl enable --now certbot-renew.timer   # enable 保证重启后依然有效
systemctl list-timers 'certbot*'                   # NEXT 一列是下次检查时间

续期只更新磁盘上的证书文件,Nginx 不会自动读取新证书,必须重载一次。certbot 为此提供"钩子"机制:放在 /etc/letsencrypt/renewal-hooks/deploy/ 下的脚本,会在每次续期成功后自动执行。

bash · renewal-hooks/deploy/reload-nginx.sh
#!/bin/bash
systemctl reload nginx      # 续期成功后重载 Nginx,让新证书生效
bash
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 说明续期链路是通的。演练时 certbot 会提示跳过了 deploy 钩子,这是正常现象:演练不会真的产生新证书,自然不需要重载 Nginx——钩子脚本本身我们已经手动验证过了。

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

回到 3.1 的实验,这次抓 443:

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

请同学用手机访问你的 https 站点。与 3.1 对比:这次看不到 GET、User-Agent,也看不到网页标题,屏幕上全是无法阅读的乱码——内容已经被加密了。

但仔细看,可能会在乱码中找到一样东西:你的域名。因为浏览器在建立加密连接前,要先告诉服务器"我要访问哪个网站",服务器才能拿出对应证书,而这一步是明文进行的。

记住这句话:HTTPS 隐藏的是你看了什么内容,而不是你访问了哪个网站。

3.8验证交付与排障

  • 安全组和 firewalld 都已放行 443,并且 80 仍然开放
  • sudo certbot certificates 显示证书包含两个域名,剩余约 89 天
  • sudo nginx -t 通过
  • 浏览器访问 http 会自动跳转 https,地址栏有锁图标
  • curl.exe -I http://域名 返回 301;https 域名与 www 域名都返回 200
  • systemctl list-timers 'certbot*' 能看到续期定时器
  • sudo certbot renew --dry-run 成功
  • 主页底部的备案号仍然正常显示
现象可能原因排查方法
certbot 报 DNS problem: NXDOMAIN域名或 www 子域名没有解析记录nslookup 两个域名,补上缺失的 A 记录
certbot 报 Timeout during connect80 端口不通检查安全组和 firewall-cmd --list-services
certbot 报 unauthorized / 404验证文件没能被访问到核对 -w 是否与 Nginx 的 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:// 或相对路径
改坏了想恢复—cp mysite.conf.bak mysite.conf,再 nginx -t 与 reload

本讲小结:开门(放行 443,80 不能关)→ 申请(先 dry-run,再正式)→ 配置(80 跳转、443 供内容,privkey.pem 绝不外泄)→ 续期(定时器 + deploy 钩子,靠自动化而不是靠人记)。

你的主页现在拥有域名、备案号和 HTTPS 安全锁,已经是一个完整、规范的网站了。