# 腾讯云服务器运行七牛图片域名 HTTPS 证书自动续期 > 文档日期:2026-09-30。操作示例:腾讯云 Linux 服务器、Ubuntu 24.04、DNSPod 中国站、七牛 Kodo 私有空间与 CDN 加速域名、Let's Encrypt、acme.sh 3.1.6。 > > 状态:2026-10-03 已完成 Let's Encrypt staging 与正式证书签发,正式证书的颁发者、有效期和 SAN 已由维护者提供的 OpenSSL 输出确认;七牛 hook 对两个图片域名返回部署成功,两个图片域名的网络检查均返回本次正式证书的颁发者、有效期,且指纹一致。服务器使用 1Panel/OpenResty,Atlas 生产、开发应用尚未部署。定时自动续期仍待验收;网站 HTTPS 接入留待应用部署时完成。现场进度见第 6.2 节、第 7 节、第 8.2 节、第 9.2 节和第 10.2 节。操作系统、DNS 托管商和实际域名需要按现场情况核对。 ## 1. 要完成的事情 Atlas 的生产、开发网站及两个环境的图片域名共用一张证书。七牛空间仍按环境隔离,由腾讯云服务器集中执行证书申请、续期和部署。 | 环境 | 七牛空间示例 | 下载域名示例 | 空间访问控制 | |---|---|---|---| | 开发联调 | `atlas-dev-assets` | `img-dev.atlas.joeyone.cn` | 私有 | | 生产 | `atlas-prod-assets` | `img.atlas.joeyone.cn` | 私有 | ### 1.1 四个域名与一张证书 统一把 Atlas 的域名放在 `atlas.joeyone.cn` 下: | 用途 | 完整域名 | DNSPod 主机记录(区域为 `joeyone.cn`) | 类型 | 目标 | |---|---|---|---|---| | 生产网站 | `atlas.joeyone.cn` | `atlas` | A | 生产腾讯云服务器 IP | | 开发网站 | `dev.atlas.joeyone.cn` | `dev.atlas` | A | 开发服务器 IP;同机部署时可与生产相同 | | 生产图片 | `img.atlas.joeyone.cn` | `img.atlas` | CNAME | 七牛生产 CDN 分配的目标 | | 开发图片 | `img-dev.atlas.joeyone.cn` | `img-dev.atlas` | CNAME | 七牛开发 CDN 分配的目标 | 申请一张 SAN 包含以下两项的证书: ```text atlas.joeyone.cn *.atlas.joeyone.cn ``` `atlas.joeyone.cn` 覆盖生产网站;`*.atlas.joeyone.cn` 覆盖 `dev`、`img`、`img-dev` 这一级子域名。避免使用 `img.dev.atlas.joeyone.cn`,因为它多了一层,不能被这张通配符证书覆盖。`*.joeyone.cn` 也不能覆盖 `img.atlas.joeyone.cn`。 这张证书只申请、续期一次,但有三个部署目标:腾讯云 Nginx、七牛生产图片域名、七牛开发图片域名。多个域名写入同一张证书不表示它们需要指向同一个服务器或空间。 ```text 腾讯云服务器上的证书维护任务 → 通过 DNSPod API 创建临时 TXT 记录 → Let's Encrypt 签发 atlas.joeyone.cn + *.atlas.joeyone.cn → acme.sh 上传到七牛并更新两个图片域名的证书绑定 → 导出证书,验证本机 Nginx 配置后重载 生产 / 开发网站 → 腾讯云 Nginx → 各环境应用 Ozon / 图片访问 → 七牛 CDN HTTPS 域名 → 各环境私有空间 ``` 图片下载流量直接经过七牛,不经过证书维护服务器。网站 A 记录指向腾讯云,图片 CNAME 指向七牛。本手册的 Nginx 自动部署示例假设两个网站均由维护服务器上的 Nginx 提供 HTTPS;如果开发网站在另一台机器,需要为那台机器另加安全分发和配置检查/重载步骤,集中签发不会自动部署到远端服务器。 ### 1.2 通配符 DNS 与其他系统 申请通配符证书不要求创建通配符 A 记录。主流程只创建上表四条明确记录,更容易检查各环境指向。 如果希望新建 Atlas 网站子域名默认指向同一服务器,可以另设主机记录 `*.atlas`、类型 A、值为服务器 IP。已有明确的 `img.atlas`、`img-dev.atlas` CNAME 仍优先匹配,不要将图片 CNAME 改成服务器 A 记录。`*.atlas` 也不能替代 `atlas` 本身的记录。[DNSPod 泛解析说明](https://cloud.tencent.com/document/product/302/9073) 其他系统继续使用各自的域名和证书,例如 `git.joeyone.cn`、`img.other-system.joeyone.cn`。新增 Atlas 证书不会修改它们的现有绑定;不需要为了 Atlas 更换全站 `*.joeyone.cn` 证书,也不需要修改主域名的 `*` 解析。 ### 两种有效期要分开理解 | 对象 | 用途 | 谁负责更新 | |---|---|---| | HTTPS 证书 | 证明网站/图片域名身份并加密连接 | acme.sh 自动续期并部署 | | 图片签名 URL 的 `e` / `token` | 授权下载某一张私有图片 | Atlas Worker 在发布时生成 | 七牛的“上传自有证书”只上传当前证书,不会自动向 Let's Encrypt 续期或替换域名绑定;自动化依赖本手册的签发、部署 hook 和定时任务。 更换 HTTPS 证书通常不改变图片 URL。自动换证书也不会延长已经到期的图片签名链接。 ## 2. 操作前准备 准备以下资源,不需要将真实密钥填进本文或 Git 仓库: - 一台长期运行、有公网出站能力的腾讯云 Linux 服务器;已有业务服务器也可运行这个小任务。 - 可以 SSH 登录,且当前管理用户具有 `sudo` 权限。 - 你拥有的 `joeyone.cn` 域名,以及上表四个未被其他用途占用的 Atlas 域名。 - 域名实际 DNS 托管商的 API 权限。域名在哪里买、服务器在哪家云、DNS 在哪家托管是三个独立问题。 - 两个七牛私有空间,以及开通七牛 CDN 的账号。 - 能接收故障通知的运维邮箱,以及可用的 SMTP 账号或其他现有告警渠道。 示例替换表: | 本文示例 | 替换为 | |---|---| | `joeyone.cn` | 你的主域名 | | `atlas.joeyone.cn` | 生产网站及证书主域名 | | `dev.atlas.joeyone.cn` | 开发网站 | | `*.atlas.joeyone.cn` | Atlas 一级子域名的通配符 SAN | | `img.atlas.joeyone.cn` | 生产图片域名 | | `img-dev.atlas.joeyone.cn` | 开发图片域名 | | `ops@joeyone.cn` | 能接收通知的邮箱 | | `atlas-prod-assets` / `atlas-dev-assets` | 你实际创建的空间 | | `OWNER_UIN` / `DNSPOD_DOMAIN_ID` | 腾讯云主账号 UIN / DNSPod 域名 ID | 确认实际 DNS 服务商: ```bash dig +short NS joeyone.cn ``` 主流程使用腾讯云 DNSPod 新版 API,即 `dns_tencent` 插件及 `Tencent_SecretId`、`Tencent_SecretKey`。如果 NS 和管理控制台不是 DNSPod,先阅读第 15 节,不要申请无关的腾讯云 DNS 密钥。 ## 3. 在七牛创建空间和 CDN 下载域名 ### 3.1 创建两个私有空间 在七牛控制台的对象存储中创建开发、生产空间: 1. 访问控制选 **私有**。 2. 为两环境分别记录空间名、区域、S3 空间名和 S3 endpoint。 3. 区域按应用访问和目标客户地区选择;Ozon 抓图需要额外验证境外可访问性。 4. 应用使用的存储凭据按环境授权,不在本地开发配置生产空间的读写凭据。 本文的证书自动化任务是集中运维任务,可以维护两个域名。它使用的七牛管理凭据不应下发给浏览器,也不应与本地开发的素材上传权限混为一谈。 ### 3.2 绑定「自定义 CDN 加速域名」 分别为两个空间添加域名: | 控制台项目 | 开发域名 | 生产域名 | |---|---|---| | 域名 | `img-dev.atlas.joeyone.cn` | `img.atlas.joeyone.cn` | | 域名类型 | 自定义 CDN 加速域名 | 自定义 CDN 加速域名 | | 源站 | 对应开发私有空间 | 对应生产私有空间 | | 使用场景 | 图片 / 小文件 | 图片 / 小文件 | | 覆盖范围 | 按实际访问地区选择 | 必须包含 Ozon 实际抓图所需地区并验收 | 本流程使用 CDN 域名。七牛官方 FAQ 明确区分:CDN 可以通过 API 更新证书,Kodo 自定义源站域名目前需要在控制台手动绑定证书。[七牛 SSL FAQ,Q15](https://developer.qiniu.com/fusion/kb/3905/ssl-certificate-faq?category=kb) 涉及中国大陆的域名接入,按七牛控制台要求完成 ICP 备案;不将选择境外节点视为本项目的备案替代方案。 首次创建时如果支持先以 HTTP 创建,可以先完成域名和 CNAME,稍后用正式证书开启 HTTPS。如果界面要求证书才能继续,可先完成第 4~7 节申请证书,再回来绑定。若已有可用 HTTPS 证书,保留原绑定,直到新证书准备好后替换。 ### 3.3 配置四个域名的 DNS 先按第 1.1 节为网站配置 `atlas`、`dev.atlas` 的 A 记录;如果服务器有 IPv6 并已提供相应服务,可按实际情况另配 AAAA。 为两个图片子域名添加以下解析,记录值分别从七牛域名配置页复制,不自行猜测: | 主机记录 | 类型 | 记录值 | 线路 | |---|---|---|---| | `img-dev.atlas` | CNAME | 七牛给开发域名提供的目标 | 默认 | | `img.atlas` | CNAME | 七牛给生产域名提供的目标 | 默认 | 同一个主机记录不要同时保留指向应用服务器的 A / AAAA 记录。若 DNS 服务商具有代理/CDN 开关,本手册路径使用 DNS 解析模式,避免引入第二层代理。 验证: ```bash dig +short A atlas.joeyone.cn dig +short A dev.atlas.joeyone.cn dig +short CNAME img-dev.atlas.joeyone.cn dig +short CNAME img.atlas.joeyone.cn ``` 本证书的两个 SAN 都使用 `_acme-challenge.atlas.joeyone.cn` 验证,在 `joeyone.cn` 区域内的主机记录是 `_acme-challenge.atlas`。同一记录下可能同时出现两个 TXT 值,这是正常的;由 acme.sh 自动创建、保留到验证完成后清理,不手工覆盖其中一个。它和网站 A、图片 CNAME 不冲突。不要提前给挑战记录添加会干扰验证的其他 CNAME。 ### 3.4 开启私有空间回源鉴权 在七牛 **CDN → 域名管理 → 对应域名 → 配置 → 访问控制 → 回源鉴权** 中开启。源站为七牛私有 bucket 时,按七牛内置私有空间模式配置,不需要搭建自己的鉴权服务器。[七牛回源鉴权说明](https://developer.qiniu.com/fusion/3930/back-to-the-source-authentication) 两个域名都要配置,并等待控制台显示配置生效。Ozon 发布链路使用七牛私有下载凭证 `e` / `token`: - 不额外开启一套要求 `sign` / `t` 的 CDN 时间戳防盗链;那是不同签名协议,现有 Worker 不会生成该凭证。 - 不设置仅允许 Atlas 网站 Referer 的白名单;平台抓图可能没有 Referer。 - 不用登录 Cookie、固定浏览器 UA 或未经验证的 Ozon IP 白名单作为抓图前提。 - CDN 缓存可以启用,但缓存命中也必须执行私有访问鉴权。第 11 节会验证这一点。 两种签名协议和互斥限制参见[七牛访问控制 API](https://developer.qiniu.com/fusion/13364/fusion-api-domain-config)。 ## 4. 腾讯云服务器准备 ### 4.1 确认系统与网络 SSH 登录后先检查: ```bash cat /etc/os-release timedatectl status ``` 主流程假设 Ubuntu 24.04 + systemd。若为 TencentOS Server、Rocky Linux 等,使用相应包管理器安装 Bash、Git、curl、OpenSSL、CA 根证书、Python 3、DNS 查询工具及 util-linux;其余 systemd 流程相同。不要为执行本手册重装正在运行的服务器。 DNS-01 验证本身不需要开放入站 80 / 443。网站仍需要 Nginx 的 443 入站及现有 HTTP 跳转规则;保留业务服务原有规则。证书任务需要以下出站能力: | 流量 | 目的 | |---|---| | TCP 443 | GitHub、Let's Encrypt、DNSPod API、七牛 API、两个图片域名 | | UDP / TCP 53 | DNS 查询和验证 | | 实际 SMTP 端口,例如 TCP 465 / 587 | 第 10 节的通知 | | 系统原有时间同步流量 | 正确验证签名和证书时间 | 腾讯云 CVM 检查实例关联的安全组及本机防火墙;轻量应用服务器检查其防火墙与实际出站限制。不覆盖现有规则、不放通全部端口。无公网出站的私网实例还需要 NAT 等出口。[腾讯云安全组概述](https://cloud.tencent.com/document/product/213/112610) ### 4.2 安装工具并创建维护账号 以下命令由具有 `sudo` 权限的管理用户执行: ```bash sudo apt-get update sudo apt-get install -y ca-certificates curl git openssl dnsutils python3 util-linux nano sudo useradd --system --create-home \ --home-dir /var/lib/atlas-cert \ --shell /bin/bash atlas-cert sudo chmod 700 /var/lib/atlas-cert sudo install -d -m 700 -o atlas-cert -g atlas-cert /var/lib/atlas-cert/src sudo -iu atlas-cert ``` `useradd` 只在首次执行;如果账号已经存在,检查其 home 和用途后复用,不重复创建。维护账号不需要 sudo 权限。接下来第 5~8 节在这个账号的 Bash 会话里执行。 ## 5. 创建 DNSPod 和七牛凭据 ### 5.1 腾讯云 DNSPod API 凭据 在腾讯云 **访问管理 CAM → 用户列表** 创建仅用于证书 DNS 验证的子用户,再为该子用户创建 API 密钥。保存 `SecretId` 和 `SecretKey`,不要使用主账号全权限密钥。[子账号 API 密钥管理](https://cloud.tencent.com/document/product/598/37140) acme.sh 3.1.6 的 `dns_tencent` 脚本调用以下四个 DNSPod 操作: | 操作 | 用途 | |---|---| | `DescribeRecordList` | 探测域名所在 DNS 区域 | | `DescribeRecordFilterList` | 查找本次验证创建的 TXT | | `CreateRecord` | 创建验证 TXT | | `DeleteRecord` | 清理验证 TXT | 参考实现:[dns_tencent 3.1.6](https://github.com/acmesh-official/acme.sh/blob/3.1.6/dnsapi/dns_tencent.sh)。 在 CAM 创建自定义策略并关联子用户。以下 JSON 中的 `OWNER_UIN` 必须换成资源所属主账号 UIN,`DNSPOD_DOMAIN_ID` 换成 DNSPod 控制台/API 获取的域名 ID,不是文字域名: ```json { "version": "2.0", "statement": [ { "effect": "allow", "action": [ "dnspod:DescribeRecordList", "dnspod:DescribeRecordFilterList", "dnspod:CreateRecord", "dnspod:DeleteRecord" ], "resource": ["qcs::dnspod::uin/OWNER_UIN:domain/DNSPOD_DOMAIN_ID"] } ] } ``` 本例授权 `joeyone.cn` 所在的 domain 资源。DNSPod 当前资源模型按域名授权,上述权限能修改该主域名内的记录,并非仅 `_acme-challenge.atlas` 的 TXT;更严格的隔离可另行采用挑战域委托。资源格式以[腾讯云 DNSPod 授权资源说明](https://cloud.tencent.com/document/product/302/105719)为准,首次验证失败应检查策略和资源归属,不直接改成全账号管理权限。 ### 5.2 七牛证书部署凭据 准备可访问目标 CDN 域名的七牛 AK / SK。证书部署需要上传证书及修改这两个 CDN 域名的 HTTPS 配置;单纯的 bucket 上传 token 或 S3 对象写入权限不能代替这些管理权限。 能使用子账号/策略时按当前七牛账号实际支持的权限限制授权范围,并通过首次部署验证。不要假设所有七牛 AK 都是 bucket 级凭据。若开发、生产域名属于不同七牛账号,本手册单组 AK / SK 无法同时更新两者,需分别部署各账号的任务和凭据。 ### 5.3 保存维护配置 在 `atlas-cert` 会话执行。先创建文件,再用编辑器填写,真实密钥不作为命令参数或终端历史输入: ```bash umask 077 test -e /var/lib/atlas-cert/secrets.env || \ install -m 600 /dev/null /var/lib/atlas-cert/secrets.env nano /var/lib/atlas-cert/secrets.env ``` 填写下面的内容,替换所有示例值: ```bash CERT_EMAIL='ops@joeyone.cn' CERT_MAIN_DOMAIN='atlas.joeyone.cn' CERT_WILDCARD_DOMAIN='*.atlas.joeyone.cn' Tencent_SecretId='替换为证书维护子账号的SecretId' Tencent_SecretKey='替换为证书维护子账号的SecretKey' QINIU_AK='替换为七牛证书部署AK' QINIU_SK='替换为七牛证书部署SK' QINIU_CDN_DOMAIN='img.atlas.joeyone.cn img-dev.atlas.joeyone.cn' QINIU_FORCE_HTTPS='true' ``` 这是 Bash 配置文件,键名区分大小写。`CERT_WILDCARD_DOMAIN` 必须保留 `*` 并加引号;后面的申请命令也始终给变量加引号,避免 shell 展开。`QINIU_CDN_DOMAIN` 只填两个实际图片域名,用空格分隔,不带 `https://`、路径、通配符或前导点,也不填两个网站域名。 加载配置: ```bash set -a . /var/lib/atlas-cert/secrets.env set +a ``` acme.sh 会在自己的配置目录缓存 DNS/七牛凭据,因此 `secrets.env` 和 `.acme.sh` 整个目录都必须按秘密文件管理;仅删除环境文件并不能撤销缓存凭据。不要执行会完整输出配置的 `--info`,不要启用 `set -x`、不安全调试输出或把原始日志发到公共渠道。 ## 6. 安装 acme.sh 并先做 DNS 验证演练 ### 6.1 安装固定版本 继续在 `atlas-cert` 会话执行: ```bash git clone --depth 1 --branch 3.1.6 \ https://github.com/acmesh-official/acme.sh.git \ /var/lib/atlas-cert/src/acme.sh cd /var/lib/atlas-cert/src/acme.sh ./acme.sh --install \ --home /var/lib/atlas-cert/.acme.sh \ --accountemail "$CERT_EMAIL" \ --no-cron --no-profile chmod 700 /var/lib/atlas-cert/.acme.sh /var/lib/atlas-cert/.acme.sh/acme.sh --version /var/lib/atlas-cert/.acme.sh/acme.sh --set-default-ca --server letsencrypt ``` 安装目录与源码目录如果已存在,先检查原部署;不要覆盖其他证书任务。本文明确禁用安装器的默认 cron,第 9 节统一用 systemd timer,避免重复续期。版本升级应在维护窗口检查发行说明后进行,而非每次定时任务自动下载最新代码。 ### 6.2 使用 Let's Encrypt 测试环境验证 DNS 权限 测试证书保存到独立目录,不上传七牛,也不用于浏览器/Ozon。它只用于确认 DNS 验证流程可自动运行: ```bash install -d -m 700 /var/lib/atlas-cert/acme-staging /var/lib/atlas-cert/.acme.sh/acme.sh \ --home /var/lib/atlas-cert/.acme.sh \ --config-home /var/lib/atlas-cert/acme-staging \ --register-account --server letsencrypt_test -m "$CERT_EMAIL" /var/lib/atlas-cert/.acme.sh/acme.sh \ --home /var/lib/atlas-cert/.acme.sh \ --config-home /var/lib/atlas-cert/acme-staging \ --issue --server letsencrypt_test --dns dns_tencent \ -d "$CERT_MAIN_DOMAIN" -d "$CERT_WILDCARD_DOMAIN" \ --keylength 2048 ``` RSA 2048 用作兼容性示例,避免后续文件目录和 `--ecc` 参数混用。acme.sh 会创建临时 TXT、等待 DNS 验证并清理;首次可能需几分钟。成功后在 DNSPod 检查本次 TXT 已清理,且两个图片 CNAME 保持正确。 ### 2026-10-03 现场执行记录 - 服务器上的 `atlas-cert` 账号运行上述 staging `--issue` 命令,于北京时间 2026-10-03 11:02:09 收到证书文件路径及续期窗口输出;这确认测试环境签发已完成,不能视为正式证书或七牛部署验收。 - 测试证书位于 `/var/lib/atlas-cert/acme-staging/atlas.joeyone.cn/`:叶证书 `atlas.joeyone.cn.cer`、私钥 `atlas.joeyone.cn.key`、中间证书 `ca.cer`、完整证书链 `fullchain.cer`。这里只记录路径,不记录证书或私钥内容。 - 本次 staging 输出的 ARI 建议窗口为 `2026-12-01T04:47:36Z`~`2026-12-02T23:58:26Z`,所选下次续期时间为 `2026-12-01T20:38:56Z`;这些是**测试证书**的输出,不能用作正式证书续期日程。 - DNSPod 自动验证已使签发完成;临时 TXT 是否清理、两个图片域名 CNAME 是否保持正确仍需在 DNSPod 复核。随后按第 7 节申请正式证书,并用 `openssl` 检查正式证书的 SAN 和有效期。 - 正式签发和部署前,确认此前截图中暴露的腾讯云及七牛凭据均已停用、更换,并核对 `secrets.env` 与 acme.sh 缓存中的凭据。不要在项目文档或排错日志中保存真实密钥。 如果有 CAA 记录,确保它允许 Let's Encrypt 的 `letsencrypt.org` 签发;不要为排错直接删除业务已有 CAA。DNS-01 验证原理参见[Let's Encrypt 官方说明](https://letsencrypt.org/docs/challenge-types/)。 ## 7. 申请正式证书 测试验证通过后,仍在 `atlas-cert` 会话执行。正式流程使用默认配置目录,与测试目录分开: ```bash /var/lib/atlas-cert/.acme.sh/acme.sh \ --register-account --server letsencrypt -m "$CERT_EMAIL" /var/lib/atlas-cert/.acme.sh/acme.sh \ --issue --server letsencrypt --dns dns_tencent \ -d "$CERT_MAIN_DOMAIN" -d "$CERT_WILDCARD_DOMAIN" \ --keylength 2048 openssl x509 \ -in "/var/lib/atlas-cert/.acme.sh/$CERT_MAIN_DOMAIN/fullchain.cer" \ -noout -issuer -dates -ext subjectAltName ``` 确认 SAN 同时包含 `atlas.joeyone.cn` 与 `*.atlas.joeyone.cn`,证书颁发者属于正式受信任 CA。不要把第 6 节测试证书部署到七牛;不要通过反复 `--force` 申请正式证书来测试定时任务。 证书目录下: | 文件 | 七牛用途 | |---|---| | `fullchain.cer` | 完整证书链,上传为证书内容 | | `atlas.joeyone.cn.key` | 证书私钥 | 证书私钥仅用于证书部署,不是七牛 SK,也不是 Ozon 店铺 API Key。 ### 2026-10-03 正式签发记录 维护者在北京时间 2026-10-03 11:08:13 收到正式证书签发成功输出,证书目录为 `/var/lib/atlas-cert/.acme.sh/atlas.joeyone.cn/`。随后运行本节的 OpenSSL 检查,结果如下;尚未据此确认七牛或网站已使用新证书。 | 项目 | 本次结果 | |---|---| | 颁发者 | `C = US, O = Let's Encrypt, CN = YR2` | | SAN | `*.atlas.joeyone.cn`、`atlas.joeyone.cn` | | 生效时间 | UTC `2026-10-03 02:09:40`;北京时间 `2026-10-03 10:09:40` | | 到期时间 | UTC `2027-01-01 02:09:39`;北京时间 `2027-01-01 10:09:39` | | ARI 建议窗口 | UTC `2026-12-01 04:53:44`~`2026-12-03 00:04:34` | | acme.sh 所选下次续期时间 | UTC `2026-12-01 20:51:08`;北京时间 `2026-12-02 04:51:08` | ARI 时间是客户端本次选择的续期时间,实际自动执行仍依赖第 9 节的定时任务。下一步为第 8.1 节首次上传和启用七牛 HTTPS。 ## 8. 首次开启七牛 HTTPS 并配置自动部署 ### 8.1 首次开启 HTTPS 内置七牛 hook 调用修改 HTTPS 配置接口。因此首次先在控制台将两个 CDN 域名的 HTTPS 都启用,再交给 hook 维护;避免域名尚未开启 HTTPS 时直接调用更新接口失败。 1. 用 SSH/SFTP 客户端从服务器安全取回第 7 节的 `fullchain.cer` 和域名 `.key` 文件。 2. 在七牛 **SSL 证书服务 → 证书管理 → 上传自有证书** 中上传完整链和私钥;使用 PEM/Nginx 格式。 3. 在七牛 **CDN → 域名管理 → 对应域名 → HTTPS 配置**,两个域名都选择刚上传的证书并开启 HTTPS。 4. 如需 HTTP 强制跳转 HTTPS,保持与 `QINIU_FORCE_HTTPS='true'` 一致。 5. 等待控制台显示部署完成,检查两个域名的证书;临时下载到本机的私钥按秘密文件管理,传递结束后清理临时副本。 取回文件时可复用已有管理员 SSH 连接的 SFTP 文件面板。本次服务器使用已有的 `root` 管理连接访问证书目录;终端内的 `sudo -iu atlas-cert` 不会改变文件面板的登录身份。不要为下载文件给维护账号新增 SSH 密码或放宽证书目录权限。 1. 打开客户端的服务器文件面板,将远端路径设为 `/var/lib/atlas-cert/.acme.sh/atlas.joeyone.cn/`。`.acme.sh` 是隐藏目录,优先直接输入完整路径,或启用“显示隐藏文件”。 2. 只下载 `fullchain.cer` 和 `atlas.joeyone.cn.key` 到本机专用临时目录,保留服务器上的原文件。不要下载整个 `.acme.sh`,其中包含账户与 DNS/API 凭据。 3. 在七牛上传这两个文件;若页面要求填写 PEM 文本,在本机文本编辑器打开对应文件,完整复制各自的 BEGIN/END 区块。私钥不发送到聊天、项目仓库或公共日志。 4. 两个域名证书验收完成后,清理本机临时副本;服务器原文件继续供后续自动续期与部署使用。 如果两个域名原本就已开启 HTTPS,先上传并切换到新证书即可。不要先删除旧证书。 ### 8.2 执行一次自动部署 在 `atlas-cert` 会话执行: ```bash set -a . /var/lib/atlas-cert/secrets.env set +a /var/lib/atlas-cert/.acme.sh/acme.sh \ --deploy -d "$CERT_MAIN_DOMAIN" --deploy-hook qiniu ``` 该步骤会上传证书,并逐一更新 `QINIU_CDN_DOMAIN` 中的两个域名,同时保存部署 hook 供续期时调用。确认输出包含两个域名的部署成功记录,并在七牛控制台复核。首次手工上传和 hook 上传可能留下两条证书记录,这是正常的;验收后按引用情况清理旧条目。 2026-10-03 现场记录:维护者以 `atlas-cert` 执行上述部署命令,于北京时间 11:26:12 收到 `img.atlas.joeyone.cn` 和 `img-dev.atlas.joeyone.cn` 各自的 `certificate has been deployed successfully`,最终输出 `Success`。随后两个图片域名的网络证书检查结果已记录于第 10.2 节;自动续期执行仍需完成第 9 节的 systemd timer。日志中的 `updating domain atlas.joeyone.cn` 使用证书主域名标识本次证书,不能据此判断网站 Nginx 已更新。 参考:[acme.sh 七牛部署实现](https://github.com/acmesh-official/acme.sh/blob/3.1.6/deploy/qiniu.sh)、[七牛证书管理与部署 FAQ](https://developer.qiniu.com/fusion/3905/ssl-certificate-faq)。 收紧本地文件权限: ```bash find /var/lib/atlas-cert/.acme.sh -type d -exec chmod 700 {} + find /var/lib/atlas-cert/.acme.sh -type f -exec chmod go-rwx {} + find /var/lib/atlas-cert/acme-staging -type d -exec chmod 700 {} + find /var/lib/atlas-cert/acme-staging -type f -exec chmod go-rwx {} + ``` ### 8.3 导出同一张证书给本机 Nginx 继续在 `atlas-cert` 会话执行: ```bash install -d -m 700 /var/lib/atlas-cert/nginx-export /var/lib/atlas-cert/.acme.sh/acme.sh \ --install-cert -d "$CERT_MAIN_DOMAIN" \ --key-file /var/lib/atlas-cert/nginx-export/privkey.pem \ --fullchain-file /var/lib/atlas-cert/nginx-export/fullchain.pem chmod 600 /var/lib/atlas-cert/nginx-export/privkey.pem \ /var/lib/atlas-cert/nginx-export/fullchain.pem ``` `--install-cert` 会保存导出路径,续期时自动更新这两个文件。Nginx 不直接引用 acme.sh 内部文件;本流程由第 9.2 节的 root 管理脚本复制到 `/etc/nginx/tls/atlas/current/`、执行 `nginx -t` 并重载,因此这里没有设置 `--reloadcmd`。必须完成第 9 节,只有导出证书不能让 Nginx 使用新证书。[acme.sh 证书安装说明](https://github.com/acmesh-official/acme.sh#3-install-the-cert-to-apachenginx-etc) 2026-10-03 现场记录:北京时间 11:27:26,维护者收到两项 `Installing` 输出,证书已导出到以下路径;Nginx 接入仍需完成第 9.2 节。 | 文件用途 | 实际导出路径 | |---|---| | 证书私钥 | `/var/lib/atlas-cert/nginx-export/privkey.pem` | | 完整证书链 | `/var/lib/atlas-cert/nginx-export/fullchain.pem` | 维护账号不获得通用 sudo 权限。证书申请和七牛 API 调用以 `atlas-cert` 执行;复制到系统证书目录与 Nginx 重载由 systemd 的 root 步骤完成。root 脚本不加载 `secrets.env`,只读取固定路径的证书文件。 ## 9. 配置 systemd 定时续期 ### 9.1 创建续期入口 在 `atlas-cert` 会话创建脚本: ```bash cat > /var/lib/atlas-cert/renew-qiniu.sh <<'SH' #!/usr/bin/env bash set -euo pipefail umask 077 # 统一加载维护凭据,定时任务不依赖人工登录会话。 set -a . /var/lib/atlas-cert/secrets.env set +a # 与手工维护共用锁文件,避免并发操作同一套证书。 exec 9>/var/lib/atlas-cert/renew.lock flock -n 9 || exit 0 /var/lib/atlas-cert/.acme.sh/acme.sh \ --cron --home /var/lib/atlas-cert/.acme.sh SH chmod 700 /var/lib/atlas-cert/renew-qiniu.sh # 首次由维护账号创建锁,避免 root 部署脚本先创建后导致维护账号无法写入。 test -e /var/lib/atlas-cert/renew.lock || \ install -m 600 /dev/null /var/lib/atlas-cert/renew.lock bash -n /var/lib/atlas-cert/renew-qiniu.sh exit ``` `exit` 返回原管理账号。 ### 9.2 创建 Nginx 部署脚本并完成首次接入 本节按网站服务选择接入方式:当前现场使用 **9.2.1 的 1Panel 分支**;9.2.2 仅适用于已经安装并运行宿主机 systemd Nginx 的服务器。 > 2026-10-03 现场环境确认:服务器为 1Panel `v2.0.11 stable`,网站服务容器为 `1Panel-openresty-C1B1`,镜像为 `1panel/openresty:1.27.1.2-2-3-focal`。维护者执行本节原脚本时,证书检查与宿主机 `nginx -t` 通过,但 `systemctl reload nginx` 报 `nginx.service is not active, cannot reload`;本节接入未完成。应用部署后应使用 1Panel 配置网站 HTTPS,证书续期后的同步与容器重载需按实际网站配置适配。当前可先启用第 9.3 节不含网站部署步骤的基础定时任务,待适配验收后补上 `ExecStartPost`。 > > 1Panel v2.0.11 从服务器路径导入证书时会读取并保存内容,不能把导入操作视为持续引用源文件;acme.sh 更新 `nginx-export` 后仍需主动同步到 1Panel 网站使用的证书。依据:[v2.0.11 证书导入实现](https://github.com/1Panel-dev/1Panel/blob/v2.0.11/agent/app/service/website_ssl.go)。 #### 9.2.1 当前现场:1Panel/OpenResty,应用尚未部署 2026-10-03,维护者通过 `docker inspect` 与容器内 `nginx -T` 确认以下挂载关系: | 宿主机路径 | OpenResty 容器路径 | 用途 | |---|---|---| | `/opt/1panel/www` | `/www` | 网站目录及各网站证书文件 | | `/opt/1panel/www/conf.d` | `/usr/local/openresty/nginx/conf/conf.d` | 网站配置 | | `/opt/1panel/apps/openresty/openresty/conf/ssl` | `/usr/local/openresty/nginx/conf/ssl` | 默认站点证书 | 现有网站包括 `git.joeyone.cn`、`pipi-assets.joeyone.cn`、`test.joeyone.cn`、`wmd.joeyone.cn`,分别使用 `/www/sites/<网站域名>/ssl/fullchain.pem` 和 `privkey.pem`。输出中没有 `atlas.joeyone.cn` 或 `dev.atlas.joeyone.cn` 的网站配置;维护者确认两套 Atlas 应用尚未部署。 **当前可先完成图片域名验收及第 9.3 节的七牛续期任务。** 网站应用未部署前,无需创建占位反向代理,也不把项目本地开发端口当作云端应用地址。现有网站及默认站点证书继续由各自配置维护。 应用部署时按以下顺序接入: 1. 部署生产、开发应用,确认各自实际访问地址、监听端口及 OpenResty 容器可达性。 2. 在 1Panel 的“网站 → 证书”上传正式证书,使用“本地导入/服务器路径”时填写以下**宿主机路径**;若界面要求文件内容,则在受控终端读取后粘贴,不将私钥发送到聊天或提交到仓库。 - 证书:`/var/lib/atlas-cert/nginx-export/fullchain.pem` - 私钥:`/var/lib/atlas-cert/nginx-export/privkey.pem` 3. 在 1Panel 新建 `atlas.joeyone.cn` 和 `dev.atlas.joeyone.cn` 网站,配置各自真实应用的反向代理,并在 HTTPS 设置中选择导入的同一张 Atlas 证书。 4. 再次检查网站配置中的实际证书路径,并从网络验证两个网站证书和环境路由。文件目录可能由网站创建方式决定,不预先假设一定为 `/www/sites/atlas.joeyone.cn`。 5. 接入续期后同步:优先通过与 1Panel 版本匹配的证书更新接口更新已导入证书,使面板记录与关联网站一起更新;验收后再接入第 9.3 节的 `ExecStartPost`。当前未确认面板接口配置、证书 ID 和新网站实际路径,自动同步尚未完成。 仅手工导入证书不会完成网站自动续期部署。以后只替换网站目录里的 PEM 文件也可能造成面板保存的证书内容与实际文件不一致,后续面板操作可能重新写入旧内容,应完成上述同步适配。 #### 9.2.2 宿主机 systemd Nginx 分支 以下示例仅用于宿主机 Nginx。当前 1Panel 现场跳过本小节;不要为使旧脚本成功而启动另一个宿主机 Nginx。 以下由管理账号执行。脚本及系统证书目录由 root 管理,维护账号不能修改部署脚本。每次部署使用独立版本目录,只有配置检查和重载成功后才记录成功指纹;失败则恢复原目录链接,供下次重试: ```bash sudo install -d -m 700 -o root -g root /etc/nginx/tls/atlas sudo tee /usr/local/sbin/atlas-cert-install-nginx > /dev/null <<'SH' #!/usr/bin/env bash set -euo pipefail umask 077 # 与申请、手工部署共用锁,避免读取尚未导出完的证书。 exec 9>/var/lib/atlas-cert/renew.lock flock 9 cert_root=/etc/nginx/tls/atlas export_root=/var/lib/atlas-cert/nginx-export release=$(mktemp -d "$cert_root/release.XXXXXXXX") old_target=$(readlink "$cert_root/current" || true) activated=0 finished=0 # 失败时恢复上一次目录,不把未成功部署的指纹标记为完成。 cleanup() { if [[ "$finished" != 1 ]]; then if [[ "$activated" == 1 ]]; then if [[ -n "$old_target" ]]; then ln -s "$old_target" "$cert_root/.rollback" mv -Tf "$cert_root/.rollback" "$cert_root/current" else rm -f "$cert_root/current" fi fi rm -rf "$release" fi } trap cleanup EXIT install -m 600 "$export_root/fullchain.pem" "$release/fullchain.pem" install -m 600 "$export_root/privkey.pem" "$release/privkey.pem" openssl x509 -in "$release/fullchain.pem" -noout -checkend 0 for cert_domain in atlas.joeyone.cn dev.atlas.joeyone.cn img.atlas.joeyone.cn img-dev.atlas.joeyone.cn; do openssl x509 -in "$release/fullchain.pem" -noout -checkhost "$cert_domain" done # 验证公钥一致,不输出私钥或证书原文。 cert_pub=$(openssl x509 -in "$release/fullchain.pem" -pubkey -noout | openssl pkey -pubin -outform DER | sha256sum) key_pub=$(openssl pkey -in "$release/privkey.pem" -pubout -outform DER | sha256sum) [[ "$cert_pub" == "$key_pub" ]] fingerprint=$(openssl x509 -in "$release/fullchain.pem" -noout -fingerprint -sha256) if [[ -L "$cert_root/current" && -f "$cert_root/deployed-fingerprint" ]] && [[ "$(cat "$cert_root/deployed-fingerprint")" == "$fingerprint" ]]; then rm -rf "$release" finished=1 exit 0 fi ln -s "$(basename "$release")" "$cert_root/.next" mv -Tf "$cert_root/.next" "$cert_root/current" activated=1 /usr/sbin/nginx -t /usr/bin/systemctl reload nginx printf '%s\n' "$fingerprint" > "$cert_root/deployed-fingerprint" finished=1 SH sudo chown root:root /usr/local/sbin/atlas-cert-install-nginx sudo chmod 700 /usr/local/sbin/atlas-cert-install-nginx sudo bash -n /usr/local/sbin/atlas-cert-install-nginx sudo /usr/local/sbin/atlas-cert-install-nginx ``` 第一次运行会建立 `current` 目录链接,并检查/重载已有 Nginx。随后在**现有**生产和开发网站 `server` 配置中分别设置如下 TLS 指令,保留各自原有 `location`、反向代理、访问控制和日志配置。以下是待合并的片段,不是完整站点配置: ```nginx # 生产网站的 TLS 配置,业务路由沿用现有生产配置。 listen 443 ssl; server_name atlas.joeyone.cn; ssl_certificate /etc/nginx/tls/atlas/current/fullchain.pem; ssl_certificate_key /etc/nginx/tls/atlas/current/privkey.pem; ``` ```nginx # 开发网站使用同一张证书,业务路由指向开发环境。 listen 443 ssl; server_name dev.atlas.joeyone.cn; ssl_certificate /etc/nginx/tls/atlas/current/fullchain.pem; ssl_certificate_key /etc/nginx/tls/atlas/current/privkey.pem; ``` 同一 IP 可承载两个网站,Nginx 按域名选择对应的 `server`;两个网站的 upstream 和环境配置仍分别维护。不要仅因证书共用就让开发流量进入生产应用。[Nginx HTTPS 配置说明](https://nginx.org/en/docs/http/configuring_https_servers.html) 配置编辑完成后,由管理账号执行: ```bash sudo nginx -t sudo systemctl reload nginx ``` 旧证书版本目录暂时保留作为回滚材料。新证书在四个域名验收通过后,可由管理账号清理不再需要的旧版本,保留当前版本和仍有效的回滚版本。目录含私钥,权限保持 700/600。 ### 9.3 配置定时任务 当前 1Panel 现场尚未部署 Atlas 网站,先创建**证书续期、七牛部署及 PEM 导出**的定时任务。以下基础服务不调用第 9.2.2 节的宿主机 Nginx 脚本,因此不会因 `nginx.service` 未运行而报错;网站自动同步仍须在应用部署后单独接入。 以下由管理账号执行,创建 systemd 单元。如果先前已创建含 `ExecStartPost=/usr/local/sbin/atlas-cert-install-nginx` 的服务,使用此基础单元覆盖后重新加载: ```bash sudo tee /etc/systemd/system/atlas-qiniu-cert-renew.service > /dev/null <<'UNIT' [Unit] Description=Atlas shared certificate renewal and deployment Wants=network-online.target After=network-online.target [Service] Type=oneshot WorkingDirectory=/var/lib/atlas-cert UMask=0077 ExecStart=/usr/sbin/runuser -u atlas-cert -- /var/lib/atlas-cert/renew-qiniu.sh TimeoutStartSec=30min NoNewPrivileges=true PrivateTmp=true ProtectHome=true UNIT sudo tee /etc/systemd/system/atlas-qiniu-cert-renew.timer > /dev/null <<'UNIT' [Unit] Description=Daily Atlas Qiniu certificate check [Timer] OnCalendar=*-*-* 03:20:00 Asia/Shanghai RandomizedDelaySec=30min Persistent=true Unit=atlas-qiniu-cert-renew.service [Install] WantedBy=timers.target UNIT sudo systemd-analyze verify \ /etc/systemd/system/atlas-qiniu-cert-renew.service \ /etc/systemd/system/atlas-qiniu-cert-renew.timer sudo systemctl daemon-reload sudo systemctl enable --now atlas-qiniu-cert-renew.timer sudo systemctl start atlas-qiniu-cert-renew.service sudo systemctl list-timers atlas-qiniu-cert-renew.timer --all sudo systemctl show atlas-qiniu-cert-renew.service \ -p Result -p ExecMainStatus sudo journalctl -u atlas-qiniu-cert-renew.service -n 50 --no-pager ``` 该基础服务由 root 启动,`ExecStart` 通过 `runuser` 降权执行 acme.sh,维护账号仍没有 sudo 权限。基础服务成功只证明当前续期检查流程完成,不证明网站证书已同步;还须监控 service 失败和两个图片域名实际证书。 每天北京时间 03:20 后随机延迟最多 30 分钟执行。每天检查不等于每天重新申请;acme.sh 按证书续期窗口决定是否申请,并更新保存的证书导出路径、调用七牛部署 hook。服务器关机时不会运行,`Persistent=true` 会在恢复后补跑错过的定时检查。 **已有宿主机 Nginx 网站的服务器**:先完成第 9.2.2 节验收,再在服务 `[Service]` 中添加 `ExecStartPost=/usr/local/sbin/atlas-cert-install-nginx`,重新执行单元校验、`daemon-reload` 和手工触发 service。**1Panel 现场**:完成第 9.2.1 节的网站及证书同步适配后,才添加对应的部署脚本;不能使用宿主机 Nginx 脚本替代。带 `ExecStartPost` 时,只有前置续期流程成功才执行网站部署,网站部署失败会使整个 service 失败;应同时监控网站证书和任务状态。 刚申请的证书显示跳过续期是正常的。`Type=oneshot` 完成后显示 inactive/dead 也是正常的,检查 timer 为 active、任务 `Result=success`、`ExecMainStatus=0`。日志只在受控终端查看,不导出带凭据的调试日志。 ## 10. 配置通知与独立到期检查 ### 10.1 配置续期/部署邮件通知 在管理账号执行 `sudo -iu atlas-cert`,编辑 `/var/lib/atlas-cert/secrets.env`,追加以下值。示例采用邮箱服务商的 SMTP 465;主机、端口及授权码按实际邮箱说明填写: ```bash SMTP_BIN='/usr/bin/curl' SMTP_HOST='smtp.your-mail-provider.example' SMTP_PORT='465' SMTP_SECURE='ssl' SMTP_USERNAME='cert-bot@example.com' SMTP_PASSWORD='替换为SMTP授权码' SMTP_FROM='cert-bot@example.com' SMTP_TO='ops@joeyone.cn' ``` 如使用 587/STARTTLS,改为 `SMTP_PORT='587'`、`SMTP_SECURE='tls'`。在服务器/安全组出站规则中允许对应端口,不使用可能被云平台限制的 25 端口。 ```bash set -a . /var/lib/atlas-cert/secrets.env set +a /var/lib/atlas-cert/.acme.sh/acme.sh \ --set-notify --notify-hook smtp --notify-level 2 --notify-mode 0 exit ``` 这条命令会发送配置测试邮件给 `SMTP_TO`,应确认实际收到。级别 2 用于报告错误和实际续期结果,日常跳过无需每次发信。已有可靠通知渠道时可替换为 acme.sh 支持的 hook,但部署失败必须能通知到维护者。参数见[acme.sh 通知说明](https://github.com/acmesh-official/acme.sh/wiki/notify)及[SMTP hook 实现](https://github.com/acmesh-official/acme.sh/blob/master/notify/smtp.sh)。 ### 10.2 从网络检查四个域名实际证书 本地证书更新成功不等于所有 CDN 节点完成更新。当前两个网站尚未部署,先使用 OpenSSL 检查两个图片域名。应用上线且网站 HTTPS 配置完成后,将循环域名扩展为 `atlas.joeyone.cn dev.atlas.joeyone.cn img.atlas.joeyone.cn img-dev.atlas.joeyone.cn`,再验收四个域名。 仅复制代码块内的命令,不复制 Markdown 的三个反引号。若终端显示连续的 `>` 续行提示,先按 `Ctrl+C` 返回正常提示符,再重新粘贴命令。 ```bash set -o pipefail for cert_domain in img.atlas.joeyone.cn img-dev.atlas.joeyone.cn; do printf '%s\n' "$cert_domain" timeout 20 openssl s_client \ -connect "$cert_domain:443" -servername "$cert_domain" \ -verify_hostname "$cert_domain" -verify_return_error ` / `Unable to load certificate`,表示管道没有获得可解析的证书。由于当时隐藏了 `s_client` 的错误输出,无法仅凭这一结果区分 DNS、连接或 TLS 失败;结合应用尚未部署的现场状态,两个网站暂列待部署后验收,无需重新签发证书。 ### 10.3 给四个域名分别加独立监控 在现有外部监控系统或支持证书检查的云监控产品中,当前先把两个图片域名纳入 TLS 到期监控;网站上线后补齐两个网站域名。同一张证书也可能在某个部署目标更新失败: - 证书剩余不足 21 天告警,不足 7 天升级处理。 - TLS 握手、证书链或域名匹配失败立即告警。 - 日常任务长期没有运行,单独按任务执行时间监控。 - 私有资源 HTTP 探测需使用受控授权;普通无签名 403 不是资源故障。 首次手工检查到期阈值可使用以下命令;它只检查当前网络节点,不能代替多地区监控。当前仅检查图片域名,网站上线后再扩展域名列表: ```bash set -o pipefail for cert_domain in img.atlas.joeyone.cn img-dev.atlas.joeyone.cn; do if timeout 20 openssl s_client \ -connect "$cert_domain:443" -servername "$cert_domain" \ -verify_hostname "$cert_domain" -verify_return_error /dev/null | openssl x509 -noout -checkend 1814400; then printf '%s:当前证书超过21天有效\n' "$cert_domain" else printf '%s:证书不足21天或TLS检查失败,请处理\n' "$cert_domain" >&2 fi done ``` ## 11. 验证私有图片链接和缓存鉴权 为每个空间上传一张没有敏感内容的测试 JPEG,例如对象键 `ops/cert-smoke.jpg`。使用浏览器或七牛工具上传即可,保留该测试对象供验收;不要覆盖业务图片。 先确认第 10.2 节 TLS 验证通过,再执行图片访问测试。签名 URL、AK/SK 都不直接打印到终端。以下脚本在 `atlas-cert` 会话运行,当前配置假设管理 AK/SK 同时具有这两个空间的私有下载签名能力;若使用了仅能管理证书的凭据,请改用目标空间的授权下载凭据执行本步骤,不扩大证书维护凭据权限。 ```bash set -a . /var/lib/atlas-cert/secrets.env set +a python3 <<'PY' import base64 import hashlib import hmac import os import time import urllib.error import urllib.parse import urllib.request def status(url): try: with urllib.request.urlopen(url, timeout=20) as response: response.read(1) return response.status except urllib.error.HTTPError as error: return error.code except Exception as error: # 网络异常可能含完整签名链接,只输出异常类型。 raise SystemExit(f"网络检查失败:{type(error).__name__}") from None def signed(base, deadline): url = f"{base}?e={deadline}" digest = hmac.new( os.environ["QINIU_SK"].encode(), url.encode(), hashlib.sha1 ).digest() token = base64.urlsafe_b64encode(digest).decode() return f"{url}&token={os.environ['QINIU_AK']}:{token}" for domain in os.environ["QINIU_CDN_DOMAIN"].split(): base = f"https://{domain}/{urllib.parse.quote('ops/cert-smoke.jpg', safe='/')}" unsigned = status(base) valid_url = signed(base, int(time.time()) + 30) valid = status(valid_url) cached_unsigned = status(base) # 对同一个已访问链接等待过期,验证缓存命中没有绕过授权。 time.sleep(32) expired = status(valid_url) print(f"{domain}: 无签名={unsigned}, 有效签名={valid}, " f"缓存后无签名={cached_unsigned}, 过期签名={expired}") if not (unsigned in (401, 403) and valid == 200 and cached_unsigned in (401, 403) and expired in (401, 403)): raise SystemExit("私有访问验收未通过,请检查对象和回源鉴权") PY ``` 预期:有效签名 200,无签名、缓存后无签名和已过期签名均被拒绝。有效链接得到 404 通常表示测试对象未上传到对应空间或对象键不同;得到 403 应检查签名密钥、域名、时钟和访问控制。 这个测试只验证你的网络到七牛的访问。还需从另一网络/境外环境核验,最终以测试商品提交后的 Ozon 图片抓取与处理结果验收为准。[七牛私有下载凭证](https://developer.qiniu.com/kodo/1656/download-private) ## 12. 与 Atlas 应用配置对接 ### 12.1 应用配置与维护配置分开 证书任务使用 `QINIU_AK` / `QINIU_SK`;应用使用以下既有环境项。两者职责和权限不同,不把证书维护文件放到应用仓库或前端构建环境。 以华东浙江区域的生产空间为示例,实际 endpoint 和 S3 空间名从七牛控制台获取: ```dotenv OBJECT_STORAGE_PROVIDER=qiniu_kodo OBJECT_STORAGE_ENDPOINT=https://s3.cn-east-1.qiniucs.com OBJECT_STORAGE_REGION=cn-east-1 OBJECT_STORAGE_BUCKET=atlas-prod-assets OBJECT_STORAGE_FORCE_PATH_STYLE=true OBJECT_STORAGE_ACCESS_KEY=填写生产对象存储凭据 OBJECT_STORAGE_SECRET_KEY=填写生产对象存储凭据 OZON_IMAGE_DOWNLOAD_ORIGIN=https://img.atlas.joeyone.cn OZON_IMAGE_URL_TTL_SECONDS=604800 QINIU_ACCESS_KEY=填写生产私有下载签名凭据 QINIU_SECRET_KEY=填写生产私有下载签名凭据 OZON_PUBLISH_ENABLED=auto ``` 开发联调配置替换为开发空间、开发凭据和 `https://img-dev.atlas.joeyone.cn`。`OZON_IMAGE_DOWNLOAD_ORIGIN` 同时用于 API 的图片预览、附件下载和 Worker 向 Ozon 交付图片;七牛私有空间的链接由服务端按用途签发短期下载凭证。上传仍使用 `OBJECT_STORAGE_ENDPOINT`。数据库素材身份继续记录 `storage_provider + bucket + object_key`,不保存签名 URL 作为素材主数据。 `OBJECT_STORAGE_BUCKET` 当前用于 S3 操作,因此应填七牛显示的 **S3 空间名**;如它与普通空间名不同,不可混用。自定义 CDN 域名绑定相应的实际空间。[七牛 S3 服务域名说明](https://developer.qiniu.com/kodo/4088/s3-access-domainname) ### 12.2 接入校验事项 证书配置完成后,还需确认应用与存储空间的联通性: 1. API 和 Worker 的 S3 客户端读取 `OBJECT_STORAGE_REGION`;它必须与七牛空间的区域及 endpoint 匹配。API 的七牛预览与附件下载使用绑定的 HTTPS 图片域名签发私有下载链接。 2. 需验证现有 S3 上传、HEAD/GET、图片域名私有下载和浏览器直传的七牛兼容性;上传 endpoint 的 CORS 要允许实际 Atlas 开发/生产网站 Origin、PUT/GET/HEAD 及预检请求中实际使用的头,不能拿 CDN 配置代替上传 CORS。 3. 发布代码拒绝 `local_minio` 或与当前 bucket 不一致的素材。原 MinIO 图片需要复制、校验并更新素材定位,或重新导入,不能仅切换配置。 4. 原有架构文件仍以普通本地开发使用 MinIO 为基线。本手册描述待实施的七牛联调操作,不自行宣布该基线已被替换;正式切换时应同步更新上游架构和交接文档。 5. 生产 `OZON_PUBLISH_ENABLED=auto` 默认不启用发布。完成七牛延迟抓图及真实店铺其他前置验收后,另行显式启用,不在证书维护步骤中顺便开启生产上架。 图片链接默认 7 天是现有应用设置,不是 Ozon 官方有效期保证。CDN 对 `publishing/` 的对象缓存可以存在,但空间生命周期不能提前删除仍在发布/重试/对账中的对象。 ## 13. 日常维护、换密钥与回滚 ### 查看任务 管理账号执行: ```bash sudo systemctl list-timers atlas-qiniu-cert-renew.timer --all sudo systemctl show atlas-qiniu-cert-renew.service -p Result -p ExecMainStatus sudo journalctl -u atlas-qiniu-cert-renew.service --since '2 days ago' --no-pager ``` ### 重试部署现有证书 如果正式证书已经签发,只有七牛部署失败,不需要再次申请证书: ```bash sudo -iu atlas-cert set -a . /var/lib/atlas-cert/secrets.env set +a flock /var/lib/atlas-cert/renew.lock \ /var/lib/atlas-cert/.acme.sh/acme.sh \ --deploy -d "$CERT_MAIN_DOMAIN" --deploy-hook qiniu exit ``` 七牛重试成功后,运行以下命令也检查/部署 Nginx,不重新申请证书: ```bash sudo systemctl start atlas-qiniu-cert-renew.service ``` 如果导出文件丢失,先在 `atlas-cert` 会话重新执行第 8.3 节的 `--install-cert`。部署之后按第 10.2 节再次检查网络实际证书,七牛配置传播期间部分节点可能仍返回旧证书。 ### 换 DNS/七牛密钥 1. 保留旧密钥,在控制台创建权限正确的新密钥。 2. 停止 timer,并检查/等待正在运行的 service 完成。 3. 编辑 `secrets.env`;DNS 凭据还可能缓存为 `account.conf` 中的 `SAVED_Tencent_SecretId` / `SAVED_Tencent_SecretKey`,七牛 hook 凭据缓存于域名配置。部分缓存会优先于新的 shell 环境值,不能只改一个文件就宣告完成轮换。 4. 在受控编辑器中同步缓存配置或删除旧的对应缓存键,让下次调用重新保存;不要输出整份配置到日志。备份也必须保留 600 权限。 5. 七牛凭据用上一段的 `--deploy` 验证;DNS 新凭据用独立 staging 配置目录重新做 DNS 验证,正常生产续期时确认保存新值。 6. 再次启动 timer,确认任务/通知和网络证书正常后,禁用并删除旧密钥。 ```bash sudo systemctl stop atlas-qiniu-cert-renew.timer sudo systemctl is-active atlas-qiniu-cert-renew.service ``` 如果 service 正在执行,先等待完成,不同时手工改凭据。轮换结束: ```bash sudo systemctl start atlas-qiniu-cert-renew.timer ``` ### 回滚 保留原有效证书及七牛证书 ID、Nginx 旧版本目录,直到新证书在四个域名、多网络测试均通过。发生新证书部署问题时,可暂停 timer,在七牛控制台将受影响域名重新绑定原有效证书;确认网络实际证书后排查。Nginx 需要回滚时,由管理账号将 `/etc/nginx/tls/atlas/current` 链接恢复到原有效版本,执行 `nginx -t` 后重载,并重新核验网站证书;同步移除 `deployed-fingerprint`,修复后才恢复定时任务,避免记录与实际部署不一致。原证书已经过期时不能回滚到它,应部署新正式证书。 ### 备份与迁移维护服务器 加密备份整个 `/var/lib/atlas-cert`(含 ACME 账号、证书、私钥、缓存凭据)、`/etc/nginx/tls/atlas`、root 部署脚本、两个网站的 Nginx 配置和两个 systemd 单元,限制备份读取权限。换机器时先在旧机停 timer,恢复目录与账号权限,再在新机部署单元和做一次续期检查、证书部署/网络检查,避免两台机器同时操作同一证书。 ## 14. 常见故障 | 现象 | 优先检查 | 处理 | |---|---|---| | DNS 验证失败 / 找不到根域 | 实际 NS、DNSPod 账号归属、四个操作权限 | 确认正确插件和域名级资源;不要扩大到主账号权限 | | `Tencent_SecretId` 未配置 | 变量大小写、配置文件权限、systemd 用户 | 确认环境文件已加载,不使用 `DP_Id` 代替 | | TXT 验证超时 | DNS 传播、权威 DNS、DNSSEC、CAA、出站网络 | staging 中排查,再申请正式证书 | | 七牛 `no such domain` / 612 | 域名是否存在于该 AK 所属 CDN 账号、是否用了源站域名、前导点 | 填准确 CDN 域名,移除前导点,不填写 URL | | 首次更新 HTTPS 失败 | 两个域名是否均已开启 HTTPS | 先完成控制台首次绑定,再部署 hook | | 证书已上传但图片域名仍旧证书 | 两域名绑定、配置传播、当前 CDN 节点 | 检查两个域名而非只看证书列表,等待传播并多网络核验 | | TLS 正常但图片 401/403 | 链接过期、签名、服务器时间、Referer/IP/UA 限制 | 检查图片授权,不重新申请 HTTPS 证书 | | 已过期链接仍能下载 | 私有回源鉴权和缓存命中路径 | 修正鉴权,重跑第 11 节,不改 bucket 为公开 | | 网站仍返回旧证书 | Nginx 导出目录、部署脚本、`nginx -t`、service 状态 | 修复导出/配置后重跑 service,核验两个网站证书 | | 本地证书新、七牛证书旧 | hook 调用失败、AK 权限、目标域名列表 | 重试 `--deploy`,不强制重复签发 | | systemd 执行后 inactive | oneshot 已正常结束 | 看 timer、Result 和退出码 | | 没收到邮件 | SMTP 授权码、From、端口出站、收件箱 | 重做通知配置测试,检查邮箱服务商要求 | | 图片签名链接 404 | 对象是否在正确空间、key 编码、是否被提前清理 | 检查对象身份和保留策略 | | 应用 S3 访问失败 | region、endpoint、S3 空间名、对象权限 | 按第 12 节核对应用配置和存储空间权限 | 排错时只提供脱敏后的错误码、域名、时间和请求 ID;不提供 SK、证书私钥、完整授权 URL 或完整账号配置文件。 ## 15. DNS 不在 DNSPod、或需要其他证书形式 ### DNS 托管商不同 腾讯云服务器可以使用任意受支持 DNS 服务商的 API。仅替换第 5 节凭据和第 6~7 节的 `--dns` 插件,七牛部署和 systemd 流程不变: | DNS 实际托管商 | acme.sh 插件 | 凭据说明 | |---|---|---| | 腾讯云 DNSPod 新版 API | `dns_tencent` | `Tencent_SecretId`、`Tencent_SecretKey` | | DNSPod 旧版 API | `dns_dp` | `DP_Id`、`DP_Key`;不是腾讯云 SecretId/SecretKey | | 阿里云 DNS | `dns_ali` | 按该插件文档配置对应凭据 | | Cloudflare DNS | `dns_cf` | 按该插件文档使用限定 DNS 权限的 API token | 不要仅因为服务器在腾讯云就迁移域名 DNS。其他插件的最新变量名和权限参考 [acme.sh DNS API 指南](https://github.com/acmesh-official/acme.sh/wiki/dnsapi)及 [DNS API 指南第二部分](https://github.com/acmesh-official/acme.sh/wiki/dnsapi2)。 ### 扩展 Atlas 域名时保持同一层级 主流程已使用 `atlas.joeyone.cn` + `*.atlas.joeyone.cn`。未来增加 `api.atlas.joeyone.cn`、`staging.atlas.joeyone.cn` 或 `img-staging.atlas.joeyone.cn`,证书覆盖范围无需改变,但仍要配置 DNS、对应服务以及证书部署。新增图片域名还要绑定独立环境空间,并加入 `QINIU_CDN_DOMAIN` 和监控列表。 若使用 `img.dev.atlas.joeyone.cn` 等更深层域名,需要额外 SAN 或新的通配符,不适合当前四域名共用一张证书的命名目标。`--deploy -d`、`--install-cert -d` 始终使用证书主域名 `atlas.joeyone.cn`,不能拿某个图片域名当证书身份。 ### 开发生产使用不同七牛账号 需要每账号独立配置目录、凭据和部署任务,分别申请/维护证书更容易管理权限。本手册集中任务适用于同一七牛账号下、由同一维护者管理的两个域名;不把共享证书当作共享存储权限的理由。 ## 16. 完成验收清单 - [ ] 两个空间都是私有,生产与开发数据/应用凭据隔离。 - [ ] 两个下载域名是七牛 CDN 域名,分别指向正确空间,CNAME 已生效。 - [ ] 两个域名均启用私有空间回源鉴权,未添加与现有签名协议冲突的限制。 - [x] DNS-01 staging 验证及测试证书签发成功(2026-10-03)。 - [ ] 临时 TXT 已清理,两个图片域名 CNAME 保持正确。 - [x] 正式 SAN 含 `atlas.joeyone.cn` 与 `*.atlas.joeyone.cn`,覆盖四个域名;颁发者及有效期已检查(2026-10-03)。 - [ ] 两个网站 A 记录正确,Nginx 分别路由到对应环境,证书导出、配置检查和重载成功。 - [x] 七牛 hook 首次部署对两个图片域名均返回成功(2026-10-03 11:26:12,北京时间)。 - [x] 两个图片域名的实际网络证书已核验更新(2026-10-03,当前探测节点的颁发者及有效期与本次正式证书一致,两个域名指纹相同)。 - [ ] systemd timer 启用,手工触发 service 成功,七牛及 Nginx 均完成部署;没有另一个 cron/服务器并发维护。 - [ ] 当前 1Panel 阶段:先启用仅续期、七牛部署及 PEM 导出的基础 timer,手工触发成功;应用上线后补齐网站同步,再验收上一项完整链路。 - [ ] 故障/实际续期通知已收到测试消息,四个域名已纳入独立到期监控。 - [ ] 两空间测试图片均通过有效签名、无签名、缓存后无签名、过期签名测试。 - [ ] 权限受控的加密备份和旧有效证书回滚方式已准备。 - [ ] 应用 S3 region、上传与图片域名的私有下载均已验证,未把证书部署成功当作 Ozon 上架验收成功。 ## 17. 项目与官方参考 - [项目对象存储概念与身份边界](./object-storage-object-key.md) - [项目技术栈与环境边界](../architecture/technology-and-deployment.md) - [七牛 SSL FAQ:证书 API 与源站域名限制](https://developer.qiniu.com/fusion/kb/3905/ssl-certificate-faq?category=kb) - [七牛私有空间 CDN 回源鉴权](https://developer.qiniu.com/fusion/3930/back-to-the-source-authentication) - [七牛私有下载 URL](https://developer.qiniu.com/kodo/1656/download-private) - [腾讯云子账号 API 密钥管理](https://cloud.tencent.com/document/product/598/37140) - [腾讯云 DNSPod 可授权资源类型](https://cloud.tencent.com/document/product/302/105719) - [acme.sh 3.1.6 发行说明](https://github.com/acmesh-official/acme.sh/releases/tag/3.1.6) - [Nginx HTTPS 配置](https://nginx.org/en/docs/http/configuring_https_servers.html) - [DNSPod 泛解析与明确记录匹配](https://cloud.tencent.com/document/product/302/9073) - [acme.sh 项目与安装说明](https://github.com/acmesh-official/acme.sh) - [Let's Encrypt DNS-01 验证](https://letsencrypt.org/docs/challenge-types/) 云平台界面和接口可能调整;操作时以实际控制台和上列官方文档为准。本文脚本需在你的真实账号做首次验收,不能仅靠复制成功就认为完成续期闭环。