[2026 Remastered · 02] 告别 Common Name 失效报错:OpenSSL 3.5 私有 Root CA 与多域名/内网 IP SAN 证书一键签发实战(Debian 13 真机版)
🛸 站长重制导读(Anti Gravi's Remaster Note)
本文是 「来时路 · 2026 Debian 13 真机重制企划(2/12)」 的第 2 篇,对应本站 2020 年 1 月发布的 《一键生成SSL证书》(
CID: 12)。2020 年的原稿只有短短 25 行,用最朴素的
openssl genrsa -des3加上openssl req -new -x509完成了自签证书启蒙。但如果今天你把那段脚本原封不动拷进生产或内网环境,会立刻撞上四堵墙:Chrome/Safari 全面废弃CN (Common Name)强推SAN (Subject Alternative Name)扩展、超过 398 天有效期被浏览器直接拒认、手动加密再解密的无效折腾与history明文密码残留,以及每次重签都要在客户端重新导入信任的痛点。本次重制版在 Proxmox VE 的 Debian 13 (Trixie ·
OpenSSL 3.5.7·Nginx 1.26.3) 真机上完成全流程实测,不仅彻底根治上述四大隐患,还在真机抓包中见证了 OpenSSL 3.5 默认启用的X25519MLKEM768后量子混合密钥交换!
一、 2020 原稿 (CID: 12) 四大历史硬伤复盘
我们先对照 2020 年原稿的核心代码,看看这六年里 TLS/PKI 世界发生了怎样的代际更替:
| 维度 | 2020 原稿 (CID: 12) 写法 |
2026 真实环境后果 | 2026 Remastered (Debian 13) 根治方案 |
|---|---|---|---|
1. 缺失 X.509 v3 SAN 扩展 |
仅传入 -subj "/C=CN/.../CN=$domain",无任何 v3 扩展 |
自 RFC 2818 与 Chrome 58 起,现代浏览器完全忽略 CN 字段,只校验 subjectAltName;直接报 NET::ERR_CERT_COMMON_NAME_INVALID |
同时提供 OpenSSL 3.x -addext 单行极速方案 与 支持多域名 + 通配符 + 内网 IP 的 SAN 扩展模板 |
| 2. 证书有效期超限 | 写死 -days 3650(10 年有效期) |
Apple / Chrome / Mozilla 根计划自 2020 年 9 月起强制规定 TLS 服务器证书最长有效期不得超过 398 天(2026 年后进一步缩短) | 将 Root CA 根证书(10 年,3650 天) 与 Server 业务证书(397 天) 解耦:客户端只需信任一次 Root CA,业务证书随时秒级轮换 |
| 3. 先加锁再解锁 + 明文密码残留 | 第 1 步用 genrsa -des3 -passout pass:$password 加密,第 3 步又用 openssl rsa -in ... -passin pass:$password 剥离密码 |
既然最终给 Nginx 用的都是无密码私钥,中间加锁再解锁纯属无用功;且命令行参数里的明文密码会永久留在 .bash_history 与 ps aux 中 |
全程使用 -nodes(OpenSSL 3.0+ 亦可写 -noenc)免密生成,配合 umask 077 与 chmod 600 *.key 从文件系统权限层锁死私钥 |
| 4. 传统 3DES 弱算法 | 显式指定 -des3 对私钥加密 |
3DES 早已被 NIST 标记废弃,在开启 FIPS 或现代安全策略的发行版上直接报错 | 支持 ECDSA P-256 (prime256v1) 与 RSA 2048/4096 + SHA-256,并在 Debian 13 下直通 TLS 1.3 |
真机复现:2020 原稿生成的证书为什么会被现代浏览器拦截?
我们在 PVE Debian 13 (10.0.0.137) 真机上运行 2020 原稿命令,并用 openssl x509 -ext subjectAltName 检查扩展字段,OpenSSL 3.5.7 直接在标准错误流打出 No extensions in certificate:
=== [1. DEBIAN 13 & OPENSSL 3.5 ENVIRONMENT] ===
Linux localhost 6.12.96+deb13-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.96-1 (2026-07-20) x86_64 GNU/Linux
OpenSSL 3.5.7 9 Jun 2026 (Library: OpenSSL 3.5.7 9 Jun 2026)
nginx version: nginx/1.26.3
=== [2. REPRODUCE 2020 LEGACY SCRIPT FAILURE (NO SAN EXTENSION)] ===
--- Legacy Cert Extensions (Notice NO Subject Alternative Name!) ---
subject=C=CN, ST=GD, L=SZ, O=Legacy, CN=zero-g.internal
No extensions in certificate
二、 2026 场景选型:什么时候该自签?什么时候用自动化工具?
在动手写脚本前,先建立清晰的工程边界:
- 公网真实域名(如
lateresonance.com):- 绝不自签,统一使用
acme.sh(DNS API 自动续期泛域名证书) 或 Caddy 自动向 Let's Encrypt / ZeroSSL 申请免费可信证书。
- 绝不自签,统一使用
- 本机或团队极速开发调试(macOS / Linux 本地环境):
- 首推开源神器
mkcert(mkcert -install && mkcert zero-g.internal "*.zero-g.internal" 10.0.0.137 127.0.0.1),自动注入系统与浏览器信任库。
- 首推开源神器
- 无外网隔离生产内网 / Kubernetes Webhook / 嵌入式网关 / PVE 内部节点:
- 环境只有基础
openssl二进制,此时就轮到我们下面的 2026 重制版 OpenSSL 3.x 方案 登场了!
- 环境只有基础
三、 方案 A:OpenSSL 3.x 单行极速自签(-addext 现代语法)
如果你只需要临时起一个带 HTTPS 的测试容器或 Webhook,在 OpenSSL 1.1.1+ / 3.x 中根本不需要写临时配置文件,直接用 -addext 即可在一行命令内生成带 ECDSA P-256 椭圆曲线密钥 + 多域名与 IP SAN 扩展 的合规证书:
# 一行生成 ECDSA P-256 私钥 + 带域名与内网 IP SAN 扩展的自签名证书:
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
-keyout fast.key -out fast.crt -days 365 -sha256 \
-subj "/CN=zero-g.internal" \
-addext "subjectAltName=DNS:zero-g.internal,DNS:*.zero-g.internal,IP:10.0.0.137,IP:127.0.0.1"
Debian 13 真机检验输出:
=== [3. 2026 REMASTERED ONE-LINER (OPENSSL 3.x -addext)] ===
subject=CN=zero-g.internal
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2c5fA6ys2214xRRXl64jcyrpnMnk
qKF2ajRYEHyar9EQc/KK2wCJNiHe2nuKMO8ayL5mXcgBqpEbp/Vce+PZJg==
X509v3 Subject Alternative Name:
DNS:zero-g.internal, DNS:*.zero-g.internal, IP Address:10.0.0.137, IP Address:127.0.0.1
四、 方案 B:企业内网生产级重构脚本 gen_san_cert.sh(私有 Root CA + SAN)
在内网有多台微服务、Proxmox VE、GitLab 或 Harbor 时,如果每台机器各自自签一张单证书,运维人员就得把几十张证书逐一导入电脑信任库。
正确的架构是**“两级签发体系”**:
- 脚本首次运行时自动初始化一套 10 年期私有根证书(
ca.crt/ca.key),团队成员只需将ca.crt导入系统钥匙串信任一次; - 随后无论签发多少个内网域名或 IP 的业务证书(
397天有效期),只要是由这把ca.key签出的,所有电脑和浏览器自动 100% 绿锁通过!
完整可复用脚本:gen_san_cert.sh
#!/usr/bin/env bash
set -euo pipefail
DOMAIN="${1:-zero-g.internal}"
EXTRA_IP="${2:-127.0.0.1}"
OUT_DIR="${3:-./certs}"
DAYS_CA=3650
DAYS_CERT=397
umask 077
mkdir -p "$OUT_DIR"
cd "$OUT_DIR"
# 1. 初始化私有 Root CA(若已存在则直接复用,免去每次重复导入根证书)
if [[ ! -f ca.key || ! -f ca.crt ]]; then
echo "[+] Initializing Private Root CA..."
openssl genrsa -out ca.key 4096 2>/dev/null
openssl req -x509 -new -nodes -key ca.key -sha256 -days "$DAYS_CA" \
-subj "/C=CN/ST=Guangdong/L=Shenzhen/O=Zero-G Internal CA/CN=Zero-G Root CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-out ca.crt
fi
# 2. 生成服务端私钥与 CSR(全程 -nodes 免交互,杜绝命令行明文密码泄露)
echo "[+] Generating Server Key & CSR for ${DOMAIN} (IP: ${EXTRA_IP})..."
openssl genrsa -out "${DOMAIN}.key" 2048 2>/dev/null
openssl req -new -key "${DOMAIN}.key" \
-subj "/C=CN/ST=Guangdong/L=Shenzhen/O=Zero-G Space/CN=${DOMAIN}" \
-out "${DOMAIN}.csr"
# 3. 注入 X.509 v3 SAN (Subject Alternative Name) 与 EKU 扩展
cat > "${DOMAIN}.ext" <<EXT
authorityKeyIdentifier = keyid,issuer
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth,clientAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${DOMAIN}
DNS.2 = *.${DOMAIN}
DNS.3 = localhost
IP.1 = ${EXTRA_IP}
IP.2 = 127.0.0.1
EXT
# 4. 使用私有 Root CA 签发服务器证书(严格控制在 Apple/Chrome 398 天上限内)
openssl x509 -req -in "${DOMAIN}.csr" -CA ca.crt -CAkey ca.key -CAcreateserial \
-out "${DOMAIN}.crt" -days "$DAYS_CERT" -sha256 -extfile "${DOMAIN}.ext" 2>/dev/null
# 5. 组装完整证书链 (Fullchain) 并收紧私钥权限
cat "${DOMAIN}.crt" ca.crt > "${DOMAIN}.fullchain.crt"
rm -f "${DOMAIN}.csr" "${DOMAIN}.ext"
chmod 600 ./*.key
chmod 644 ./*.crt
echo "[✓] Certificate successfully issued in: $(pwd)"
五、 PVE Debian 13 真机签发与 Nginx TLS 1.3 双路验证
我们在 PVE Debian 13 (10.0.0.137) 上执行 ./gen_san_cert.sh "zero-g.internal" "10.0.0.137" /tmp/ssl-lab/out,来看真实的证书结构与 Nginx 挂载测试结果:
1. 证书文件权限与 X.509 v3 扩展核验
可以看到所有 .key 私钥权限均被严格锁死为 0600 (-rw-------),且 CA:FALSE、TLS Web Server Authentication 与 DNS + IP 混合 SAN 扩展一应俱全:
=== [4. 2026 REMASTERED PRIVATE ROOT CA + SAN SCRIPT (gen_san_cert.sh)] ===
[+] Initializing Private Root CA...
[+] Generating Server Key & CSR for zero-g.internal (IP: 10.0.0.137)...
[✓] Certificate successfully issued in: /tmp/ssl-lab/out
--- File Permissions & X.509 v3 Extensions ---
-rw-r--r-- 1 codex codex 2061 Nov 4 12:07 ca.crt
-rw------- 1 codex codex 3272 Nov 4 12:07 ca.key
-rw-r--r-- 1 codex codex 1842 Nov 4 12:07 zero-g.internal.crt
-rw-r--r-- 1 codex codex 3903 Nov 4 12:07 zero-g.internal.fullchain.crt
-rw------- 1 codex codex 1704 Nov 4 12:07 zero-g.internal.key
subject=C=CN, ST=Guangdong, L=Shenzhen, O=Zero-G Space, CN=zero-g.internal
issuer=C=CN, ST=Guangdong, L=Shenzhen, O=Zero-G Internal CA, CN=Zero-G Root CA
notBefore=Nov 4 12:07:50 2026 GMT
notAfter=Dec 6 12:07:50 2027 GMT
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication, TLS Web Client Authentication
X509v3 Subject Alternative Name:
DNS:zero-g.internal, DNS:*.zero-g.internal, DNS:localhost, IP Address:10.0.0.137, IP Address:127.0.0.1
2. 挂载至 Nginx 1.26.3 并分别通过「域名」与「内网 IP」请求验证
我们将生成的 zero-g.internal.fullchain.crt 挂载到 Debian 13 的 Nginx 上,随后在不加 -k(即开启严格证书校验,仅指定 --cacert ca.crt)的前提下,分别用域名 https://zero-g.internal/ 和纯 IP https://10.0.0.137/ 发起访问:
=== [5. MOUNT ON NGINX & VERIFY TLS 1.3 VIA BOTH DOMAIN AND IP] ===
--- [Test A] curl via Domain (https://zero-g.internal) with Root CA ---
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519MLKEM768 / RSASSA-PSS
* Server certificate:
* subject: C=CN; ST=Guangdong; L=Shenzhen; O=Zero-G Space; CN=zero-g.internal
* issuer: C=CN; ST=Guangdong; L=Shenzhen; O=Zero-G Internal CA; CN=Zero-G Root CA
< HTTP/1.1 200 OK
HTTPS Verified OK! Host=zero-g.internal | Proto=TLSv1.3 | Cipher=TLS_AES_256_GCM_SHA384
--- [Test B] curl via Direct IP (https://10.0.0.137) with Root CA ---
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519MLKEM768 / RSASSA-PSS
* Server certificate:
* subject: C=CN; ST=Guangdong; L=Shenzhen; O=Zero-G Space; CN=zero-g.internal
* issuer: C=CN; ST=Guangdong; L=Shenzhen; O=Zero-G Internal CA; CN=Zero-G Root CA
< HTTP/1.1 200 OK
HTTPS Verified OK! Host=10.0.0.137 | Proto=TLSv1.3 | Cipher=TLS_AES_256_GCM_SHA384
[!IMPORTANT]
Debian 13 真机彩蛋:后量子密码学(PQC)已悄然降临!
仔细看上面curl -v的第一行握手输出:
SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519MLKEM768 / RSASSA-PSS
在 Debian 13 内置的 OpenSSL3.5.7中,TLS 1.3 默认密钥交换已经从传统的纯X25519自动演进为X25519MLKEM768(结合经典椭圆曲线 X25519 与 NIST FIPS 203 标准晶格抗量子算法 ML-KEM-768 的混合密钥封装机制)!从 2020 年的-des3到 2026 年真机上的X25519MLKEM768,这正是亲手实践重制带给我们的时代回响。