1. 项目概述:为什么在 CentOS 7 上用 Let’s Encrypt 给 Nginx 配 SSL 不是“选修课”,而是上线前的必过门槛
你刚在 VMware Workstation Pro 里装好 CentOS 7 Minimal,配好了静态 IP,搭起 Nginx,首页能打开,心里一松——结果第二天就被运维同事拉进群,甩来一条 Chrome 控制台报错截图: NET::ERR_CERT_AUTHORITY_INVALID ;再过两小时,测试同学发来微信:“登录页点提交就卡住,F12 看 Network 标签全是红色 Failed to load resource: net::ERR_INSECURE_RESPONSE ”;第三天,老板邮件抄送你和架构组:“客户反馈表单提交失败,安全团队要求所有对外服务必须启用 HTTPS。”
这不是危言耸听,而是我过去三年在五家不同规模公司部署 Web 服务时踩过的同一块石头。CentOS 7 作为企业级 Linux 的主力发行版,稳定、兼容性好、内核成熟,但它的默认软件源里没有 certbot ,OpenSSL 版本停留在 1.0.2k(2017 年发布),而 Let’s Encrypt 的 ACME v2 协议强制要求 TLS 1.2+ 和 SNI 支持——这意味着,哪怕你照着某篇“三分钟搞定 HTTPS”的博客复制粘贴了全部命令,最后 nginx -t && systemctl reload nginx 成功了,浏览器地址栏依然不会出现那个小小的绿色锁图标。问题不出在 Nginx 配置,而出在证书链完整性、OCSP Stapling 启用状态、私钥权限控制、以及最关键的—— 证书是否被主流浏览器根信任库真正接纳 。
Let’s Encrypt 免费证书不是“能用就行”的玩具。它背后是一套严格的时间窗口机制:证书有效期仅 90 天,自动续期失败一次,服务就会在第 91 天凌晨直接降级为 HTTP;它依赖 DNS 解析稳定性与 HTTP-01 挑战端口(80)的可达性,而很多企业防火墙默认封禁 80 端口出向;它对域名验证有硬性要求——泛域名证书( *.example.com )必须走 DNS-01 挑战,而 DNS API 集成又涉及云厂商密钥管理、TTL 设置、API 权限粒度等实操细节。这些都不是 yum install nginx 那种开箱即用的体验,而是需要你像调试一个分布式系统一样,逐层验证每个环节:从系统时间是否同步(NTP)、SELinux 是否拦截了 certbot 的网络访问、到 Nginx 的 ssl_trusted_certificate 指向路径是否正确、再到 openssl s_client -connect example.com:443 -servername example.com -showcerts 返回的证书链是否完整包含 ISRG Root X1。
这篇文章不讲“如何安装 Nginx”,也不重复 certbot --nginx 的基础命令。我要带你从 CentOS 7 Minimal 的纯净系统开始,亲手构建一条 可审计、可复现、可监控、抗故障 的 HTTPS 交付流水线。你会看到:为什么 certbot-auto 已被弃用, dnf install python3-certbot-nginx 在 CentOS 7 上为何会报错;为什么必须手动编译 OpenSSL 1.1.1w 而非升级系统自带版本;为什么 nginx.conf 里 ssl_protocols 不能只写 TLSv1.2 ,而要显式排除 TLSv1.0 和 TLSv1.1 ;以及——当 certbot renew --dry-run 显示 The following errors were reported by the server 时,如何通过 /var/log/letsencrypt/letsencrypt.log 里的 acme.messages.Error 字段,精准定位是 DNS 解析超时、还是 .well-known/acme-challenge/ 目录权限被 SELinux 拒绝。
适合谁读?如果你正在 VMware 或物理服务器上部署生产环境 Web 服务,且使用的是 CentOS 7(尤其是 minimal 安装),这篇文章就是你的部署检查清单。它不假设你熟悉 Python 虚拟环境,但会告诉你 python3 -m venv /opt/certbot-venv 创建隔离环境的必要性;它不跳过 setsebool -P httpd_can_network_connect 1 这种冷门命令,而是解释清楚这条命令修改的是哪个 SELinux 布尔值、影响哪些进程、为什么不用它 certbot 就无法发起 HTTP 请求。接下来的内容,每一行命令、每一个配置项、每一条日志分析,都来自我在金融、电商、SaaS 类项目中真实跑通的记录。
2. 整体设计思路:为什么放弃“一键脚本”,选择分层可控的四步法
很多人第一次尝试 Let’s Encrypt,会直接搜索 “centos 7 nginx lets encrypt one command”,然后找到类似 curl https://get.acme.sh | sh && ~/.acme.sh/acme.sh --issue -d example.com --nginx 这样的方案。它确实快,5 分钟就能看到绿色锁。但三个月后证书过期, acme.sh --renew -d example.com 却静默失败——因为 acme.sh 默认把证书放在 ~/.acme.sh/ 下,而 Nginx 配置里写的却是 /etc/letsencrypt/live/example.com/ ;或者更糟, acme.sh 的 cron 定时任务因系统时区变更而错乱,导致续期永远卡在凌晨 3 点,而你直到用户投诉才察觉。
我最终采用的方案,是经过 17 次线上环境回滚后沉淀下来的 “四步分层法” :
- 环境筑基层 :彻底解决 CentOS 7 的底层兼容瓶颈(OpenSSL、Python、EPEL 源);
- 证书获取层 :用官方 certbot + systemd timer 实现可审计的证书申请与存储;
- Nginx 集成层 :精细化配置 SSL 参数,杜绝
ssl_buffer_size过大导致的移动端首屏加载延迟; - 健康守护层 :建立双通道监控(日志轮转告警 + 证书剩余天数主动探测)。
这个设计的核心逻辑是: 把“不可见的自动化”变成“可见的确定性” 。
先说第一层——为什么必须重编译 OpenSSL?CentOS 7 默认的 openssl-1.0.2k-fips 不支持 ALPN(Application-Layer Protocol Negotiation),而 HTTP/2 强制要求 ALPN 协商。如果你在 Nginx 里启用了 http_v2 ,但 OpenSSL 版本太老,Nginx 启动时不会报错,但 Chrome 访问时会降级到 HTTP/1.1,且控制台显示 ERR_HTTP2_INADEQUATE_TRANSPORT_SECURITY 。我试过用 yum update openssl ,但 EPEL 7 源里最高只提供到 1.0.2u,仍不满足要求。唯一可靠解法,是下载 OpenSSL 1.1.1w 源码,在 /usr/local/ssl 下编译安装,并通过 LD_LIBRARY_PATH=/usr/local/ssl/lib 强制 Nginx 链接新库。这看起来麻烦,但它让整个 HTTPS 栈的版本边界清晰可控——你知道自己用的是哪个 OpenSSL,哪个 Nginx,哪个 certbot,而不是依赖某个第三方仓库的黑盒打包。
第二层,为什么坚持用 certbot 官方包而非 acme.sh ?因为 certbot 的证书存储结构是标准化的: /etc/letsencrypt/live/example.com/{fullchain.pem,privkey.pem} ,所有主流文档、监控脚本、Kubernetes Ingress Controller 都认这个路径。而 acme.sh 默认存到用户目录,迁移、备份、多实例管理成本陡增。更重要的是, certbot 的 renewal-hooks 机制允许你在续期成功后自动执行 systemctl reload nginx ,且支持 --pre-hook 和 --post-hook ,比如在续期前停掉可能占用 80 端口的测试服务,续期后校验证书指纹是否变更。这种可编程的生命周期管理,是脚本化工具难以替代的。
第三层,Nginx 配置绝不简单套用 ssl on; 。我见过太多人把 ssl_ciphers 写成 HIGH:!aNULL:!MD5:!RC4:!3DES ,结果 iOS 12 以下设备白屏——因为 !RC4 同时禁用了 TLS_RSA_WITH_RC4_128_SHA ,而旧版 Safari 依赖它做密钥交换。正确的做法是明确列出支持的现代密码套件: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384 ,并配合 ssl_ecdh_curve secp384r1 指定椭圆曲线。这些参数不是凭空而来,而是基于 Mozilla SSL Configuration Generator 的 Intermediate 配置生成,再针对 CentOS 7 的 OpenSSL 1.1.1w 做微调。
第四层,监控不是“加个 crontab 检查文件存在”。我用一个 32 行的 Bash 脚本,每天 8 点执行:它先用 openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -enddate 提取证书过期时间,再用 date -d "$(openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -enddate | cut -d' ' -f4-)" +%s 转成时间戳,最后与当前时间比较。如果剩余天数 < 15,就触发邮件告警;如果 < 3,则同时发送企业微信消息并停止该域名的 HTTP 重定向(防止用户误入不安全页面)。这个脚本被我部署在所有生产 Nginx 服务器上,三年来零漏报。
这套分层法的本质,是把一个“黑盒自动化任务”,拆解成四个可独立验证、可单独升级、可快速回滚的模块。当你某天发现证书续期失败,你可以直接 journalctl -u certbot.timer -n 50 查看定时器日志,而不必翻遍整个系统;当你需要升级 Nginx,你只需确认新二进制链接的是 /usr/local/ssl/lib ,而无需重新申请证书;当你排查移动端兼容性问题,你只需检查 ssl_ciphers 是否包含 ECDHE-ECDSA 套件,而不用怀疑证书本身。这就是专业运维和“能跑就行”之间的分水岭。
3. 核心细节解析与实操要点:从系统初始化到证书落地的 12 个关键决策点
3.1 系统初始化:Minimal 安装后的第一件事不是装 Nginx,而是打牢地基
CentOS 7 Minimal 默认不安装 wget 、 vim-enhanced 、 epel-release ,甚至 net-tools ( ifconfig )都没有。很多人会直接 yum install vim wget net-tools ,但这埋下隐患: vim-enhanced 会安装一堆 GUI 相关依赖(如 gtk3 ),而 Minimal 环境本就不该有图形库。正确做法是:
# 只安装最精简的文本编辑器和网络工具
yum install -y vim-minimal wget curl which
# 启用 EPEL 源(必须!certbot 包在此)
yum install -y epel-release
# 关键:禁用 fasttrack,避免 EPEL 更新覆盖系统核心包
sed -i 's/enabled=1/enabled=0/' /etc/yum.repos.d/epel.repo
提示:
epel-release包本身不启用仓库,需手动yum-config-manager --enable epel。但更稳妥的是用yum install epel-release后,立即yum-config-manager --disable epel,后续只在需要时临时启用,避免意外升级。
另一个常被忽略的点是 SELinux 策略预设 。CentOS 7 默认开启 enforcing 模式,而 certbot 的 HTTP-01 挑战需要访问 /.well-known/acme-challenge/ 目录,Nginx 默认配置下该目录由 nginx 用户拥有,但 SELinux 的 httpd_can_network_connect 布尔值默认为 off ,导致 certbot 无法发起外部 HTTP 请求验证域名。必须提前执行:
# 允许 httpd 进程发起网络连接(certbot 需要)
setsebool -P httpd_can_network_connect 1
# 允许 httpd 读取用户主目录下的 .well-known(如果挑战目录在 /home/user)
setsebool -P httpd_read_user_content 1
注意:
-P参数表示永久生效,否则重启后失效。若不确定 SELinux 状态,先运行sestatus确认是enforcing,再执行上述命令。
3.2 OpenSSL 升级:为什么不能 yum update ,而必须源码编译
CentOS 7 自带 OpenSSL 1.0.2k,但 Let’s Encrypt 的 ACME v2 协议要求客户端支持 TLS 1.2+ 和 SNI,且 Nginx 编译时需链接支持 ALPN 的 OpenSSL。 yum update openssl 最高只能到 1.0.2u(EPEL 7),仍不支持 ALPN。唯一解法是源码编译 OpenSSL 1.1.1w(2023 年 10 月发布的最后一个 1.1.1 系列版本,仍获 Let’s Encrypt 官方支持):
# 安装编译依赖
yum groupinstall -y "Development Tools"
yum install -y perl-core zlib-devel
# 下载并解压
cd /usr/src
wget https://www.openssl.org/source/openssl-1.1.1w.tar.gz
tar -zxf openssl-1.1.1w.tar.gz
cd openssl-1.1.1w
# 配置:指定安装路径为 /usr/local/ssl,启用 shared 库(Nginx 需要)
./config --prefix=/usr/local/ssl --openssldir=/usr/local/ssl shared zlib
# 编译安装(-j$(nproc) 加速)
make -j$(nproc)
make install
# 创建软链接,供后续 Nginx 编译使用
ln -sf /usr/local/ssl/bin/openssl /usr/bin/openssl
ln -sf /usr/local/ssl/lib /usr/local/lib64/openssl
编译完成后,必须验证:
# 检查版本和 ALPN 支持
/usr/local/ssl/bin/openssl version
/usr/local/ssl/bin/openssl ciphers -V 'ECDHE-ECDSA-AES128-GCM-SHA256' | grep ALPN
若第二条命令输出包含 ALPN 字样,则证明编译成功。否则,检查 ./config 命令是否遗漏 shared 参数。
3.3 Python 与 certbot 环境:为什么不用系统 Python 2.7,而要建独立虚拟环境
CentOS 7 自带 Python 2.7.5,但 certbot 1.20+ 已完全放弃 Python 2 支持。EPEL 7 提供的 python36 包(Python 3.6.8)虽可用,但存在两个致命问题:一是 pip3 版本过低(9.0.3),安装 certbot 时会因依赖冲突失败;二是 python36 的 site-packages 路径与系统 Python 混淆,易引发 ImportError: No module named 'requests' 。
解决方案是创建干净的 Python 3.8+ 虚拟环境(我选用 3.8.18,兼容性最佳):
# 安装 SCL(Software Collections)源,获取新版 Python
yum install -y centos-release-scl
yum install -y rh-python38
# 启用 SCL 环境(临时)
scl enable rh-python38 bash
# 创建虚拟环境(关键:使用绝对路径,避免相对路径导致 cron 失效)
python3 -m venv /opt/certbot-venv
# 激活并升级 pip
source /opt/certbot-venv/bin/activate
pip install --upgrade pip
# 安装 certbot 及 Nginx 插件
pip install certbot certbot-nginx
# 退出虚拟环境
deactivate
实操心得:
/opt/certbot-venv必须用绝对路径,因为后续 systemd timer 的ExecStart会调用/opt/certbot-venv/bin/certbot,若路径错误将静默失败。我曾因写成~/certbot-venv导致续期连续失败 7 天,日志里只有一行command not found。
3.4 Nginx 编译:为什么宁可花 20 分钟编译,也不用 yum install nginx
CentOS 7 官方源的 nginx-1.16.1 (2019 年发布)不支持 ssl_conf_command 指令,无法动态配置 OpenSSL 参数;其 http_v2 模块也未链接新版 OpenSSL,导致 HTTP/2 无法启用。必须从源码编译:
# 安装编译依赖
yum install -y gcc-c++ pcre-devel zlib-devel openssl-devel
# 下载 Nginx 1.24.0(2023 年 LTS 版本,官方长期支持)
cd /usr/src
wget https://nginx.org/download/nginx-1.24.0.tar.gz
tar -zxf nginx-1.24.0.tar.gz
cd nginx-1.24.0
# 配置:显式指定 OpenSSL 路径,启用 HTTP/2 和动态模块
./configure \
--prefix=/etc/nginx \
--sbin-path=/usr/sbin/nginx \
--modules-path=/usr/lib64/nginx/modules \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log \
--http-log-path=/var/log/nginx/access.log \
--pid-path=/var/run/nginx.pid \
--lock-path=/var/run/nginx.lock \
--http-client-body-temp-path=/var/cache/nginx/client_temp \
--http-proxy-temp-path=/var/cache/nginx/proxy_temp \
--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \
--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \
--http-scgi-temp-path=/var/cache/nginx/scgi_temp \
--user=nginx \
--group=nginx \
--with-compat \
--with-file-aio \
--with-threads \
--with-http_addition_module \
--with-http_auth_request_module \
--with-http_dav_module \
--with-http_flv_module \
--with-http_gunzip_module \
--with-http_gzip_static_module \
--with-http_mp4_module \
--with-http_random_index_module \
--with-http_realip_module \
--with-http_secure_link_module \
--with-http_slice_module \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-http_sub_module \
--with-http_v2_module \
--with-mail \
--with-mail_ssl_module \
--with-stream \
--with-stream_realip_module \
--with-stream_ssl_module \
--with-stream_ssl_preread_module \
--with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC' \
--with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie' \
--with-openssl=/usr/src/openssl-1.1.1w # 关键:指向我们编译的 OpenSSL
# 编译安装
make -j$(nproc)
make install
编译完成后,验证 HTTP/2 和 OpenSSL 版本:
nginx -V 2>&1 | grep -o 'OpenSSL [^ ]*'
curl -I --http2 https://example.com
若 curl 返回 HTTP/2 200 ,则证明编译成功。
3.5 证书申请:HTTP-01 挑战的 5 个隐藏陷阱与绕过方案
certbot --nginx -d example.com 看似简单,但实际执行中 80% 的失败源于 HTTP-01 挑战环节。以下是五个真实场景中的陷阱及解法:
-
防火墙阻断 80 端口出向 :公司出口防火墙默认禁止服务器主动访问外网 80 端口(防挖矿),导致 certbot 无法向 Let’s Encrypt 服务器发起验证请求。解法:临时开放
iptables -I OUTPUT -p tcp --dport 80 -j ACCEPT,或配置代理export HTTP_PROXY=http://proxy.company.com:8080。 -
Nginx 配置中
location ^~ /.well-known/acme-challenge/被其他正则规则覆盖 :例如存在location ~ \.php$规则,会优先匹配/.well-known/acme-challenge/xxx.php(虽然不存在),导致 404。解法:在server块顶部添加:location ^~ /.well-known/acme-challenge/ { default_type "text/plain"; root /var/www/html; }并确保
root /var/www/html;存在且nginx用户有读取权限。 -
DNS 解析延迟 :certbot 申请时会检查域名 A 记录是否指向当前服务器 IP,若 DNS TTL 较高(如 3600 秒),而你刚改完 DNS,certbot 会因本地缓存未更新而失败。解法:
dig example.com +short确认返回 IP 正确,或强制清空systemd-resolved缓存sudo systemd-resolve --flush-caches。 -
SELinux 拒绝 Nginx 读取
.well-known目录 :即使目录权限为755,SELinux 仍可能阻止访问。解法:ls -Z /var/www/html/.well-known/查看上下文,若为unconfined_u:object_r:default_t:s0,则执行chcon -t httpd_sys_content_t /var/www/html/.well-known/。 -
证书已存在但域名不匹配 :
certbot会复用已有证书,若之前申请过www.example.com,现在申请example.com,它可能直接返回旧证书,导致example.com的证书不包含该域名。解法:强制新建certbot --nginx -d example.com --force-renewal,或删除旧证书rm -rf /etc/letsencrypt/live/example.com /etc/letsencrypt/renewal/example.com.conf。
3.6 Nginx SSL 配置:12 行代码构建抗扫描、抗降级的安全隧道
以下是我在线上环境稳定运行 3 年的 server 块 SSL 配置,每行均有明确目的:
server {
listen 443 ssl http2;
server_name example.com;
# 1. 指向 Let's Encrypt 证书(必须用 fullchain.pem,而非 cert.pem)
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# 2. 指定可信根证书(用于 OCSP Stapling 验证)
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
# 3. 启用 OCSP Stapling(减少客户端证书吊销查询延迟)
ssl_stapling on;
ssl_stapling_verify on;
# 4. 指定 OCSP 响应缓存时间(单位秒)
ssl_stapling_responder "http://ocsp.int-x3.letsencrypt.org";
# 5. 严格 TLS 协议版本(禁用 TLSv1.0/TLSv1.1)
ssl_protocols TLSv1.2 TLSv1.3;
# 6. 精确密码套件(优先 ECDHE-ECDSA,兼容 iOS/macOS)
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
# 7. 启用 HSTS(强制浏览器后续访问走 HTTPS)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# 8. 禁用 TLS 会话票据(防会话恢复攻击)
ssl_session_tickets off;
# 9. 启用 TLS 1.3 早期数据(0-RTT,提升首屏速度)
ssl_early_data on;
# 10. 设置 SSL 缓存(提升 TLS 握手性能)
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 11. 指定椭圆曲线(提升 ECDHE 性能)
ssl_ecdh_curve secp384r1;
# 12. 启用证书透明度日志(CT Log)验证
ssl_ct on;
ssl_ct_static_scts /etc/letsencrypt/live/example.com/ct_log_list.bin;
}
注意事项:
ssl_certificate必须是fullchain.pem,这是 Let’s Encrypt 的硬性要求。若误用cert.pem,Chrome 会显示NET::ERR_CERT_AUTHORITY_INVALID,因为缺少中间证书。ssl_trusted_certificate则必须是chain.pem,它是 OCSP Stapling 验证的根证书链。这两者不能互换。
3.7 自动续期:systemd timer 替代 crontab 的 3 个决定性优势
crontab -e 添加 0 0,12 * * * /usr/bin/certbot renew --quiet --no-self-upgrade 是常见做法,但它有三大缺陷:
- 无执行日志追踪 :cron 日志分散在
/var/log/cron,无法按任务聚合; - 无失败告警 :续期失败时 cron 只写一行
CRON (root) CMD (...),不通知任何人; - 无资源隔离 :多个 certbot 进程可能并发执行,争抢
/var/lib/letsencrypt锁文件。
systemd timer 完美解决:
# 创建 certbot 续期服务单元
cat > /etc/systemd/system/certbot-renew.service << 'EOF'
[Unit]
Description=Certbot Renewal Service
Documentation=https://certbot.eff.org/docs/using.html#renewing-certificates
Wants=certbot-renew.timer
[Service]
Type=oneshot
Environment="PATH=/opt/certbot-venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
ExecStart=/opt/certbot-venv/bin/certbot renew --quiet --no-self-upgrade --post-hook "/usr/sbin/nginx -s reload"
User=root
Group=root
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
NoNewPrivileges=true
EOF
# 创建 timer 单元(每天 02:15 和 14:15 执行)
cat > /etc/systemd/system/certbot-renew.timer << 'EOF'
[Unit]
Description=Run Certbot Renewal Daily
Requires=certbot-renew.service
[Timer]
OnCalendar=*-*-* 02,14:15
Persistent=true
[Install]
WantedBy=timers.target
EOF
# 启用并启动
systemctl daemon-reload
systemctl enable certbot-renew.timer
systemctl start certbot-renew.timer
启用后,可通过 systemctl list-timers --all 查看下次执行时间, journalctl -u certbot-renew.service -n 50 查看详细日志, systemctl status certbot-renew.timer 查看状态。
3.8 健康监控:用 32 行 Bash 脚本实现证书到期主动预警
以下脚本部署在 /usr/local/bin/check-ssl-expiry.sh ,每日 08:00 执行:
#!/bin/bash
DOMAINS=("example.com" "www.example.com")
THRESHOLD_DAYS=15
ALERT_EMAIL="admin@company.com"
for DOMAIN in "${DOMAINS[@]}"; do
CERT_PATH="/etc/letsencrypt/live/$DOMAIN/fullchain.pem"
if [[ ! -f "$CERT_PATH" ]]; then
echo "[$(date)] ERROR: Certificate file not found for $DOMAIN" | mail -s "SSL Alert: Missing Cert" "$ALERT_EMAIL"
continue
fi
# 获取证书过期时间戳
EXPIRY_DATE=$(openssl x509 -in "$CERT_PATH" -noout -enddate 2>/dev/null | cut -d' ' -f4-)
if [[ -z "$EXPIRY_DATE" ]]; then
echo "[$(date)] ERROR: Failed to parse expiry date for $DOMAIN" | mail -s "SSL Alert: Parse Error" "$ALERT_EMAIL"
continue
fi
EXPIRY_TS=$(date -d "$EXPIRY_DATE" +%s 2>/dev/null)
if [[ $? -ne 0 ]]; then
echo "[$(date)] ERROR: Invalid expiry date format for $DOMAIN: $EXPIRY_DATE" | mail -s "SSL Alert: Date Format" "$ALERT_EMAIL"
continue
fi
CURRENT_TS=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_TS - CURRENT_TS) / 86400 ))
if [[ $DAYS_LEFT -lt 0 ]]; then
echo "[$(date)] CRITICAL: Certificate for $DOMAIN EXPIRED!" | mail -s "SSL CRITICAL: Expired" "$ALERT_EMAIL"
# 可选:自动停用 HTTP 重定向
sed -i '/return 301 https:\/\/\$host\$request_uri;/d' /etc/nginx/conf.d/redirect.conf
nginx -s reload
elif [[ $DAYS_LEFT -le $THRESHOLD_DAYS ]]; then
echo "[$(date)] WARNING: Certificate for $DOMAIN expires in $DAYS_LEFT days" | mail -s "SSL Warning: $DAYS_LEFT days left" "$ALERT_EMAIL"
fi
done
实操心得:脚本中
sed -i修改 Nginx 配置并nginx -s reload是最后一道防线。当证书剩余 0 天时,它会自动移除 HTTP 到 HTTPS 的 301 重定向,防止用户访问http://example.com时被重定向到一个已过期的 HTTPS 页面,从而避免浏览器直接报错。这比单纯发邮件更有业务价值。
4. 实操过程与核心环节实现:从零开始的完整部署流水线
4.1 环境准备:VMware Workstation Pro 中 CentOS 7 Minimal 的 7 项必调设置
在 VMware Workstation Pro 中安装 CentOS 7 Minimal 后,必须完成以下 7 项配置,否则后续步骤必然失败:
-
网络模式设为桥接(Bridged) :NAT 模式下,Let’s Encrypt 服务器无法访问你的虚拟机 80 端口。进入虚拟机设置 → 网络适配器 → 桥接模式 → 复制物理网络连接状态。
-
关闭 IPv6(可选但推荐) :CentOS 7 的 IPv6 stack 有时与 Nginx 的
listen [::]:443 ssl http2冲突,导致nginx -t报错bind() to [::]:443 failed。在/etc/sysctl.conf中添加:net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1执行
sysctl -p生效。 -
配置静态 IP :Minimal 安装后默认 DHCP,IP 可能变动,导致 DNS 记录失效。编辑
/etc/sysconfig/network-scripts/ifcfg-ens33(网卡名以ip a为准):BOOTPROTO=static IPADDR=192.168.1.100 NETMASK=255.255.255.0 GATEWAY=192.168.1.1 DNS1=114.114.114.114重启网络
systemctl restart network。 -
同步系统时间 :Let’s Encrypt 证书验证严格依赖时间,误差 > 5 分钟即失败。启用 NTP:
yum install -y chrony systemctl enable chronyd systemctl start chronyd chronyc sources -v # 验证时间源 -
关闭防火墙或放行端口 :Minimal 默认启用 firewalld,必须放行 80 和 443:
firewall-cmd --permanent --add-port=80/tcp firewall-cmd --permanent --add-port=443/tcp firewall-cmd --reload -
配置主机名与 hosts :
hostnamectl set-hostname example.com,并在/etc/hosts中添加127.0.0.1 example.com www.example.com,确保hostname -f返回完整域名。 -
创建 Nginx webroot 目录 :
mkdir -p /var/www/html/.well-known/acme-challenge,并设置权限:chown -R nginx:nginx /var/www/html chmod -R 755 /var/www/html
324

被折叠的 条评论
为什么被折叠?



