找回密码
立即注册
搜索
发回帖 发新帖

6789

积分

0

好友

858

主题
发表于 7 小时前 | 查看: 1| 回复: 0

新机器上要部署一个带域名的网站,Nginx 安装、证书签发、自动续签配置——这套流程我在 Amazon Linux 2023 上已经操作过不下十几次。

网上的教程大多是 CentOS 时代的写法,搬到 AL2023 上会碰到不少问题:EPEL 装不上,crontab 命令不存在,续签任务看起来配好了,实际一次都没跑过。

这篇按我自己的实际操作顺序写,从系统检查一直到续签监控,命令都可以直接复制。新手照着做,半小时左右能弄完。

先看下我这台机器:

[ec2-user@ALG ~]$ cat /etc/os-release
NAME="Amazon Linux"
VERSION="2023"
PRETTY_NAME="Amazon Linux 2023.12.20261102"

下文域名统一用 example.com 和 www.example.com,换成你自己的就行。


装软件之前,先把网络和域名弄对

我帮人排查证书签不下来,大部分问题出在网络和 DNS 上,跟 Nginx 本身关系不大。所以这一步宁可多花五分钟。

EC2 默认分配的公网 IP,实例停止再启动就会变。域名解析到这个 IP 上,过段时间停机升个配置,网站就打不开了。去控制台 EC2 → 弹性 IP,分配一个,关联到实例上。

安全组入站规则加两条:TCP 80 和 TCP 443,来源都是 0.0.0.0/0。SSH 的 22 端口最好只放自己的 IP。80 端口配完 HTTPS 以后也要一直开着,续签要用,后面会细讲。

AL2023 默认没开 firewalld,SELinux 默认也是 permissive,系统层一般不用额外处理,看一眼心里有数:

systemctl is-active firewalld
getenforce

然后去域名服务商那里加 A 记录,主域名和 www 都指向弹性 IP。回到服务器上核对:

sudo dnf install -y bind-utils
curl -s https://checkip.amazonaws.com
dig +short example.com
dig +short www.example.com
dig +short AAAA example.com

前三条 IP 一致就没问题。最后那条 AAAA 建议一定要查,有的服务商会默认给域名带一条 IPv6 记录,而你的 EC2 没配 IPv6,Let's Encrypt 验证时优先走 IPv6,连不上就失败,报错内容还看不出跟 IPv6 有关系。我同事就在这上面卡了一个多小时,删掉那条记录后一次就过了。

DNS 刚改完可能要等几分钟生效,dig 结果对了再往下走。


安装 Nginx

AL2023 官方仓库自带 Nginx,不用加第三方源:

sudo dnf update -y
sudo dnf install -y nginx
sudo systemctl enable --now nginx
nginx -v
curl -I http://127.0.0.1

返回 HTTP/1.1 200 OK 就是起来了。再用自己电脑的浏览器访问 http://弹性IP,能看到 Nginx 欢迎页,说明安全组也通了。

nginx -v 输出的版本号先记下来,后面写 HTTP/2 配置要用。

接着建网站目录。别放在 /home/ec2-user 下面,这个目录权限是 700,Nginx 的 worker 进程用 nginx 用户运行,读不到里面的文件,访问就是 403。我统一放 /var/www:

sudo mkdir -p /var/www/example.com /var/www/letsencrypt
echo '<h1>Hello HTTPS</h1>' | sudo tee /var/www/example.com/index.html

多建的 /var/www/letsencrypt 是给证书验证用的,具体原理下面讲 Certbot 的时候再说。这个路径我习惯单独给一个目录,跟业务文件分开,以后换网站代码也不会影响续签。

站点配置放在 /etc/nginx/conf.d/ 下面,主配置 nginx.conf 尽量不动,以后升级 Nginx 会省事很多:

sudo vim /etc/nginx/conf.d/example.com.conf

先写一个只有 80 端口的版本:

server {
    listen 80;
    server_name example.com www.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    location / {
        root /var/www/example.com;
        index index.html;
    }
}

改完配置先测试,再重载:

sudo nginx -t
sudo systemctl reload nginx

nginx -t 显示 syntax is ok 和 test is successful 才能 reload。我以前改完配置直接 restart,少了个分号,Nginx 起不来,网站挂了好几分钟。reload 在配置有错时会拒绝加载,旧进程继续工作,比 restart 安全得多。

验证路径通不通,可以自己放个文件测一下:

sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge
echo ok | sudo tee /var/www/letsencrypt/.well-known/acme-challenge/test
curl http://example.com/.well-known/acme-challenge/test

能返回 ok 就说明外部访问这个路径没问题,测完把 test 文件删掉。证书签不下来的时候,先用它排查最快。


Certbot 是什么,它替你干了哪些事

很多新手把 Certbot 和 Let's Encrypt 当成一回事,其实是两样东西。

Let's Encrypt 是发证书的机构,也就是 CA,由非营利组织 ISRG 运营,免费给网站签发证书。Certbot 是装在你服务器上的客户端,由 EFF(电子前哨基金会)开发维护,负责去 Let's Encrypt 申请证书、完成验证、把证书存到本地,以后再按时续签。打个比方,Let's Encrypt 是办证大厅,Certbot 是替你跑腿排队的人。

官网地址是 https://certbot.eff.org/ 。

Certbot 官方网站首页截图

两边沟通用的是 ACME 协议,标准编号 RFC 8555。所以 Certbot 并不是唯一选择,acme.sh、lego 也都是 ACME 客户端,原理一样。我用 Certbot 主要是因为它是 Let's Encrypt 官方推荐的,文档全,出了问题搜得到答案。acme.sh 是纯 shell 写的,支持的 DNS 服务商多,用惯了也挺顺手。

签一张证书,Certbot 在后台大致做了这些事:第一次运行时生成账号密钥,在 Let's Encrypt 注册一个账号。然后告诉 CA 要给哪几个域名申请证书,CA 会出一道“考题”,叫 challenge。Certbot 按要求把答案放好,CA 从外网来检查,检查通过就说明这个域名确实归你管。最后 Certbot 在本地生成证书私钥,提交证书请求,把签好的证书拿回来存到 /etc/letsencrypt 下面。私钥从头到尾都留在你自己的服务器上,不会发给 CA。

考题常见的有两种。

HTTP-01 是最常用的验证方式,CA 会访问 http://你的域名/.well-known/acme-challenge/一串随机文件名,能读到 Certbot 放的内容就算通过。它只走 80 端口,这也是 80 不能关的根本原因。

DNS-01 是让你在域名下加一条 _acme-challenge 开头的 TXT 记录。它不需要开任何端口,也只有它能签通配符证书 *.example.com。代价是续签时要能自动改 DNS,得装对应的 DNS 插件,比如域名托管在 Route 53 上,可以用 certbot-dns-route53 配合 IAM 角色。

至于 Certbot 怎么交这份答卷,它有几种插件可选:

  • --webroot:往已经在运行的网站目录里放验证文件,Nginx 不用停,配置也不会被改动,本文用的就是这个
  • --nginx:自动读取并修改 Nginx 配置,临时加验证规则,签完还能顺手帮你写好 443 配置,省事,但配置会被它改得比较乱
  • --standalone:Certbot 自己临时起一个 Web 服务占用 80 端口,机器上已经有 Nginx 就得先停掉,续签时也一样,不适合线上
  • --manual:手动放文件或者手动加 DNS 记录,偶尔签一张可以,没办法自动续签

想清楚这几种方式的区别,后面配置为什么这么写,是不是就好理解多了?


安装 Certbot

从 CentOS 过来的人,第一反应是 yum install epel-release。AL2023 不支持 EPEL,这条路走不通。

可以先 dnf search certbot 看看官方仓库有没有,不同版本的仓库内容会有变化。我自己用的是 Certbot 官方推荐的 pip 虚拟环境方式,版本新,也不会跟系统 Python 包冲突。系统自带的 python3 是 3.9,我另外装个 3.11:

sudo dnf install -y python3.11 python3.11-pip
sudo python3.11 -m venv /opt/certbot
sudo /opt/certbot/bin/pip install --upgrade pip
sudo /opt/certbot/bin/pip install certbot
sudo ln -sf /opt/certbot/bin/certbot /usr/bin/certbot
certbot --version

能打印版本号就装好了。用的是 webroot 模式,所以 certbot-nginx 插件就没装,Nginx 配置自己写,出了问题也知道该查哪里。


签发证书

签正式证书之前,先用 --dry-run 演练一次。Let's Encrypt 有频率限制,同一组域名 7 天内最多签 5 张一样的证书,反复删了重签很容易被限住,只能等。dry-run 走测试环境,不占次数,也不会保存证书:

sudo certbot certonly --webroot -w /var/www/letsencrypt \
  -d example.com -d www.example.com --dry-run

看到 The dry run was successful 再签正式的:

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

成功后证书路径是:

/etc/letsencrypt/live/example.com/fullchain.pem
/etc/letsencrypt/live/example.com/privkey.pem

现在把站点配置改成完整版:

server {
    listen 80;
    server_name example.com www.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    http2 on;
    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;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

80 端口那段特意保留了验证路径,其他请求一律跳转 HTTPS。这样续签照常走 80,用户访问 http 也会被带到 https。

http2 on 这种写法要 Nginx 1.25.1 及以上。前面记下的版本号如果比这个低,就删掉这一行,把 listen 改成 listen 443 ssl http2。

sudo nginx -t && sudo systemctl reload nginx
curl -I https://example.com
curl -I http://example.com

第一条返回 200,第二条返回 301,浏览器地址栏有小锁,证书部分就完成了。


签完之后,/etc/letsencrypt 里都有什么

这个目录建议花两分钟看一下,以后排查续签问题基本都在这里:

sudo ls /etc/letsencrypt

几个关键目录:

  • accounts:Let's Encrypt 账号信息和账号密钥
  • archive:证书的真实文件,每续签一次多一组,名字是 cert1.pem、cert2.pem 这样递增
  • live:指向 archive 里最新一组文件的软链接,Nginx 配置引用的就是这里
  • renewal:每张证书一个续签配置文件
  • renewal-hooks:放续签前后要执行的脚本,下面有 pre、deploy、post 三个子目录

Nginx 配置里一定要写 live 目录。续签后 live 下的软链接会自动指向新文件。有人把证书复制到 /etc/nginx/ssl/ 下面再引用,续签明明成功了,Nginx 读的还是旧证书,到期照样出问题。

renewal 目录里的文件也值得打开看看:

sudo cat /etc/letsencrypt/renewal/example.com.conf

里面大概是这样:

[renewalparams]
account = 一串账号ID
authenticator = webroot
server = https://acme-v02.api.letsencrypt.org/directory
key_type = ecdsa
[[webroot_map]]
example.com = /var/www/letsencrypt
www.example.com = /var/www/letsencrypt

签证书时用了什么验证方式、webroot 目录在哪,Certbot 都记在这个文件里。所以后面执行 certbot renew 不用带任何参数,它会照着这份记录重新走一遍。反过来说,以后要是改了验证目录,这里没跟着改,续签就会失败。key_type = ecdsa 是 Certbot 2.0 之后的默认值,证书用的是 ECDSA 密钥,比 RSA 更短,握手更快,现在的浏览器都支持。

日常用得最多的几条命令,我整理在这:

# 查看所有证书、包含的域名和到期时间
sudo certbot certificates

# 演练续签
sudo certbot renew --dry-run

# 给现有证书加一个域名,所有域名要一起写上
sudo certbot certonly --webroot -w /var/www/letsencrypt \
  --cert-name example.com \
  -d example.com -d www.example.com -d api.example.com

# 不再使用的证书,删掉后续签任务也会跳过它
sudo certbot delete --cert-name example.com

# 私钥泄露时吊销证书
sudo certbot revoke --cert-name example.com

还有一个 --force-renewal 参数可以强制续签,它会计入频率限制,平时别随手加。


自动续签

这里是 AL2023 上最容易漏掉的地方。pip 装的 certbot 不会自动建定时任务,AL2023 默认又没有安装 cron。照着网上的教程执行 crontab -e,要么提示命令不存在,要么装了 cronie 却没启动 crond,任务写进去了但不会执行。

系统本身就是 systemd,我直接用 systemd timer,不用再装别的东西。

先写 service:

sudo vim /etc/systemd/system/certbot-renew.service
[Unit]
Description=Certbot Renew
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/opt/certbot/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"

再写 timer:

sudo vim /etc/systemd/system/certbot-renew.timer
[Unit]
Description=Run certbot renew twice daily

[Timer]
OnCalendar=*-*-* 03,15:00:00
RandomizedDelaySec=1h
Persistent=true

[Install]
WantedBy=timers.target

一天检查两次是 certbot 官方的建议。证书离到期还远的时候,renew 检查完就直接退出,默认在到期前 30 天左右才真正续签。RandomizedDelaySec 加一点随机延迟,避免大量机器同一时间请求 Let's Encrypt。Persistent=true 的意思是到点时机器正好关机,开机后会补跑一次。

Certbot 的钩子有三种。pre-hook 在续签前执行,post-hook 在续签尝试后执行,deploy-hook 只在某张证书真正续签成功后才执行。reload Nginx 放在 deploy-hook 里最合适,证书没换就不会去动 Nginx。钩子也可以写成脚本放进 /etc/letsencrypt/renewal-hooks/deploy/,加上执行权限,Certbot 续签成功后会自动调用。证书要同步到别的机器,或者顺带发个通知,都可以写在这里面。

启用定时器:

sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renew.timer
systemctl list-timers | grep certbot

能看到下次执行时间,说明定时器生效了。不过这只能说明任务排上了,续签能不能成功还得手动跑一遍:

sudo certbot renew --dry-run
sudo systemctl start certbot-renew.service
journalctl -u certbot-renew.service -n 50 --no-pager

dry-run 显示 all simulated renewals succeeded,service 日志里也没有报错,这样才算配完。如果失败,更详细的日志在 /var/log/letsencrypt/letsencrypt.log。

certbot 本身也要定期升级,我一般每个季度升一次:

sudo /opt/certbot/bin/pip install --upgrade certbot

还有个变化要知道:Let's Encrypt 从 2025 年起已经不再发送证书到期提醒邮件,证书有效期以后也会从 90 天逐步缩短到 45 天。续签失败后不会再有邮件提醒,只能靠自己做监控。


80 端口为什么不能关

我之前遇到过一次。机器上的定时任务一直正常运行,后来做安全整改,有同事觉得全站都上 HTTPS 了,80 端口没用,就在安全组里删掉了。这个想法乍一听挺合理,我当时也没拦。

前面说过,webroot 走的是 HTTP-01 验证,Let's Encrypt 必须从外网访问 80 端口才能读到验证文件。80 一关,每次续签都会失败。从到期前 30 天开始,它一天失败两次,日志里全是 Timeout during connect,因为加了 --quiet,也没人去看。最后证书过期,用户打开网站看到一整页安全警告,我们才发现。

处理起来很快,安全组加回 80,手动 certbot renew,一分钟就恢复了。可网站已经有好几个小时不能正常访问。

所以 80 端口要一直开着,除了验证路径什么内容都不提供,只负责跳转。确实要关 80 的话,就改用 DNS-01 验证,那是另一套配置,这里先不展开。


这些坑我和身边的人都踩过

  • 安全组里的 80 端口不要关
  • 证书路径写 live 目录,别写 archive,也别复制到别的地方
  • 调试时先用 --dry-run,别拿正式环境反复试
  • 改了 webroot 目录,记得同步 renewal 配置文件
  • AL2023 默认没有 cron,别直接照抄 crontab 教程
  • 改完配置先 nginx -t,再 reload
  • 域名的 AAAA 记录要检查
  • 记得绑定弹性 IP
  • 网站文件别放 /home/ec2-user
  • 配了自动续签,也要加到期监控

到期监控脚本

这个脚本从外部连接 443 端口获取证书,检查的是 Nginx 实际返回给用户的那张,比检查本地文件可靠:

#!/bin/bash
# check_cert.sh 用法: ./check_cert.sh example.com
DOMAIN=${1:-example.com}
WARN_DAYS=20

END=$(echo | openssl s_client -connect "${DOMAIN}:443" -servername "${DOMAIN}" 2>/dev/null \
      | openssl x509 -noout -enddate | cut -d= -f2)

if [ -z "$END" ]; then
  echo "[ERROR] ${DOMAIN} 获取证书失败"
  exit 2
fi

DAYS=$(( ( $(date -d "$END" +%s) - $(date +%s) ) / 86400 ))

if [ "$DAYS" -lt "$WARN_DAYS" ]; then
  echo "[WARN] ${DOMAIN} 证书还剩 ${DAYS} 天,检查续签日志"
  exit 1
fi

echo "[OK] ${DOMAIN} 证书还剩 ${DAYS} 天"

续签正常时,剩余天数一般会保持在 30 天上下。降到 20 天以下还没恢复,说明续签已经卡住了。这时离过期还有将近三周,有足够时间排查。告警可以接企业微信或钉钉机器人,在脚本里加一行 curl 就行。脚本最好放在另一台机器上跑,网站所在的机器出了问题,它也能照常告警。


整套流程下来,Nginx 和证书本身都不复杂。把 Certbot 的工作方式弄明白以后,大部分问题都能顺着查下去:验证失败就看 80 端口和 DNS,续签没跑就看 timer,续签成功网站没变就看证书路径和 deploy-hook。

我现在每次配完 HTTPS,都会跑 nginx -t、certbot renew --dry-run、systemctl list-timers 这三条命令,结果都正常,再把监控脚本挂上,这件事才算真正做完。

如果对这类实操内容感兴趣,欢迎到云栈社区和更多同行一起交流。




上一篇:Go 1.24 源码拆解:cpuprof.go 开关、环形缓冲与样本记账机制
下一篇:车载IOMMU多段映射整数溢出漏洞(CVE-2026-82049)深度解析
您需要登录后才可以回帖 登录 | 立即注册

关闭

站长推荐上一条 /1 下一条

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-11-11 17:06 , Processed in 0.106153 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表