证书管理与 SSL/TLS
1. Let's Encrypt 免费证书
1.1 Certbot 简介
Certbot 是 Let's Encrypt 官方推荐的证书管理工具,可以自动申请、续期免费的 SSL/TLS 证书。
1.2 安装 Certbot
Windows
bash
# 下载 Certbot 独立安装包
# https://dl.eff.org/certbot-beta-installer-win_amd64.exe
Linux (Ubuntu/Debian)
bash
sudo apt update
sudo apt install certbot python3-certbot-nginx
Linux (CentOS/RHEL)
bash
sudo yum install certbot python3-certbot-nginx
1.3 申请证书
独立模式(需要停止 Web 服务器)
bash
sudo certbot certonly --standalone -d example.com -d www.example.com
Nginx 模式(自动配置)
bash
sudo certbot --nginx -d example.com -d www.example.com
Webroot 模式(不停止服务)
bash
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
1.4 自动续期
Let's Encrypt 证书有效期为 90 天,需要定期续期。
测试续期
bash
sudo certbot renew --dry-run
配置自动续期
Certbot 安装后会自动创建定时任务,也可以手动配置:
Linux Cron:
bash
# 编辑 crontab
sudo crontab -e
# 添加每天检查续期的任务
0 0 * * * certbot renew --quiet
Windows 任务计划程序:
创建定时任务运行:
bash
certbot renew --quiet
1.5 证书文件位置
证书文件默认存放在:
text
/etc/letsencrypt/live/example.com/
├── cert.pem # 服务器证书
├── chain.pem # 中间证书
├── fullchain.pem # 完整证书链(推荐使用)
└── privkey.pem # 私钥
1.6 Nginx 配置示例
nginx
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_prefer_server_ciphers on;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# HSTS
add_header Strict-Transport-Security "max-age=31536000" always;
location / {
root /var/www/html;
index index.html;
}
}
# HTTP 重定向到 HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
2. 证书信息查询
2.1 使用 OpenSSL 查询
查看本地证书信息
bash
openssl x509 -in cert.pem -noout -text
查看证书到期时间
bash
openssl x509 -in cert.pem -noout -dates
查询远程服务器证书
bash
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
2.2 使用 Python 查询
python
import ssl
import socket
from datetime import datetime
def get_cert_info(hostname, port=443):
"""获取域名 SSL 证书信息"""
context = ssl.create_default_context()
with socket.create_connection((hostname, port), timeout=10) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
cert = ssock.getpeercert()
# 解析证书信息
subject = dict(x[0] for x in cert['subject'])
issuer = dict(x[0] for x in cert['issuer'])
# 解析到期时间
not_after = cert['notAfter']
expire_date = datetime.strptime(not_after, '%b %d %H:%M:%S %Y %Z')
days_remaining = (expire_date - datetime.now()).days
return {
'subject': subject.get('commonName'),
'issuer': issuer.get('organizationName'),
'not_before': cert['notBefore'],
'not_after': cert['notAfter'],
'days_remaining': days_remaining
}
# 使用示例
try:
info = get_cert_info('example.com')
print(f"域名: {info['subject']}")
print(f"颁发者: {info['issuer']}")
print(f"有效期至: {info['not_after']}")
print(f"剩余天数: {info['days_remaining']}")
except Exception as e:
print(f"错误: {e}")
2.3 批量检查证书
python
import ssl
import socket
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
def check_cert(hostname):
"""检查单个域名证书"""
try:
context = ssl.create_default_context()
with socket.create_connection((hostname, 443), timeout=5) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
cert = ssock.getpeercert()
expire_date = datetime.strptime(
cert['notAfter'],
'%b %d %H:%M:%S %Y %Z'
)
days = (expire_date - datetime.now()).days
return {
'hostname': hostname,
'days_remaining': days,
'status': 'ok' if days > 30 else 'warning'
}
except Exception as e:
return {
'hostname': hostname,
'error': str(e),
'status': 'error'
}
# 批量检查
domains = ['example.com', 'example.org', 'example.net']
with ThreadPoolExecutor(max_workers=5) as executor:
results = list(executor.map(check_cert, domains))
for result in results:
if result['status'] == 'error':
print(f"{result['hostname']}: 错误 - {result['error']}")
else:
print(f"{result['hostname']}: 剩余 {result['days_remaining']} 天 ({result['status']})")
3. 证书管理最佳实践
3.1 证书监控
设置证书过期监控,提前 30 天收到提醒。
python
import smtplib
from email.mime.text import MIMEText
def send_alert(domain, days_remaining):
"""发送证书过期警告邮件"""
if days_remaining < 30:
msg = MIMEText(f"域名 {domain} 的证书将在 {days_remaining} 天后过期!")
msg['Subject'] = f'证书过期警告: {domain}'
msg['From'] = 'monitor@example.com'
msg['To'] = 'admin@example.com'
with smtplib.SMTP('localhost') as server:
server.send_message(msg)
3.2 多域名证书
使用 SAN(Subject Alternative Name)证书支持多个域名:
bash
sudo certbot certonly --standalone \
-d example.com \
-d www.example.com \
-d api.example.com \
-d admin.example.com
3.3 泛域名证书
申请泛域名证书需要 DNS 验证:
bash
sudo certbot certonly --manual \
--preferred-challenges dns \
-d example.com \
-d *.example.com
按照提示添加 DNS TXT 记录后完成验证。
4. 常见问题
4.1 证书续期失败
检查 Web 服务器是否可以访问 .well-known/acme-challenge/
目录:
nginx
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/html;
}
4.2 证书不受信任
确保使用 fullchain.pem
而不是 cert.pem
,前者包含完整的证书链。
4.3 Rate Limit
Let's Encrypt 有速率限制:
- 每个域名每周最多 50 个证书
- 每个账户每 3 小时最多 300 个新订单