深色模式
TLS/证书管理:申请、部署与自动续期
摘要:证书事故几乎都出在"忘了续期"和"链不完整"。本文给出从申请、部署、校验到自动续期的完整命令链,并补充内网自签 CA 的做法。
适用环境
bash
cat /etc/os-release
command -v nginx && nginx -v
openssl version
command -v certbot && certbot --version1
2
3
4
2
3
4
操作步骤
1. 先用 OpenSSL 生成私钥与 CSR(自购/内网 CA 场景)
bash
mkdir -p /etc/ssl/private && chmod 700 /etc/ssl/private
openssl genrsa -out /etc/ssl/private/example.com.key 2048
openssl req -new -key /etc/ssl/private/example.com.key \
-subj "/CN=example.com" \
-addext "subjectAltName=DNS:example.com,DNS:www.example.com" \
-out /etc/ssl/example.com.csr
openssl req -in /etc/ssl/example.com.csr -noout -text | grep -A1 "Subject Alternative"1
2
3
4
5
6
7
2
3
4
5
6
7
2. Let's Encrypt:HTTP-01 验证申请证书
bash
# RHEL/CentOS
dnf install -y certbot python3-certbot-nginx
# Debian/Ubuntu
apt-get update && apt-get install -y certbot python3-certbot-nginx
certbot --nginx -d example.com -d www.example.com \
--agree-tos -m ops@example.com --no-eff-email1
2
3
4
5
6
7
2
3
4
5
6
7
DNS-01 方式(通配符或内网不出网时用):
bash
certbot certonly --manual --preferred-challenges dns \
-d "*.example.com" -d example.com1
2
2
按提示把 _acme-challenge 的 TXT 记录加到域名解析,确认生效后再回车:
bash
dig +short TXT _acme-challenge.example.com1
3. 部署到 Nginx(关键参数一次到位)
nginx
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
add_header Strict-Transport-Security "max-age=31536000" always;
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bash
nginx -t && systemctl reload nginx1
4. 私钥权限与保护
bash
chmod 600 /etc/letsencrypt/live/example.com/privkey.pem
chown root:root /etc/letsencrypt/live/example.com/privkey.pem
ls -l /etc/letsencrypt/live/example.com/1
2
3
2
3
私钥一旦泄露必须立即吊销并重签
私钥不会"过期作废",把它提交进 Git 或打进镜像等于永久泄露。用 git-secrets 之类的工具在提交前拦截。
5. 自动续期:不要只依赖 cron 的静默成功
bash
systemctl list-timers | grep certbot # systemd 系统通常自带
certbot renew --dry-run # 必须先做演练1
2
2
自定义续期钩子(renewal-hooks):
bash
cat >/etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh <<'EOF'
#!/bin/bash
nginx -t && systemctl reload nginx
EOF
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh1
2
3
4
5
2
3
4
5
6. 内网自签 CA(无公网域名时)
bash
openssl genrsa -out /etc/ssl/private/myca.key 4096
openssl req -x509 -new -nodes -key /etc/ssl/private/myca.key -sha256 -days 3650 \
-subj "/CN=Internal-CA" -out /etc/ssl/certs/myca.crt
openssl x509 -req -in /etc/ssl/example.csr -CA /etc/ssl/certs/myca.crt \
-CAkey /etc/ssl/private/myca.key -CAcreateserial \
-days 825 -sha256 -extfile <(printf "subjectAltName=DNS:app.internal\n") \
-out /etc/ssl/certs/app.internal.crt1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
客户端需把 myca.crt 加入信任库:
bash
cp /etc/ssl/certs/myca.crt /usr/local/share/ca-certificates/
update-ca-certificates1
2
2
验证
bash
# 握手与链完整性
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
# 剩余天数
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -enddate
# 协议与套件
nmap --script ssl-enum-ciphers -p 443 example.com 2>/dev/null | head -20
curl -vI https://example.com 2>&1 | grep -i "TLS\|subject"1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
判定标准:链完整(Verify return code: 0)、剩余天数 > 30、仅 TLSv1.2/1.3、--dry-run 成功。
常见坑
证书过期导致全线业务中断
90 天有效期的证书必须自动续期 + 到期监控。不要依赖人肉提醒。
只配了 ssl_certificate 用的是证书而非 fullchain
部分客户端(尤其 Java/Android)会因缺中间证书报不可信。始终使用 fullchain.pem。
续期成功但 Nginx 没 reload
进程仍用旧证书文件句柄。必须配 renewal-hooks/deploy 或 systemd 的 ExecStartPost。
私钥进 Git 仓库
即使后续删除,历史记录仍在。应立即吊销重签,并轮换所有使用该私钥的服务。
自签证书有效期过长
行业趋势是缩短有效期(目前公开 CA 普遍 90 天左右)。自签也不要给 10 年,建议不超过 825 天并建立轮换流程。