Skip to content

证书管理与 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}")

参考:Python:获取域名ssl证书信息和到期时间

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 个新订单

5. 相关资源