4. 为内部 Web 服务器签发服务器证书

为内部 Web 服务器签发服务器证书 学习卡片

任务拆解:从私钥到可用的 HTTPS 站点

本案例的目标是让内部 Web 服务器通过 HTTPS 对外提供服务。完整链路包含四个关键对象:服务器私钥、CSR(证书签名请求)、由中间 CA 签发的服务器证书、以及 Nginx 的 TLS 配置。核心约束是证书必须包含 SAN 扩展,且 SAN 中的 DNS 名称要与浏览器实际访问的地址一致,否则会触发证书名称不匹配告警。

内部 Web 服务器证书签发与部署链路 ① 服务器侧生成 server.key RSA 2048 位私钥 server.csr 含 CN 与 SAN 请求 ② 中间 CA 签发 intermediate.crt 中间 CA 证书 + 私钥 server.crt 含 SAN 扩展的服务器证书 ③ Nginx 部署 ssl_certificate server.crt + 中间 CA 链 ssl_certificate_key server.key ④ 验证与访问 openssl x509 -text 检查 SAN openssl verify 验证证书链 curl -vk https://web.internal 关键约束 SAN 中的 DNS 名称必须与浏览器访问地址一致;Nginx 的 ssl_certificate 需包含服务器证书和中间 CA 证书的完整链 私钥文件权限应设为 600,且仅 root 或 Nginx 运行用户可读

生成服务器私钥与 CSR

在 Web 服务器上创建私钥和 CSR。私钥使用 RSA 2048 位,CSR 中通过 -addext 直接写入 SAN 扩展,避免后续签发时遗漏。

# 1. 生成服务器私钥(不加密,便于 Nginx 自动加载)
openssl genrsa -out server.key 2048

# 2. 生成 CSR,同时写入 SAN 扩展
openssl req -new -key server.key -out server.csr \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=Internal/CN=web.internal" \
  -addext "subjectAltName=DNS:web.internal,DNS:www.web.internal,IP:192.168.10.20"

# 3. 确认 CSR 中的 SAN 内容
openssl req -in server.csr -noout -text | grep -A1 "Subject Alternative Name"

执行第 3 条命令后,应看到 DNS:web.internal, DNS:www.web.internal, IP Address:192.168.10.20。如果 SAN 为空,说明 -addext 未生效,需要检查 OpenSSL 版本是否支持该参数(1.1.1 及以上)。

由中间 CA 签发服务器证书

使用中间 CA 的证书和私钥对 CSR 进行签发。签发时通过 -extfile 指定扩展文件,确保 SAN 被写入最终证书。

# 创建扩展配置文件
cat > server_ext.cnf <<'EOF'
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:web.internal, DNS:www.web.internal, IP:192.168.10.20
EOF

# 用中间 CA 签发,有效期 825 天
openssl x509 -req -in server.csr \
  -CA intermediate.crt -CAkey intermediate.key -CAcreateserial \
  -out server.crt -days 825 -sha256 -extfile server_ext.cnf

# 验证签发结果
openssl x509 -in server.crt -noout -text | grep -E "Issuer:|Subject:|DNS:|IP Address:"

签发结果中,Issuer 应为中间 CA 的 Subject,Subject 为 CN=web.internal,且 SAN 中应包含三个条目。若 SAN 缺失,浏览器会直接报 NET::ERR_CERT_COMMON_NAME_INVALID。

配置 Nginx 并测试 HTTPS

将证书链文件合并后配置到 Nginx。注意 ssl_certificate 需要包含服务器证书和中间 CA 证书,顺序为服务器证书在前、中间 CA 在后。

# 合并证书链
cat server.crt intermediate.crt > fullchain.crt

# 设置私钥权限
chmod 600 server.key
chown root:root server.key

# Nginx 配置片段
server {
    listen 443 ssl;
    server_name web.internal www.web.internal;

    ssl_certificate     /etc/nginx/ssl/fullchain.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_protocols       TLSv1.2 TLSv1.3;

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

配置完成后执行 nginx -t 检查语法,再 nginx -s reload 重载。使用 curl -vk https://web.internal 观察握手过程,重点确认 SSL certificate verify ok 以及证书链中是否包含中间 CA。

如果浏览器仍提示不受信任,通常是因为根 CA 证书未导入到客户端信任库。内部 PKI 场景下,需要将根 CA 证书分发到所有需要访问该站点的客户端,并导入到系统或浏览器的受信任根证书颁发机构存储中。
私钥文件一旦泄露,攻击者可以冒充该服务器。生产环境中建议对私钥进行加密存储,或使用 HSM/TPM 等硬件保护方案。本案例为便于 Nginx 自动加载使用了未加密私钥,仅适用于受控的内部环境。