Server Ops · 听课笔记

我的运维笔记

把一台“裸奔”的服务器,变成有域名、有备案、有安全锁的网站。

第 1 讲到第 3 讲,按课堂顺序整理:先加固入口,再让网站上线,最后用 HTTPS 收尾。每一段都留下可执行的命令、容易踩的坑和验证方法。

Rocky Linux 9.8 阿里云 ECS 3 讲 · 1 条主线
三句常备 私钥永不外传 改配置先留后路 先验证,再收口
LECTURE
01 / 03

第 1 讲

服务器安全加固

把“谁都能来猜密码”的服务器,收紧成只认密钥、只开必要端口,并且会主动封禁攻击者的机器。

SSH firewalld fail2ban

01先看见威胁

服务器接入公网的那一刻,22 端口就可能已经被自动化扫描器遍历。攻击者用大量常见用户名和密码反复尝试登录,这就是暴力破解。第一件事不是急着改配置,而是先确认:自己的机器正在被谁敲门。

终端 · 查看 SSH 登录失败记录journalctl
sudo journalctl -u sshd | grep -i "failed" | tail -30
  • journalctl -u sshd:只看 sshd 这项服务的日志。
  • grep -i "failed":筛出登录失败记录,-i 忽略大小写。
  • tail -30:只保留最后 30 条,先看最近的动静。

如果看到大量陌生 IP、并且集中在尝试 root,说明服务器已经被扫描。接下来沿着“攻击者能从哪儿进来”逐处收口。

02先分清两条登录通道

这是整讲最重要的一张地图。登录云服务器其实有两条机制完全不同的路,知道第二条路存在,才敢动手改第一条路。

SSH 与 VNC 的差别
通道实际路径被改坏时定位
SSH / Workbench 公网 → 安全组 22 端口 → 主机 sshd 改坏 sshd 或封掉 22,会一起断 日常操作
VNC 阿里云带外管理,不经过安全组和 sshd 仍可用系统账号密码登录 救急后路
关键认知

Workbench 本质仍是 SSH,不是后路;真正能救命的是 VNC。在 VNC 里执行 tty 会看到 /dev/tty1,这是机器本地控制台;SSH 登录则显示 /dev/pts/0 一类伪终端。两者从系统内部印证了“这是两条独立通道”。

所以,做任何高风险修改前,先按控制台提示走一遍 VNC:用普通用户登录、执行 whoami 和 tty、再 exit 退出。后路只有亲自走通过一次才可靠。

03用密钥代替密码登录

密码可能被猜中,也可能被泄露。密钥认证换了一套思路:本地生成一对相互配对的钥匙,私钥只留在自己电脑,公钥放到服务器。登录时,服务器用公钥出一道只有对应私钥才能解开的“数学题”。

KEY 01

私钥

身份的唯一凭证,留在自己的电脑上,绝不外传、绝不发给任何人。

KEY 02

公钥

可以公开。安装到服务器账号的 authorized_keys 里。

KEY 03

验证

服务器用公钥出题,只有持私钥的人能解开并完成登录。

先准备一个日常使用的普通用户

服务器 · 创建 study 用户把 study 换成自己的用户名
sudo adduser study
sudo passwd study
sudo usermod -aG wheel study

wheel 组的成员才能用 sudo 执行管理员操作。加固完成后会禁止 root 直接远程登录,所以必须先把普通用户准备好。

在本机生成密钥对

本机 · 生成 ed25519 密钥Windows PowerShell / macOS / Linux 通用
ssh-keygen -t ed25519 -C "你的名字-服务器加固"

一路回车即可。-t ed25519 指定现代、短小且安全的算法,-C 只是备注,方便日后辨认这把钥匙属于谁。生成后查看公钥:

本机 · 查看公钥复制以 ssh-ed25519 开头的整行
# Windows PowerShell
type $env:USERPROFILE\.ssh\id_ed25519.pub

# macOS / Linux
cat ~/.ssh/id_ed25519.pub

复制出来的是一整行公钥,中间不能断行。不带 .pub 的那个文件才是私钥,不要复制、不要上传、不要发给老师。

把公钥安装到服务器

服务器 · 写入 authorized_keys权限一步都不能省
sudo su - study
mkdir -p ~/.ssh
chmod 700 ~/.ssh
vi ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
ls -l ~/.ssh

在 vi 中按 i 进入编辑模式,粘贴整行公钥,按 Esc,输入 :wq 回车保存。最后 ls -l 应看到 authorized_keys 权限为 -rw-------,属主是你本人。

最容易卡住的坑

.ssh 目录或 authorized_keys 权限过松时,SSH 会直接拒绝使用它登录,而且往往不给明显报错。目录必须是 700,文件必须是 600,属主必须正确。

先验证密钥能登录,再往下做

本机 · 用密钥登录正常情况不应再要求输入密码
ssh study@服务器公网IP
顺序一旦反了,就会出现“密码已经禁掉、密钥却还没配好”的尴尬。先验证、再收口。

给老师准备巡检账号:装公钥,不送私钥

“把私钥发给老师”是绝对错误的做法。正确思路正好相反:让老师拥有自己的密钥对,你只把老师的公钥装到服务器上。公钥可以公开,没有老师的私钥,别人拿到也进不来。

服务器 · 创建 teacher 并安装老师的公钥公钥由老师提供
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 必须输入本人密码,所以要给这个巡检账号单独配置免密 sudo:

服务器 · 配置并检查免密 sudovisudo -c 必须通过
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 可用"
注意

这里安装的是老师的公钥,不是你的公钥,别搞混。你的私钥自始至终只留在自己的电脑上。sudoers 配置写错会让所有账号的 sudo 失效,所以务必执行 visudo -c 检查;万一出错,用 VNC 以 root 登录修复。

04收紧 sshd 配置

密钥验证通过后,才开始“收口”。修改 SSH 服务端主配置文件:

服务器 · 编辑 SSH 主配置/etc/ssh/sshd_config
sudo vi /etc/ssh/sshd_config

找到并修改以下项目,没有的就新增:

配置 · sshd_config 关键项逐项确认后再重载
PubkeyAuthentication yes
PasswordAuthentication no
PermitRootLogin no
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers study teacher
每一项防的是什么
配置作用风险提示
PubkeyAuthentication yes允许密钥登录,是本章加固的基础保持开启
PasswordAuthentication no禁止密码登录,杜绝猜密码必须确认密钥能登录后再改
PermitRootLogin no禁止 root 直接远程登录,缩小攻击面需要管理时先登普通用户再 sudo
MaxAuthTries 3单次连接最多 3 次认证尝试拖慢暴力破解
ClientAliveInterval 300每 300 秒探测客户端是否还在配合下一项清理空闲会话
ClientAliveCountMax 2连续 2 次无响应就断开自动释放挂着的连接
AllowUsers study teacher登录白名单,只允许这两个账号写错用户名或漏掉 teacher 会把人挡在门外

改完不要直接重载。先测试语法,确认无误再让配置生效:

服务器 · 测试并重载 sshd没有输出通常代表语法正确
sudo sshd -t
sudo systemctl reload sshd

到这里,服务器已经从“谁都能来猜密码”,变成了“只有持有私钥的白名单用户才能进入”。

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

云服务器与普通物理机最大的不同,是它有两层防火墙,而且要分别配置。两层都遵循同一个原则:最小化,只开必须的门。

LAYER 01

安全组

阿里云平台层防火墙,管公网流量能不能进入实例。

LAYER 02

firewalld

操作系统内部防火墙,管流量到达实例后放不放行到具体服务。

RULE

两层都要开

只配一层,服务仍然可能进不来;两层相互独立。

主机防火墙:放行 SSH

服务器 · firewalld 放行 SSHpermanent + reload 缺一不可
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
Rocky 细节

改规则必须加 --permanent,再执行 --reload。否则规则只在当前运行状态生效,重启后就没了。

安全组:保留 SSH,删掉不必要的开放端口

  • 必须保留 SSH(TCP 22):本机 SSH 客户端和控制台 Workbench 都走它,删掉会同时中断这两条路。
  • 删除或收紧任何用不到的开放端口。
  • ICMP-IPv4 规则可以保留,用于 ping 连通性诊断。
别被“任何端口”吓到

安全组按“协议 + 端口”控制。TCP/UDP 才有端口概念,SSH 写 TCP 22、HTTP 写 TCP 80;ICMP 是网络层诊断协议,本身不使用端口,所以控制台只能填 -1/-1 或显示“任何”。它不代表所有端口都被打开,放行 ICMP 只是允许别人 ping 通你,不能登录、不能传数据。

把服务器实际监听的端口与安全组对照,确认没有多开:

服务器 · 查看监听端口TCP / 监听 / 数字 / 进程
sudo ss -tlnp

-t 只看 TCP,-l 只看监听状态,-n 用数字显示不做域名解析,-p 显示占用端口的进程。

06让服务器自动反击:fail2ban

前面的加固都是“被动防守”。fail2ban 会持续盯登录日志,发现某个 IP 反复失败,就自动封禁一段时间。服务器从“守得住”升级为“会还手”。

服务器 · 安装 fail2banRocky 默认源没有,先装 EPEL
sudo dnf install -y epel-release
sudo dnf install -y fail2ban

规范做法是在 jail.d/ 下新建 .local 文件,而不是直接改主配置 jail.conf。这样软件升级时自己的规则不会被覆盖。

服务器 · 新建 SSH 防护规则/etc/fail2ban/jail.d/sshd.local
sudo vi /etc/fail2ban/jail.d/sshd.local
配置 · sshd.localRocky 9 用 systemd 后端
[sshd]
enabled = true
backend = systemd
maxretry = 3
findtime = 600
bantime = 3600

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

别照抄旧教程

Rocky 9 的日志由 systemd 统一管理,必须写 backend = systemd。网上很多旧教程还在读 /var/log/secure,那种写法在这里不生效。

服务器 · 启动并查看战果重点看 Banned IP list
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd

如果封禁名单暂时为空,不代表配置失败,只说明这台机器还没被扫描器盯上。规则已经生效,挂一段时间自然会出现记录。

07验证与自救

“能跑起来”不等于“做对了”。逐项确认:

  • VNC 能以 study 登录,tty 显示 /dev/tty1
  • 本机 ssh study@公网IP 能登录,且不要密码
  • 用密码登录会被拒绝
  • root 无法直接远程登录
  • sshd -t 没有报错
  • firewall-cmd --list-all 能看到 ssh,且没有多余端口
  • fail2ban-client status sshd 显示规则运行中
  • teacher 已装老师的公钥,老师能凭自己的私钥登录
  • sudo -u teacher sudo -n true 成功
出问题时的自救方向
现象多半原因怎么处理
SSH 连不上 sshd 配置改坏,或 22 端口被挡 用阿里云控制台 VNC 登录,把 sshd_config 改回,再 sshd -t、reload
密钥登录仍要密码 目录或文件权限不对,属主不是本人 检查 ~/.ssh 为 700、authorized_keys 为 600,且属主正确
老师连不上 AllowUsers 漏了 teacher,或 .ssh 属主不对 补白名单,chown -R teacher:teacher /home/teacher/.ssh
所有账号 sudo 都失效 sudoers 语法错误 VNC 以 root 登录,修正或删除 /etc/sudoers.d/teacher,再 visudo -c
防火墙规则重启后消失 忘了 --permanent 或 --reload 按“永久写入 → 重载”的顺序重做

小结这一讲真正要带走的

  • 两个入口:SSH 受 sshd 和安全组影响,VNC 才是独立后路。
  • 两把锁:密钥登录 + 最小化放行;安全组和 firewalld 两层都要配。
  • 一个习惯:私钥永不外传,改配置先留后路,先验证再收口。
LECTURE
02 / 03

第 2 讲

网站上线

把域名指向服务器、把 80 端口打开、让 Nginx 把网页发出去;再用日志和权限知识处理 403。

DNS Nginx 权限

01三个环节,缺一不可

让别人通过域名看到主页,需要同时打通三个环节。它们也正好对应三类故障现象:

STEP 01

DNS

让域名指向服务器 IP。坏掉的表现:域名打不开,IP 能打开。

STEP 02

开门

安全组和 firewalld 放行 80。坏掉的表现:连接超时。

STEP 03

Nginx

接收请求并返回网页。坏掉的表现:403、404 或默认页。

这一讲就按这个顺序,一环一环打通。先把需要等待的 DNS 做掉,再去做后面的配置。

02域名解析:先做,让它在后台生效

DNS 是互联网的“通讯录”,负责把域名翻译成 IP。添加记录后需要一段时间才能全网生效,所以第一步就做解析,等发布主页时基本就好了。

添加 A 记录时看到的三个字段
字段含义本讲怎么填
记录类型把域名指向一个 IPv4 地址A
主机记录域名的前缀@ 和 www
TTL各地 DNS 缓存时长保持默认,约 10 分钟

在阿里云“云解析 DNS → 域名解析 → 解析设置”里添加两条 A 记录,记录值都填服务器公网 IP:

  • @:代表域名本身,例如 example.com。
  • www:代表 www.example.com。
本机 · 验证解析看 Address 是否为自己的公网 IP
nslookup 你的域名
nslookup www.你的域名

暂时查不到或 IP 不对,可能只是还在生效。先继续做后面的步骤,几分钟后再查。

03为网站开一扇门:80 端口

上一讲只开了 SSH。“最小化”并不等于什么都不开,而是服务真正上线时,才开放对应的门。这一讲只开 HTTP 的 80;HTTPS 的 443 等到下一讲配证书时再开。

阿里云安全组

进入“实例 → 安全组 → 配置规则 → 入方向 → 添加规则”:协议选 TCP,端口填 80,授权对象填 0.0.0.0/0,表示允许任何来源访问网页。控制台通常也有“HTTP(80)”的快速添加。

主机防火墙 firewalld

服务器 · 放行 HTTP列表里应同时出现 ssh 和 http
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
sudo firewall-cmd --list-services
两层门

安全组和 firewalld 必须都放行。只开一层,浏览器仍然可能连接超时。

04安装 Nginx,配置自己的站点

Nginx 是目前使用最广泛的 Web 服务器之一。它守在 80 端口,收到浏览器请求后,找到对应网页文件发回去;后续的 HTTPS、反向代理也都会用到它。

服务器 · 安装并启动 Nginxactive (running) 才算成功
sudo dnf install -y nginx
sudo systemctl enable --now nginx
systemctl status nginx
nginx -v

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

为站点单独准备目录

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

服务器 · 创建网站目录属主交给 study,日常改网页不用 sudo
sudo mkdir -p /var/www/mysite
sudo chown -R study:study /var/www/mysite
sudo chmod 755 /var/www/mysite
ls -ld /var/www/mysite
SELinux 备注

阿里云提供的 Rocky Linux 公共镜像默认关闭 SELinux,可用 getenforce 确认。本讲不涉及 SELinux 配置;但将来在其他环境遇到 403,记得它也是一个排查方向。

编写站点配置

Nginx 会自动加载 /etc/nginx/conf.d/ 下所有以 .conf 结尾的文件。新建本站配置:

服务器 · 新建站点配置/etc/nginx/conf.d/mysite.conf
sudo vi /etc/nginx/conf.d/mysite.conf
配置 · mysite.conf把 example.com 换成自己的域名
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;
    }
}

server_name 是最关键的一行。一台服务器可以运行多个网站,Nginx 靠浏览器请求头里的 Host 字段来区分:请求域名和哪个站点的 server_name 匹配,就交给哪个站点处理。下一讲申请证书时,证书工具也会根据这一行识别域名。

服务器 · 测试并重载 Nginxsyntax is ok / test is successful
sudo nginx -t
sudo systemctl reload nginx
同一个习惯

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

05发布个人主页

首页底部必须显示自己的 ICP 备案号,并链接到工信部备案查询网站,这是备案网站的合规要求,不是可选项。

本机 · 用 scp 上传网页与 ssh 使用同一把密钥
scp index.html study@你的服务器公网IP:/var/www/mysite/

也可以直接在服务器上编辑,目录已经交给 study,不需要 sudo:

服务器 · 直接编辑首页/var/www/mysite/index.html
vi /var/www/mysite/index.html

完成后访问 http://你的域名 和 http://www.你的域名,应该都能看到主页。用 IP 访问看到的却仍是 Nginx 默认页,这正好证明 server_name 在起作用:IP 请求的 Host 不是你的域名,匹配不上你的站点。

服务器 · 用 curl 验证 Host 匹配127.0.0.1 表示访问本机
curl -H "Host: 你的域名" http://127.0.0.1
curl http://127.0.0.1 | head -5

第一条返回你的主页内容;第二条不带你的域名,返回默认测试页。head -5 只看前 5 行。

06亲手制造一次 403,再修好它

403 Forbidden 是部署网站时最常见的故障之一,最常见的原因是文件权限不对。做一个实验,把原因和修复过程走一遍。

服务器 · 制造 403600 权限会让 Nginx 读不了
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

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

服务器 · 查 Nginx 身份与日志权限与报错会互相印证
ps -o user= -C nginx | sort -u
ls -l /var/www/mysite/
sudo tail -3 /var/log/nginx/mysite_error.log

真正读取网页文件的是 Nginx 的工作进程,身份是 nginx 用户。对 nginx 来说,test.html 属于“其他人”的文件,而 600 权限不允许其他人读取,于是返回 403。错误日志里的 (13: Permission denied) 直接说明了这一点。

服务器 · 修复并清理实验网页文件至少 644,目录至少 755
chmod 644 /var/www/mysite/test.html
curl -I -H "Host: 你的域名" http://127.0.0.1/test.html
rm /var/www/mysite/test.html
排查 403 的顺序:先看错误日志找原因,再用 ls -l 核对权限。

07看见第一位访客,然后逐项验证

在服务器上持续查看访问日志,然后请同学用手机访问你的域名。每刷新一次,屏幕上就多一行:来访者 IP、时间、页面、状态码和浏览器。日志是运维最重要的信息来源。

服务器 · 实时查看访问日志Ctrl+C 退出
sudo tail -f /var/log/nginx/mysite_access.log
  • nslookup 域名和 www 都返回公网 IP
  • 安全组已放行 TCP 80
  • firewall-cmd --list-services 包含 http
  • systemctl is-enabled nginx 显示 enabled
  • nginx -t 通过
  • 域名和 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 日内通常还需要办理公安联网备案,取得公安备案号后同样要显示在网站底部。具体要求以当地规定为准。

小结网站上线的三段链路

  • DNS:两条 A 记录(@ 和 www),解析要等 TTL。
  • 开门:安全组 + firewalld 同时放行 80,缺一不可。
  • Nginx:server_name 匹配 Host;配置先 nginx -t 再 reload。
  • 权限:Nginx 以 nginx 用户读文件,网页 644、目录 755。
LECTURE
03 / 03

第 3 讲

HTTPS 与证书自动续期

从“裸奔”的 HTTP 到带安全锁的 HTTPS:申请免费证书、配置 80 跳转,并让证书在到期前自己续上。

TLS certbot 自动续期

01HTTP 在“裸奔”

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

服务器 · 安装抓包工具只抓自己的网站
sudo dnf install -y tcpdump
服务器 · 抓取 HTTP 明文流量Ctrl+C 停止
sudo tcpdump -i any -A -nn 'tcp port 80' | grep -a -E "GET |Host:|User-Agent|<title>"

请同学用手机访问你的 http://域名,屏幕上会出现:请求了哪个页面(GET /)、访问的是哪个域名(Host:)、用的什么手机和浏览器(User-Agent:),甚至网页标题(<title>)。如果页面里有登录表单,账号和密码也会被看得一清二楚。

实验边界

抓包只用于观察自己的网站和课堂实验,不要对别人的流量做同样的事。

02HTTPS 到底做了什么

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

01

加密

传输内容变成只有浏览器和服务器才能解开的密文。

02

身份认证

证明你连接的确实是这个域名的真实服务器,而不是冒充者。

03

数字证书

网站的“身份证”:写明属于哪些域名、由谁颁发、何时到期。

证书要由证书颁发机构(CA)签发。浏览器和操作系统内置了一份受信任的 CA 名单,只有名单上的 CA 签发的证书,浏览器才会显示安全锁。本讲使用 Let's Encrypt:免费、全自动的公益 CA。

CA 怎么确认域名是你的

本讲使用 HTTP-01 验证。流程可以拆成四步:

  1. 发起申请

    在服务器上运行 certbot,向 Let's Encrypt 申请证书。

  2. 拿到挑战文件

    CA 给出一串随机内容,要求放到 http://你的域名/.well-known/acme-challenge/随机文件名。

  3. 写入网站目录

    certbot 把验证文件写进 /var/www/mysite。

  4. CA 从公网读取

    读到了正确内容,就证明你控制着这个域名和这台服务器,于是签发证书。

三个前提,也是三个排查方向

① 域名正确解析到服务器;② 80 端口对外开放;③ Nginx 能把验证文件发出去(server_name 和 root 都要正确)。

90 天,不能靠人记

Let's Encrypt 证书有效期只有 90 天,并计划在 2028 年 2 月前逐步缩短到 45 天;2025 年起已停止发送到期提醒邮件。靠人记着续期迟早会忘,自动续期是必做步骤,不是可选项。

03开放 443,但 80 不能关

HTTPS 使用 443 端口。按照“服务上线时才开门”的原则,现在开放它。和之前一样,安全组和 firewalld 两层都要开。

阿里云安全组:入方向添加规则,协议 TCP,端口 443,授权对象 0.0.0.0/0;控制台通常也有“HTTPS(443)”快速添加。

服务器 · firewalld 放行 HTTPS核对应包含 ssh、http、https
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-services
80 端口不能关:一是把 http:// 的访问引导到 HTTPS,二是证书每次续期时,Let's Encrypt 仍要通过 80 端口验证。

如果关掉 80,三个月后证书就无法续期了。

04用 certbot 申请证书

certbot 是 Let's Encrypt 官方推荐的申请工具,位于 EPEL 扩展源中。第 1 讲安装 fail2ban 时已经装过 EPEL,这里直接安装即可。

服务器 · 安装 certbot确认版本号
sudo dnf install -y certbot
certbot --version

先演练,再正式申请

Let's Encrypt 对失败的申请有次数限制:同一域名一小时内验证失败太多次,会被暂时拒绝。所以先用 --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
  • certonly:只申请证书,不自动改 Nginx 配置。配置自己写,才能看懂每一行。
  • --webroot -w /var/www/mysite:把验证文件放进网站目录,由 Nginx 发出去;目录要和 Nginx 的 root 一致。
  • -d:证书要包含的域名,带 www 和不带 www 各写一个。
  • -m:联系邮箱;--agree-tos 同意服务条款;--no-eff-email 不订阅 EFF 新闻邮件。

看到 The dry run was successful. 说明演练成功。失败就按“解析、80 端口、Nginx”三个方向排查,修好后重跑。

服务器 · 正式申请去掉 --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.,说明证书已经签发。行尾的反斜杠 \ 表示命令还没写完,下一行接着写,三行合起来是一条命令。

看看证书文件

服务器 · 查看证书目录名取自第一个 -d 域名
sudo certbot certificates
sudo ls -l /etc/letsencrypt/live/example.com/
证书目录里的四个文件
文件内容用途
fullchain.pem你的证书 + 中间证书发给访问者,证明网站身份
privkey.pem证书私钥留在服务器上用于解密,绝不能泄露
cert.pem仅你的证书本讲不直接使用
chain.pem仅中间证书本讲不直接使用
和 SSH 私钥同一个道理

privkey.pem 谁拿到,谁就能冒充你的网站。它所在的目录只有 root 能访问,这是 certbot 有意设置的保护。

还可以用 openssl 查看证书细节,核对颁发者、到期时间和域名列表:

服务器 · 查看证书内容issuer / dates / subjectAltName
sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \
     -noout -issuer -dates -ext subjectAltName

核对两件事:到期时间是否大约在 90 天之后?域名列表里两个域名是否都在?

05改写 Nginx:80 跳转,443 提供内容

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

服务器 · 备份站点配置后缀用 .bak,不要用 .conf
sudo cp /etc/nginx/conf.d/mysite.conf /etc/nginx/conf.d/mysite.conf.bak

现在网站分成两部分:80 端口不再直接提供网页,只负责证书续期验证和跳转;443 端口才真正提供内容。把配置文件全部替换为下面内容,并把所有 example.com 换成自己的域名(共 6 处)。

配置 · 80 跳转 + 443 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;
    }
}

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://www.example.com/about.html,而不是一律跳回首页。

服务器 · 测试并重载cannot load certificate 多半是路径写错
sudo nginx -t
sudo systemctl reload nginx

验证跳转与证书

浏览器访问 http://你的域名,地址应自动变为 https://,并出现锁的图标。点击锁可以查看颁发者和有效期,应与 certbot certificates 查到的一致。

在 Windows PowerShell 中要用 curl.exe,因为 PowerShell 里的 curl 是另一个命令的别名:

本机 · curl 验证301 + 两个 200
curl.exe -I http://example.com
curl.exe -I https://example.com
curl.exe -I https://www.example.com

第一条应返回 301 Moved Permanently,Location 指向 https://example.com/;后两条都应返回 200 OK。

06自动续期:定时器 + 部署钩子

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

服务器 · 启用续期定时器NEXT 是下次检查时间
sudo systemctl enable --now certbot-renew.timer
systemctl list-timers 'certbot*'

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

服务器 · 新建部署钩子/etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
sudo vi /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
脚本 · 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

最后演练一次完整续期:

服务器 · 续期演练不会真的更换证书
sudo certbot renew --dry-run

看到 Congratulations, all simulated renewals succeeded,说明续期流程是通的。演练时 certbot 会提示跳过 deploy 钩子,这是正常的:没有真的产生新证书,自然不需要重载 Nginx。钩子本身已经手动验证过。

07再抓一次包,然后验收

回到第 1 节的实验,这次抓 443 端口:

服务器 · 抓取 HTTPS 流量抓够 40 个包自动停止
sudo tcpdump -i any -A -nn -c 40 'tcp port 443'

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

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

HTTPS 隐藏的是你看了什么内容,而不是你访问了哪个网站。
  • 安全组和 firewalld 都放行 443,且 80 仍开放
  • certbot certificates 显示两个域名,剩余约 89 天
  • nginx -t 通过
  • 浏览器访问 http 自动跳转 https,地址栏有锁
  • curl.exe -I:http 返回 301,两个 https 返回 200
  • systemctl list-timers 'certbot*' 能看到续期定时器
  • certbot renew --dry-run 成功
  • 主页底部备案号仍正常显示
HTTPS 常见故障
现象可能原因排查方法
DNS problem: NXDOMAIN 域名或 www 子域名没有解析记录 nslookup 域名和 www,补上缺失的 A 记录
Timeout during connect 80 端口不通 检查安全组和 firewall-cmd --list-services
unauthorized 且带 404 验证文件没能被访问到 核对 -w 与 Nginx 的 root 是否一致,server_name 是否包含该域名
too many failed authorizations 一小时内失败次数过多 等待一小时,之后先用 --dry-run 演练成功再正式申请
cannot load certificate 证书路径写错 对照 certbot certificates 中的路径修改
HTTPS 访问超时 443 端口没开 检查安全组和 firewalld
访问 http 没有跳转 配置未生效或浏览器缓存 nginx -t 后 reload;用 curl.exe -I 直接看响应
锁的图标带有警告 页面引用了 http:// 开头的图片或脚本 资源地址改为 https:// 或相对路径
改坏配置想恢复 没有备份或改错行 复制回 mysite.conf.bak,再 nginx -t 和 reload

小结三讲之后,拥有一个完整作品

  • 开门:放行 443,但 80 必须保留——跳转和续期都要用。
  • 申请:certbot 以 webroot 方式申请,先 --dry-run 再正式。
  • 配置:80 负责跳转和验证,443 提供内容;privkey.pem 绝不外泄。
  • 续期:90 天有效期靠定时器 + deploy 钩子自动化,不靠人记。