跳转到内容

SSL 证书管理

HTTPS 已经成为现代 Web 服务的基本要求。本文介绍如何在 EL 系发行版上获取、配置和管理 SSL/TLS 证书,包括免费的 Let’s Encrypt 证书和内部使用的自签名证书。

SSL 证书的信任建立在证书链之上:

根证书 (Root CA)
└── 中间证书 (Intermediate CA)
└── 服务器证书 (Server Certificate)

浏览器信任根证书颁发机构(CA),根 CA 签发中间证书,中间证书签发你的服务器证书。配置服务器时,通常需要提供服务器证书 + 中间证书的完整链。

扩展名说明
.pemBase64 编码的证书/密钥(最常见)
.crt / .cer证书文件(通常是 PEM 格式)
.key私钥文件
.csr证书签名请求
.p12 / .pfxPKCS#12 格式(含证书+私钥)

Let’s Encrypt 提供免费的 DV(域名验证)证书,有效期 90 天,支持自动续期。

Terminal window
# 安装 certbot 及对应的 Web 服务器插件
sudo dnf install epel-release -y
sudo dnf install certbot -y
# 根据 Web 服务器选择插件
sudo dnf install python3-certbot-nginx -y # Nginx
# 或
sudo dnf install python3-certbot-apache -y # Apache
Terminal window
# 自动获取并配置(推荐)
# certbot 会自动修改 Nginx 配置并重载
sudo certbot --nginx -d example.com -d www.example.com
# 交互过程中需要:
# 1. 输入邮箱(用于到期提醒)
# 2. 同意服务条款
# 3. 选择是否将 HTTP 重定向到 HTTPS
Terminal window
sudo certbot --apache -d example.com -d www.example.com

如果你想手动配置 Web 服务器:

Terminal window
# Standalone 模式(需要临时占用 80 端口)
sudo certbot certonly --standalone -d example.com -d www.example.com
# Webroot 模式(Web 服务器保持运行)
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com

证书文件默认保存在 /etc/letsencrypt/live/example.com/ 下:

/etc/letsencrypt/live/example.com/
├── cert.pem # 服务器证书
├── chain.pem # 中间证书链
├── fullchain.pem # 完整证书链(cert + chain)
└── privkey.pem # 私钥
Terminal window
sudo tee /etc/nginx/conf.d/example.com.conf > /dev/null <<'EOF'
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# SSL 安全配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS(启用后浏览器将强制使用 HTTPS)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
# 会话缓存
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
EOF
sudo nginx -t && sudo systemctl reload nginx

通配符证书(*.example.com)需要 DNS 验证:

Terminal window
# 使用 DNS 验证获取通配符证书
sudo certbot certonly \
--manual \
--preferred-challenges dns \
-d "example.com" \
-d "*.example.com"
# certbot 会提示你添加一条 _acme-challenge TXT 记录
# 在 DNS 服务商处添加后等待生效,再继续

对于自动化场景,可使用 DNS 插件:

Terminal window
# 以 Cloudflare 为例
sudo dnf install python3-certbot-dns-cloudflare -y
# 创建 API 凭据文件
sudo mkdir -p /etc/letsencrypt
sudo tee /etc/letsencrypt/cloudflare.ini > /dev/null <<'EOF'
dns_cloudflare_api_token = your_cloudflare_api_token
EOF
sudo chmod 600 /etc/letsencrypt/cloudflare.ini
# 自动获取通配符证书
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d "example.com" \
-d "*.example.com"

常用 DNS 插件:

Terminal window
# 阿里云 DNS(第三方插件)
pip install certbot-dns-aliyun
# Cloudflare
dnf install python3-certbot-dns-cloudflare
# Route53 (AWS)
dnf install python3-certbot-dns-route53

Certbot 安装后默认配置了 systemd timer 来自动续期:

Terminal window
# 查看续期定时器状态
systemctl list-timers certbot*
# 手动测试续期(不实际执行)
sudo certbot renew --dry-run
# 如果定时器不存在,手动创建
sudo tee /etc/systemd/system/certbot-renew.timer > /dev/null <<'EOF'
[Unit]
Description=Certbot 自动续期定时器
[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo tee /etc/systemd/system/certbot-renew.service > /dev/null <<'EOF'
[Unit]
Description=Certbot 自动续期
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renew.timer

--deploy-hook 确保证书更新后自动重载 Web 服务器。

Terminal window
# 列出所有证书
sudo certbot certificates
# 删除证书
sudo certbot delete --cert-name example.com
# 扩展证书域名
sudo certbot certonly --nginx \
--cert-name example.com \
-d example.com \
-d www.example.com \
-d api.example.com

自签名证书适用于内网服务、开发测试等不需要公信力的场景。

Terminal window
# 一条命令生成自签名证书(有效期 3650 天)
openssl req -x509 -nodes -days 3650 \
-newkey rsa:2048 \
-keyout /etc/pki/tls/private/selfsigned.key \
-out /etc/pki/tls/certs/selfsigned.crt \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=internal.example.com"
# 设置权限
sudo chmod 600 /etc/pki/tls/private/selfsigned.key
sudo chmod 644 /etc/pki/tls/certs/selfsigned.crt

对于多台内部服务器,建议创建自己的 CA,签发的证书统一受信:

Terminal window
# 1. 创建 CA 私钥和证书
mkdir -p /etc/pki/internal-ca
cd /etc/pki/internal-ca
# 生成 CA 私钥
openssl genrsa -aes256 -out ca.key 4096
# 生成 CA 证书(有效期 10 年)
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-out ca.crt \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=MyCompany Internal CA"
# 2. 为服务器生成证书
# 生成服务器私钥
openssl genrsa -out server.key 2048
# 创建证书签名请求 (CSR)
openssl req -new -key server.key \
-out server.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=app.internal.example.com"
# 创建扩展配置(支持 SAN)
tee server-ext.cnf > /dev/null <<'EOF'
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = app.internal.example.com
DNS.2 = *.internal.example.com
IP.1 = 192.168.1.10
EOF
# 使用 CA 签发服务器证书
openssl x509 -req -in server.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 825 -sha256 \
-extfile server-ext.cnf
# 3. 在客户端机器上信任内部 CA
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/mycompany-ca.crt
sudo update-ca-trust
Terminal window
# 查看证书详情
openssl x509 -in /etc/pki/tls/certs/selfsigned.crt -text -noout
# 只看关键信息
openssl x509 -in cert.pem -noout -subject -issuer -dates
# 查看远程服务器的证书
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | \
openssl x509 -text -noout
# 只看过期时间
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -dates
Terminal window
# 验证证书与私钥是否匹配
openssl x509 -noout -modulus -in cert.pem | openssl md5
openssl rsa -noout -modulus -in key.pem | openssl md5
# 两个 MD5 值应相同
# 验证证书链完整性
openssl verify -CAfile ca.crt -untrusted intermediate.crt server.crt
# 测试 HTTPS 连接
curl -vI https://example.com 2>&1 | grep -A 5 "SSL connection"

问题 1:证书链不完整

Terminal window
# 症状:浏览器提示"证书不受信任"但证书本身未过期
# 检查:
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>&1 | \
grep -i "verify"
# 解决:确保配置了完整证书链
# Nginx 中 ssl_certificate 应使用 fullchain.pem 而非 cert.pem
cat cert.pem intermediate.pem > fullchain.pem

问题 2:证书过期

Terminal window
# 检查证书过期时间
echo | openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -noout -enddate
# 批量检查多个域名
for domain in example.com api.example.com www.example.com; do
expiry=$(echo | openssl s_client -connect ${domain}:443 -servername ${domain} 2>/dev/null | \
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
echo "${domain}: ${expiry}"
done
# 检查 certbot 续期是否正常
sudo certbot renew --dry-run
journalctl -u certbot-renew

问题 3:协议或密码套件不兼容

Terminal window
# 测试支持的 TLS 版本
openssl s_client -connect example.com:443 -tls1_2 < /dev/null
openssl s_client -connect example.com:443 -tls1_3 < /dev/null
# 列出服务器支持的密码套件(需要 nmap)
nmap --script ssl-enum-ciphers -p 443 example.com

问题 4:Nginx 配置错误

Terminal window
# 检查配置语法
sudo nginx -t
# 常见错误:
# 1. 私钥与证书不匹配
# 2. 文件路径错误
# 3. 权限问题(私钥文件权限应为 600)
ls -la /etc/letsencrypt/live/example.com/

问题 5:SELinux 阻止访问证书文件

Terminal window
# 检查 SELinux 审计日志
ausearch -m avc -ts recent | grep ssl
# 恢复正确的 SELinux 上下文
sudo restorecon -Rv /etc/letsencrypt/
sudo restorecon -Rv /etc/pki/tls/
/usr/local/bin/check_ssl.sh
#!/bin/bash
# 监控 SSL 证书过期时间
DOMAINS=(
"example.com"
"api.example.com"
"www.example.com"
)
WARN_DAYS=30
MAILTO="admin@example.com"
for domain in "${DOMAINS[@]}"; do
expiry_date=$(echo | openssl s_client -connect "${domain}:443" \
-servername "$domain" 2>/dev/null | \
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -z "$expiry_date" ]; then
echo "[错误] 无法获取 ${domain} 的证书信息"
continue
fi
expiry_epoch=$(date -d "$expiry_date" +%s)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
if [ "$days_left" -le 0 ]; then
MSG="[严重] ${domain} 证书已过期!"
echo "$MSG"
echo "$MSG" | mail -s "SSL 证书过期告警" "$MAILTO"
elif [ "$days_left" -le "$WARN_DAYS" ]; then
MSG="[警告] ${domain} 证书将在 ${days_left} 天后过期 (${expiry_date})"
echo "$MSG"
echo "$MSG" | mail -s "SSL 证书即将过期" "$MAILTO"
else
echo "[正常] ${domain} 证书剩余 ${days_left} 天 (${expiry_date})"
fi
done
Terminal window
chmod +x /usr/local/bin/check_ssl.sh
# 加入 cron,每天检查一次
echo '0 9 * * * /usr/local/bin/check_ssl.sh >> /var/log/ssl_check.log 2>&1' | sudo crontab -
Terminal window
# 生成 DH 参数(增强前向保密)
sudo openssl dhparam -out /etc/pki/tls/dhparam.pem 2048
# Nginx 中引用
# ssl_dhparam /etc/pki/tls/dhparam.pem;
  • 仅启用 TLS 1.2 和 TLS 1.3(禁用 SSLv3、TLS 1.0、TLS 1.1)
  • 使用强密码套件,禁用弱密码
  • 配置完整的证书链
  • 启用 HSTS 头
  • 启用 OCSP Stapling
  • 私钥文件权限设为 600
  • 配置自动续期并验证其正常工作
  • 定期检查证书过期时间
  • 使用 SSL Labs 在线测试评估配置

配置完成后,使用以下工具测试你的 SSL 配置质量:

Terminal window
# SSL Labs(Web)
# 访问 https://www.ssllabs.com/ssltest/ 输入域名
# 命令行测试
curl -I https://example.com
curl -vI https://example.com 2>&1 | grep -E "SSL|TLS|subject|expire"

目标是在 SSL Labs 测试中获得 A 或 A+ 评级。