从域名到一行 nginx 配置:一次站点 HTTPS 证书更换与排障复盘

场景:接到一句「把某某站点的证书换一下」。手里只有一个域名,其余一无所知。

本文记录从「域名指向哪台机器」到「配置文件到底在哪个目录」的完整定位过程,以及证书更换、校验、回滚的实操细节。文中域名、IP、容器名均已脱敏。

一、起点:只知道一个域名

任务描述只有一句话:coc.example.com 的 HTTPS 证书要换。

第一件事不是登录服务器,而是确认这个域名到底落在哪台机器上:

bash 复制代码
ping coc.example.com

返回 10.10.13.101。这是个内网地址,说明解析是在内网完成的(内网 DNS 或 hosts),公网查不到。顺手确认一下解析来源,能避免后面误判:

bash 复制代码
cat /etc/hosts | grep example
cat /etc/resolv.conf

拿到 IP 后通过 SSH 登录,先用一条命令确认系统环境:

bash 复制代码
cat /etc/os-release

结果是 CentOS Linux 7 (Core) 。这里插一句题外话:CentOS 7 已在 2024 年 6 月 EOL,官方源早已下线,yum install 会直接 404,需要换到 vault.centos.org 或国内镜像的 vault 源。如果这台机器还对外提供服务,建议排进迁移计划(Rocky Linux 9 / AlmaLinux 9)。不过对本次任务而言,它不影响操作,继续。

二、第一个弯路:按传统思路找 nginx 配置

按照老习惯,直接去默认路径翻:

bash 复制代码
grep -rIl "coc.example.com" /etc/nginx /etc/httpd /usr/local/nginx 2>/dev/null

一无所获。

这不是配置写得隐蔽,而是思路错了。再补两条命令看进程和端口:

bash 复制代码
ss -lntp | grep -E ':80|:443'
ps -ef | grep -Ei 'nginx|httpd|java' | grep -v grep

输出很有意思:

复制代码
LISTEN  0  4096  *:8082  *:*  users:(("docker-proxy",pid=104798,fd=4))
LISTEN  0  4096  *:8083  *:*  users:(("docker-proxy",pid=97595,fd=4))

root  97633  ...  java -Xms512m -Xmx1024m -jar /app.jar --spring.config.location=/conf/application.yml
root 104836  ...  nginx: master process nginx -g daemon off;
101  104891  ...  nginx: worker process

三个关键信号:

  1. 端口监听者是 docker-proxy,不是 nginx 本身;
  2. nginx 的启动命令是 nginx -g daemon off;------这是容器里跑前台进程的标准写法;
  3. Java 进程的工作目录是 /app.jar,配置从 /conf/application.yml 读------典型的容器挂载路径。

结论:所有服务都跑在 Docker 里,宿主机上当然搜不到配置。 这大概是容器化环境排障时最容易踩的第一个坑:/etc/nginx 目录可能压根不存在,或者存在但只是个空壳。

三、顺着容器找到真正的挂载目录

先看全貌:

bash 复制代码
docker ps --format 'table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Ports}}'
复制代码
CONTAINER ID   IMAGE          NAMES        PORTS
087d3b64c789   httpapi:v1.4   api-service  0.0.0.0:8083->8083/tcp
70e3de68ba9a   vue:latest     web-front    80/tcp, 0.0.0.0:8082->8082/tcp
ac04623a603f   contract:v1    contract     0.0.0.0:5000->5000/tcp
71a8b01c767d   redis          redis        6379/tcp

对应关系一下清楚了:

  • web-front:nginx + 前端静态资源,宿主 8082 → 容器 8082;
  • api-service:Spring Boot 后端,8083;
  • contract:另一个业务应用,5000;
  • redis:缓存。

接下来是最关键的一步------看容器挂了哪些宿主机目录:

bash 复制代码
docker inspect web-front -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
复制代码
/root/config/nginx -> /etc/nginx

答案出来了。宿主机 /root/config/nginx 整个目录挂到容器的 /etc/nginx,所以真正的配置文件在宿主机上,容器重启也不会丢。

这一步值得单独强调:判断配置是「挂载」还是「打进镜像」,决定了后续所有操作的姿势 。挂载的直接改宿主机文件;打进镜像的,改完容器一重建就白改,必须 docker commit 或改 Dockerfile 重建。docker inspect 的 Mounts 字段是最快的判断依据。

四、在容器内确认配置生效情况

找到挂载点之后,再验证一下域名到底写在哪个文件里:

bash 复制代码
docker exec web-front sh -c 'grep -rl "coc.example.com" /etc/nginx /usr/share/nginx 2>/dev/null'
复制代码
/etc/nginx/conf.d/default.conf          # 配置本体
/usr/share/nginx/html/static/js/*.js    # 前端产物里硬编码的接口地址

配置文件锁定为 conf.d/default.conf,宿主机路径是 /root/config/nginx/conf.d/default.conf。

比起 cat,更推荐用 nginx -T------它会把主配置和所有 include 的文件全部展开打印,避免漏看:

bash 复制代码
docker exec web-front nginx -T | grep -n -B10 -A40 "coc.example.com"

五、读懂这份配置:它比看起来更「朴素」

配置核心部分如下(省略注释):

nginx 复制代码
server {
    listen       8082 ssl;
    server_name  coc.example.com;

    ssl_certificate     /etc/nginx/example.com_cert_chain.pem;
    ssl_certificate_key /etc/nginx/example.com_key.key;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_dhparam /etc/nginx/dhparams.pem;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
}

几个值得记录的观察点:

1. 只有 8082 ssl,没有 80 端口。

也就是说站点必须用 https://coc.example.com:8082 访问,直接敲域名是打不开的。这是个非标准端口,用户每次都要带端口号,体验一般。要改成 443,不只要改配置里的 listen,还得改容器的端口映射并重建容器------reload 是不够的。

2. TLSv1.1 已被主流浏览器废弃。

等保测评或安全扫描大概率会报这一条,建议至少收紧到 TLSv1.2 TLSv1.3。

3. 全篇没有 proxy_pass。

这说明 nginx 只做静态托管,不代理后端。那么前端怎么调接口?答案在第四步 grep 到的 JS 文件里------接口地址是编译时写死在前端产物里的。这带来两个隐患:换 IP 要重新打包前端;浏览器直连后端端口会涉及跨域。

如果哪天要统一入口,可以加一段反代:

nginx 复制代码
location /api/ {
    proxy_pass http://10.10.13.101:8083/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

但注意:前端打包时的 baseURL 必须同步改成 /api,否则改了 nginx 也没用。这种「配置改一半」的情况,排查起来比不改还费劲。

4. 静态资源没有缓存策略。

js/css 没配 expires,每次刷新都回源。可以补一段,但要记得 index.html 不能缓存,否则发版后用户看不到新版本:

nginx 复制代码
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
    root /usr/share/nginx/html;
    expires 30d;
    add_header Cache-Control "public";
}

六、换证书:四步,不重启容器

目标很明确:替换两个挂载文件,然后 reload。不需要重启容器,业务零中断。

1. 备份

bash 复制代码
cd /root/config/nginx
mkdir -p bak_$(date +%Y%m%d)
cp example.com_cert_chain.pem example.com_key.key bak_$(date +%Y%m%d)/

备份这一步别省。证书更换失败会导致整站 HTTPS 不可用,有备份就能在 30 秒内回滚。

2. 校验新证书

新证书必须是 PEM 格式 ,且证书在前、中间证书在后。顺序错了,部分客户端会报证书链不完整。

bash 复制代码
# 看有效期、域名、签发者
openssl x509 -in new.pem -noout -subject -issuer -dates

# 证书链里应包含 2~3 张证书
grep -c "BEGIN CERTIFICATE" new.pem

# 私钥与证书是否匹配:两条命令输出的 MD5 必须一致
openssl x509 -in new.pem -noout -modulus | md5sum
openssl rsa  -in new.key -noout -modulus | md5sum

如果拿到的文件是 .pfx / .p12,需要先转换:

bash 复制代码
openssl pkcs12 -in cert.pfx -clcerts -nokeys -out new.pem
openssl pkcs12 -in cert.pfx -nocerts -nodes -out new.key

注意这样只导出了叶子证书,还得把中间证书 cat 追加到 new.pem 末尾。

3. 覆盖文件并设置权限

bash 复制代码
cp new.pem example.com_cert_chain.pem
cp new.key example.com_key.key

chmod 600 example.com_key.key
chmod 644 example.com_cert_chain.pem
chown root:root example.com_*

私钥权限给 600 是底线,避免被同机其他账号读取。

4. 校验 + 热加载

bash 复制代码
docker exec web-front nginx -t
docker exec web-front nginx -s reload

nginx -t 会同时验证证书和私钥是否匹配,如果报 SSL_CTX_use_PrivateKey,就是私钥不配套,直接回滚。

七、验证:别只看浏览器锁标

最可靠的验证是直接问 TLS 握手要证书:

bash 复制代码
echo | openssl s_client -connect 127.0.0.1:8082 -servername coc.example.com 2>/dev/null \
  | openssl x509 -noout -dates -subject -issuer

本次输出:

复制代码
notBefore=Oct  9 00:00:00 2026 GMT
notAfter =Apr 25 23:59:59 2027 GMT
subject  = ... O = 某建筑企业, CN = *.example.com
issuer   = C = US, O = "DigiCert, Inc.", CN = DigiCert Basic OV G2 TLS CN RSA4096 SHA256 2022 CA1

三个信息同时确认了结果:

  1. notBefore 是新的起始日期,说明确实换成了新证书;
  2. 是通配符证书 *.example.com,以后新增子域不用重新签发;
  3. 能拿到 issuer,说明中间证书随服务下发正常。

再补一条命令确认链完整:

bash 复制代码
echo | openssl s_client -connect 127.0.0.1:8082 -servername coc.example.com -showcerts 2>/dev/null \
  | grep -c "BEGIN CERTIFICATE"

返回 2 或 3 才是对的。返回 1 说明只下发了叶子证书,PC 浏览器可能没事,但部分安卓 WebView 和老客户端会报错------这种问题上线后才暴露,很难查。

回滚

bash 复制代码
cp /root/config/nginx/bak_20261019/* /root/config/nginx/
docker exec web-front nginx -s reload

八、复盘:这次踩到的坑和沉淀下来的清单

三个认知纠正

其一,容器环境里,宿主机上的「默认路径」大概率是空的。

/etc/nginx 存在不代表配置在里面。看到 docker-proxy 占端口、进程带 daemon off、路径是 /app.jar 这类特征,就该立刻切到 docker 视角。

其二,docker inspect 的 Mounts 是排障的入口,不是可选项。

它同时回答了两个问题:配置文件在哪、改了会不会丢。

其三,nginx -T 比 cat 可靠。

配置分散在 nginx.conf、conf.d/*.conf、sites-enabled/* 多个文件里时,只有 -T 能保证你看到的是「实际生效的组合」。

一份可复用的定位流程

bash 复制代码
# 1. 确认系统
cat /etc/os-release

# 2. 谁在占端口
ss -lntp | grep -E ':80|:443'
ps -ef | grep -Ei 'nginx|httpd|java' | grep -v grep

# 3. 列出容器与端口映射
docker ps --format 'table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.Ports}}'

# 4. 看挂载
docker inspect <容器名> -f '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'

# 5. 容器内搜域名
docker exec <容器名> sh -c 'grep -rl "域名" /etc/nginx'

# 6. 打印生效配置
docker exec <容器名> nginx -T | grep -n -B10 -A40 "域名"

六条命令,从「只有一个域名」到「精确锁定一行 server_name」,通常五分钟内能走完。

顺手发现的待办

  • CentOS 7 已 EOL,需排迁移计划;
  • TLSv1.1 建议移除;
  • 站点跑在 8082 非标端口,可评估是否收敛到 443;
  • 前端接口地址硬编码,建议后续改为相对路径 + nginx 反代;
  • 静态资源缺少缓存策略;
  • 证书 2027-04-25 到期,提前 30 天续期。

一次换证书的任务,最后变成了一次小规模架构梳理。运维工作里这种情况挺常见的------问题表面是「改个文件」,实际考验的是「你能不能先找到那个文件」 。而找到它的关键,往往不是更熟练的 grep,而是先问一句:这个进程,到底跑在哪儿?

相关推荐
云水初1 小时前
【agent篇】RAG Agent避坑指南(附高级RAG Agent汇总)
开发语言·笔记·python·学习·langchain·agent·rag
薛定猫AI2 小时前
【技术干货】多AI团队大模型协作编排与编程项目评测架构解析
python·机器学习·ai·大模型·技术实战
外收内放2 小时前
数据分析(图表信息详解)
python·学习·数据分析·matplotlib
1-1=02 小时前
Python3 开发环境
python
miofly2 小时前
GitHub 今日推荐|MOSS-Image:靠坐标框精确控制文字排版的图像生成模型
python·开源·github
Tiandaren2 小时前
么是agent、为什么要做agent、怎么做agent、怎么工程化
人工智能·python·深度学习·大模型
半摆烂日常3 小时前
手机表单的左滑操作:触摸事件的处理细节
大数据·开发语言·人工智能·智能手机·https·c#
梅梅绵绵冰3 小时前
Nginx反向代理
运维·nginx
张张123y3 小时前
后端第十一课:RAG 落地实战(从 0 到 1 接上私有知识库,并设计一个好用的 RAG)
开发语言·人工智能·python·机器学习·fastapi·rag