Nginx HTTPS配置与证书自动续期:Let‘s Encrypt实战

一、概述

1.1 背景介绍

2018年Chrome开始把所有HTTP网站标记为"不安全",这个改变直接推动了整个互联网向HTTPS迁移。到2025年,HTTPS已经是网站的标配,没有HTTPS不仅影响用户信任,还会影响SEO排名,很多现代Web API(如地理定位、Service Worker)也必须在HTTPS环境下才能使用。

Let's Encrypt是这场变革的最大功臣。2015年它开始提供免费的SSL/TLS证书,彻底打破了证书费用的门槛。我还记得以前买个单域名证书要几百块,通配符证书要几千块,现在全都免费了。Let's Encrypt的证书有效期只有90天,但配合自动续期工具,根本不用操心。

我见过太多因为证书过期导致的事故了。有一次凌晨3点被叫醒,用户反馈网站打不开,一查是证书过期了。运维同学忘记续费,证书静悄悄地过期了,没有任何告警。那次之后我就把所有项目都迁到了Let's Encrypt,配合自动续期,再也没出过这种问题。

这篇文章会讲清楚HTTPS配置的方方面面:从基础的证书申请到生产级的安全配置,从手动部署到全自动化的续期方案。

1.2 技术特点

HTTPS工作原理

复制代码
1. TCP三次握手
2. TLS握手:
   - Client Hello: 客户端发送支持的加密套件、TLS版本
   - Server Hello: 服务端选择加密套件,返回证书
   - 密钥交换: 协商对称加密密钥
   - 完成握手: 开始加密通信
3. 加密的HTTP通信

Let's Encrypt证书特点

特性 说明
价格 完全免费
有效期 90天
域名验证 DV(Domain Validation)证书
验证方式 HTTP-01、DNS-01、TLS-ALPN-01
支持通配符 是(需DNS验证)
每周限制 单域名50张/周,子域名证书不限
兼容性 支持99%+的浏览器

Certbot工具

Certbot是Let's Encrypt官方推荐的证书管理工具,支持:

  • 自动申请证书

  • 自动配置Web服务器

  • 自动续期

  • 多种验证方式

1.3 适用场景

适合Let's Encrypt的场景

  • 个人网站和博客

  • 中小企业官网

  • 开发和测试环境

  • API服务器

  • 内部系统(需要HTTPS但不对外)

不适合Let's Encrypt的场景

  • 需要EV(扩展验证)证书的金融网站

  • 需要OV(组织验证)证书显示公司信息

  • 证书有效期要求超过90天的场景

  • 离线环境(无法自动续期)

1.4 环境要求

组件 版本要求 说明
操作系统 Rocky Linux 9 / Ubuntu 24.04 LTS 64位Linux
Nginx 1.26.x / 1.27.x 需支持TLS 1.3
Certbot 2.x 最新版本
OpenSSL 3.x TLS 1.3支持
域名 已解析到服务器IP DNS生效
端口 80、443 防火墙已开放

二、详细步骤

2.1 准备工作

安装Certbot

Rocky Linux 9:

复制代码
# 安装EPEL仓库
dnf install epel-release -y

# 安装Certbot和Nginx插件
dnf install certbot python3-certbot-nginx -y

# 验证安装
certbot --version
# certbot 2.x.x

Ubuntu 24.04:

复制代码
# 安装Certbot
apt update
apt install certbot python3-certbot-nginx -y

# 验证安装
certbot --version

检查Nginx配置

复制代码
# 确保Nginx已安装并运行
nginx -v
systemctl status nginx

# 检查配置语法
nginx -t

# 确认80端口可访问
curl -I http://your-domain.com
# 应该返回200或301/302

DNS检查

复制代码
# 检查域名解析
dig +short your-domain.com
# 应该返回你的服务器IP

# 检查从互联网是否可访问
curl -I http://your-domain.com

防火墙配置

复制代码
# Rocky Linux (firewalld)
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

# Ubuntu (ufw)
ufw allow 80/tcp
ufw allow 443/tcp
ufw reload

2.2 核心配置

方式一:Certbot自动配置(推荐新手)

复制代码
# 自动申请证书并配置Nginx
certbot --nginx -d example.com -d www.example.com

# 交互过程:
# 1. 输入邮箱(用于紧急通知和续期提醒)
# 2. 同意服务条款
# 3. 选择是否重定向HTTP到HTTPS(推荐选择2)

# Certbot会自动:
# - 验证域名所有权
# - 申请证书
# - 配置Nginx
# - 设置自动续期

方式二:仅申请证书(推荐有经验的运维)

复制代码
# 只申请证书,不修改Nginx配置
certbot certonly --nginx -d example.com -d www.example.com

# 证书存放位置:
# /etc/letsencrypt/live/example.com/fullchain.pem  # 证书链
# /etc/letsencrypt/live/example.com/privkey.pem    # 私钥
# /etc/letsencrypt/live/example.com/cert.pem       # 证书
# /etc/letsencrypt/live/example.com/chain.pem      # 中间证书

方式三:Webroot验证(适合不想停服务)

复制代码
# 先在Nginx配置验证目录
# /etc/nginx/conf.d/example.conf
server {
    listen 80;
    server_name example.com www.example.com;

    # Let's Encrypt验证目录
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

# 创建验证目录
mkdir -p /var/www/certbot
nginx -s reload

# 使用webroot方式申请
certbot certonly --webroot -w /var/www/certbot -d example.com -d www.example.com

方式四:DNS验证(申请通配符证书)

复制代码
# 通配符证书必须使用DNS验证
certbot certonly --manual --preferred-challenges dns -d "*.example.com" -d example.com

# 过程中会要求添加DNS TXT记录
# _acme-challenge.example.com  TXT  "随机字符串"
# 添加后等待DNS生效,再继续

# 自动化DNS验证(以阿里云为例,需要安装插件)
pip install certbot-dns-aliyun
certbot certonly --authenticator dns-aliyun \
    --dns-aliyun-credentials /etc/letsencrypt/aliyun.ini \
    -d "*.example.com" -d example.com

生产级Nginx HTTPS配置

复制代码
# /etc/nginx/conf.d/example-ssl.conf
# 生产环境HTTPS完整配置

# HTTP重定向到HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # Let's Encrypt验证(保留)
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    # 其他请求重定向到HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS服务
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;  # Nginx 1.25.1+的新语法

    server_name example.com www.example.com;

    # ==================== SSL证书配置 ====================

    # Let's Encrypt证书
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # 可信CA(用于OCSP Stapling)
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

    # ==================== SSL协议配置 ====================

    # 只启用TLS 1.2和1.3
    ssl_protocols TLSv1.2 TLSv1.3;

    # 加密套件配置(Mozilla推荐的Intermediate配置)
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

    # 服务器优先选择加密套件
    ssl_prefer_server_ciphers off;

    # ==================== SSL会话配置 ====================

    # 会话缓存(提高性能)
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    # 禁用会话票证(更安全,但影响0-RTT)
    ssl_session_tickets off;

    # ==================== OCSP Stapling ====================

    # 启用OCSP Stapling(减少客户端验证时间)
    ssl_stapling on;
    ssl_stapling_verify on;

    # DNS解析器(用于OCSP)
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # ==================== DH参数 ====================

    # 自定义DH参数(更安全)
    # 生成命令: openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
    ssl_dhparam /etc/nginx/ssl/dhparam.pem;

    # ==================== 安全头配置 ====================

    # HSTS(HTTP严格传输安全)
    # max-age=31536000 表示一年
    # includeSubDomains 包含所有子域名
    # preload 可提交到浏览器预加载列表
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # 防止点击劫持
    add_header X-Frame-Options "SAMEORIGIN" always;

    # 防止MIME类型嗅探
    add_header X-Content-Type-Options "nosniff" always;

    # XSS过滤
    add_header X-XSS-Protection "1; mode=block" always;

    # 引用策略
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # 内容安全策略(根据实际需求调整)
    # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;

    # ==================== 站点配置 ====================

    root /var/www/html;
    index index.html;

    # 日志
    access_log /var/log/nginx/example-ssl.access.log;
    error_log /var/log/nginx/example-ssl.error.log;

    # 站点内容
    location / {
        try_files $uri $uri/ =404;
    }

    # 其他配置...
}

生成DH参数

复制代码
# 创建目录
mkdir -p /etc/nginx/ssl

# 生成DH参数(这个过程比较慢,可能需要几分钟)
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048

# 设置权限
chmod 600 /etc/nginx/ssl/dhparam.pem

2.3 启动和验证

验证配置

复制代码
# 检查Nginx配置
nginx -t

# 重载配置
nginx -s reload

# 检查监听端口
ss -tlnp | grep nginx
# 应该看到80和443端口

测试HTTPS

复制代码
# 命令行测试
curl -I https://example.com
# HTTP/2 200
# strict-transport-security: max-age=31536000; includeSubDomains; preload

# 检查证书信息
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
# notBefore=Jan  7 00:00:00 2025 GMT
# notAfter=Apr  7 23:59:59 2025 GMT

# 检查证书链
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -issuer

在线检测工具

目标是在SSL Labs获得A+评分。

三、示例代码和配置

3.1 完整配置示例

多站点HTTPS配置

复制代码
# /etc/nginx/conf.d/ssl-params.conf
# 通用SSL参数(被其他配置include)

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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

ssl_dhparam /etc/nginx/ssl/dhparam.pem;

# /etc/nginx/conf.d/security-headers.conf
# 通用安全头

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# /etc/nginx/conf.d/www.example.com.conf
# 主站配置

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://www.example.com$request_uri;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

    include /etc/nginx/conf.d/ssl-params.conf;

    # 裸域名重定向到www
    return 301 https://www.example.com$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name www.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

    include /etc/nginx/conf.d/ssl-params.conf;
    include /etc/nginx/conf.d/security-headers.conf;

    root /var/www/www.example.com;
    index index.html;

    access_log /var/log/nginx/www.example.com.access.log;
    error_log /var/log/nginx/www.example.com.error.log;

    location / {
        try_files $uri $uri/ =404;
    }
}

# /etc/nginx/conf.d/api.example.com.conf
# API站点配置

server {
    listen 80;
    listen [::]:80;
    server_name api.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name api.example.com;

    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/api.example.com/chain.pem;

    include /etc/nginx/conf.d/ssl-params.conf;
    include /etc/nginx/conf.d/security-headers.conf;

    # API特殊配置
    add_header Access-Control-Allow-Origin "*" always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

通配符证书配置

复制代码
# /etc/nginx/conf.d/wildcard.example.com.conf
# 通配符证书配置(一个证书服务所有子域名)

# 动态子域名处理
server {
    listen 80;
    listen [::]:80;
    server_name *.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name *.example.com;

    # 通配符证书
    ssl_certificate /etc/letsencrypt/live/example.com-wildcard/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com-wildcard/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com-wildcard/chain.pem;

    include /etc/nginx/conf.d/ssl-params.conf;
    include /etc/nginx/conf.d/security-headers.conf;

    # 根据子域名路由
    set $subdomain "";
    if ($host ~* ^([^.]+)\.example\.com$) {
        set $subdomain $1;
    }

    # 不同子域名不同root
    root /var/www/subdomains/$subdomain;

    location / {
        try_files $uri $uri/ =404;
    }
}

3.2 实际应用案例

案例一:自动续期配置

复制代码
# Certbot自动续期原理:
# 安装时自动创建了systemd timer或cron job
# 每天检查两次,证书有效期<30天时自动续期

# 查看systemd timer(推荐方式)
systemctl list-timers | grep certbot
# certbot.timer

# 查看timer配置
systemctl cat certbot.timer

# 手动测试续期(dry-run)
certbot renew --dry-run

# 查看证书状态
certbot certificates

自定义续期后操作(reload Nginx)

复制代码
# 方式一:使用deploy-hook
# 创建hook脚本
cat > /etc/letsencrypt/renewal-hooks/deploy/nginx-reload.sh << 'EOF'
#!/bin/bash
# 证书续期后重载Nginx
nginx -t && systemctl reload nginx
echo "$(date): Nginx reloaded after cert renewal" >> /var/log/letsencrypt/renewal.log
EOF

chmod +x /etc/letsencrypt/renewal-hooks/deploy/nginx-reload.sh

# 方式二:在certbot命令中指定
certbot renew --deploy-hook "nginx -s reload"

案例二:证书监控和告警

复制代码
#!/bin/bash
# /usr/local/bin/check_ssl_expiry.sh
# SSL证书过期检查和告警脚本

# 配置
DOMAINS=("example.com" "api.example.com" "www.example.com")
WARN_DAYS=30  # 提前30天告警
ALERT_EMAIL="ops@example.com"
SLACK_WEBHOOK="https://hooks.slack.com/services/xxx/yyy/zzz"

check_cert() {
    local domain=$1
    local port=${2:-443}

    # 获取证书过期时间
    local expiry_date=$(echo | openssl s_client -connect ${domain}:${port} -servername ${domain} 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)

    if [ -z "$expiry_date" ]; then
        echo "ERROR: Failed to get certificate for $domain"
        return 1
    fi

    # 转换为时间戳
    local expiry_epoch=$(date -d "$expiry_date" +%s)
    local now_epoch=$(date +%s)
    local days_left=$(( (expiry_epoch - now_epoch) / 86400 ))

    echo "$domain: $days_left days left (expires: $expiry_date)"

    if [ $days_left -lt $WARN_DAYS ]; then
        return 2  # 需要告警
    fi

    return 0
}

send_alert() {
    local message=$1

    # 发送邮件
    echo "$message" | mail -s "SSL Certificate Alert" $ALERT_EMAIL

    # 发送Slack通知
    curl -s -X POST -H 'Content-type: application/json' \
        --data "{\"text\": \"$message\"}" \
        $SLACK_WEBHOOK
}

# 主逻辑
echo "=== SSL Certificate Expiry Check $(date) ==="
alerts=""

for domain in "${DOMAINS[@]}"; do
    result=$(check_cert $domain)
    status=$?

    echo "$result"

    if [ $status -eq 2 ]; then
        alerts="$alerts\n$result"
    elif [ $status -eq 1 ]; then
        alerts="$alerts\nERROR: Cannot check $domain"
    fi
done

if [ -n "$alerts" ]; then
    send_alert "SSL Certificate Warning:$alerts"
fi

# 添加定时任务,每天检查
crontab -e
0 9 * * * /usr/local/bin/check_ssl_expiry.sh >> /var/log/ssl-check.log 2>&1

案例三:多服务器证书同步

复制代码
#!/bin/bash
# /usr/local/bin/sync_certs.sh
# 主服务器续期后,同步证书到其他服务器

CERT_PATH="/etc/letsencrypt/live"
REMOTE_SERVERS=("10.0.1.2" "10.0.1.3" "10.0.1.4")
REMOTE_USER="deploy"
DOMAINS=("example.com" "api.example.com")

for domain in "${DOMAINS[@]}"; do
    echo "Syncing certificates for $domain..."

    for server in "${REMOTE_SERVERS[@]}"; do
        echo "  -> $server"

        # 同步证书文件
        rsync -avz --delete \
            $CERT_PATH/$domain/ \
            $REMOTE_USER@$server:$CERT_PATH/$domain/

        # 重载远程Nginx
        ssh $REMOTE_USER@$server "sudo nginx -t && sudo systemctl reload nginx"
    done
done

echo "Certificate sync completed at $(date)"

# 在主服务器的deploy-hook中调用
cat > /etc/letsencrypt/renewal-hooks/deploy/sync-and-reload.sh << 'EOF'
#!/bin/bash
# 先重载本地Nginx
nginx -t && systemctl reload nginx

# 同步到其他服务器
/usr/local/bin/sync_certs.sh >> /var/log/cert-sync.log 2>&1
EOF

chmod +x /etc/letsencrypt/renewal-hooks/deploy/sync-and-reload.sh

案例四:Docker环境下的HTTPS配置

复制代码
# docker-compose.yml
version: '3.8'

services:
  nginx:
    image: nginx:1.27
    container_name: nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - ./certbot/www:/var/www/certbot:ro
      - ./certbot/conf:/etc/letsencrypt:ro
    depends_on:
      - app
    networks:
      - web

  certbot:
    image: certbot/certbot:latest
    container_name: certbot
    volumes:
      - ./certbot/www:/var/www/certbot
      - ./certbot/conf:/etc/letsencrypt
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
    networks:
      - web

  app:
    image: your-app:latest
    container_name: app
    networks:
      - web

networks:
  web:
    driver: bridge

# 首次申请证书(Docker环境)
docker compose run --rm certbot certonly \
    --webroot \
    --webroot-path /var/www/certbot \
    -d example.com \
    -d www.example.com \
    --email your@email.com \
    --agree-tos \
    --no-eff-email

# 重启Nginx加载证书
docker compose restart nginx

四、最佳实践和注意事项

4.1 最佳实践

1. 始终使用fullchain.pem

复制代码
# 正确:使用完整证书链
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;

# 错误:只用证书,没有中间证书
# ssl_certificate /etc/letsencrypt/live/example.com/cert.pem;
# 会导致某些客户端验证失败

2. 启用HSTS并提交Preload

复制代码
# HSTS配置
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# 提交到HSTS Preload列表:https://hstspreload.org/
# 提交后浏览器会硬编码你的域名必须使用HTTPS
# 注意:提交后很难撤销,确保所有子域名都支持HTTPS

3. 禁用旧版TLS协议

复制代码
# 只启用TLS 1.2和1.3
ssl_protocols TLSv1.2 TLSv1.3;

# 不要使用:
# ssl_protocols SSLv3 TLSv1 TLSv1.1;
# 这些协议已有已知漏洞

4. 正确配置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;
resolver_timeout 5s;

# 验证OCSP Stapling是否生效
echo | openssl s_client -connect example.com:443 -status 2>/dev/null | grep -A 17 "OCSP Response Status"
# 应该显示: OCSP Response Status: successful

5. 证书透明度(Certificate Transparency)

Let's Encrypt证书自动包含SCT(Signed Certificate Timestamp),符合Chrome的CT要求,不需要额外配置。

6. 多证书备份

复制代码
# 备份证书(虽然可以随时重新申请,但备份更安全)
tar -czf letsencrypt-backup-$(date +%Y%m%d).tar.gz \
    /etc/letsencrypt/live \
    /etc/letsencrypt/archive \
    /etc/letsencrypt/renewal

4.2 注意事项

常见错误表

错误 原因 解决方案
Challenge failed 验证目录不可访问 检查防火墙、Nginx配置
Too many requests 达到速率限制 等待1小时后重试
DNS problem 域名解析错误 检查DNS配置
Certificate not trusted 没用fullchain 使用fullchain.pem
ERR_SSL_PROTOCOL_ERROR TLS配置错误 检查ssl_protocols
HSTS导致无法访问 HTTP子域名被强制HTTPS 确保所有子域名都支持HTTPS

速率限制注意

复制代码
# Let's Encrypt速率限制:
# - 每个注册域名50张证书/周
# - 重复证书5张/周
# - 失败验证5次/小时/账户/主机名
# - 每3小时10个pending authorizations

# 测试时使用staging环境(无速率限制)
certbot certonly --staging -d test.example.com

# 测试成功后再用生产环境
certbot certonly -d test.example.com

安全注意事项

复制代码
# 1. 保护私钥
chmod 600 /etc/letsencrypt/live/*/privkey.pem
chown root:root /etc/letsencrypt/live/*/privkey.pem

# 2. 不要把证书提交到Git
echo "*.pem" >> .gitignore

# 3. 定期检查证书状态
certbot certificates

# 4. 监控证书过期
# 配置告警脚本(见案例二)

兼容性考虑

复制代码
# 如果需要支持老旧客户端(不推荐)
# 可以添加TLS 1.0和1.1支持
# ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;

# 添加更多加密套件
# ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:!DSS';

五、故障排查和监控

5.1 故障排查

问题一:证书申请失败

复制代码
# 查看详细错误
certbot certonly --nginx -d example.com -v

# 常见原因和解决方案:

# 1. HTTP验证失败
# 检查80端口是否可访问
curl -I http://example.com/.well-known/acme-challenge/test

# 2. DNS解析问题
dig +short example.com
# 确保解析到正确的服务器IP

# 3. 防火墙问题
# Rocky Linux
firewall-cmd --list-all

# Ubuntu
ufw status

# 4. Nginx配置问题
nginx -T | grep -A 20 "location /.well-known"

问题二:证书不被信任

复制代码
# 1. 检查证书链是否完整
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -text | grep -A 2 "Certificate chain"

# 2. 检查是否使用fullchain.pem
nginx -T | grep ssl_certificate

# 3. 检查中间证书
curl https://example.com -v 2>&1 | grep "SSL certificate problem"

# 4. 在线检测
# https://www.ssllabs.com/ssltest/

问题三:HTTPS连接失败

复制代码
# 1. 检查端口监听
ss -tlnp | grep 443

# 2. 测试TLS连接
openssl s_client -connect example.com:443 -servername example.com

# 3. 检查加密套件
nmap --script ssl-enum-ciphers -p 443 example.com

# 4. 查看Nginx错误日志
tail -f /var/log/nginx/error.log

问题四:自动续期失败

复制代码
# 1. 手动测试续期
certbot renew --dry-run

# 2. 查看Certbot日志
cat /var/log/letsencrypt/letsencrypt.log

# 3. 检查systemd timer
systemctl status certbot.timer
journalctl -u certbot

# 4. 检查权限
ls -la /etc/letsencrypt/
# 确保certbot有写入权限

# 5. 检查磁盘空间
df -h /etc/letsencrypt

调试脚本

复制代码
#!/bin/bash
# debug_ssl.sh - SSL配置调试脚本

DOMAIN=$1
if [ -z "$DOMAIN" ]; then
    echo "Usage: $0 <domain>"
    exit 1
fi

echo "=== SSL Debug for $DOMAIN ==="
echo ""

echo "--- DNS Resolution ---"
dig +short $DOMAIN
echo ""

echo "--- Port Check ---"
nc -zv $DOMAIN 443 2>&1
echo ""

echo "--- Certificate Info ---"
echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -dates -subject -issuer
echo ""

echo "--- Certificate Chain ---"
echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | grep -E "(Certificate chain|^\s*[0-9]+ s:|depth=)"
echo ""

echo "--- TLS Version ---"
for v in tls1 tls1_1 tls1_2 tls1_3; do
    result=$(echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN -$v 2>&1 | grep "Protocol")
    echo "$v: $result"
done
echo ""

echo "--- OCSP Stapling ---"
echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN -status 2>/dev/null | grep "OCSP Response Status"
echo ""

echo "--- Security Headers ---"
curl -sI https://$DOMAIN | grep -iE "(strict-transport|x-frame|x-content-type|x-xss|referrer-policy)"

5.2 性能监控

TLS握手时间监控

复制代码
# 测试TLS握手时间
curl -w "DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nTotal: %{time_total}s\n" -o /dev/null -s https://example.com

# 期望值:
# TLS握手 < 100ms (同城)
# TLS握手 < 300ms (跨地域)

证书监控Prometheus指标

复制代码
# prometheus.yml
scrape_configs:
  - job_name: 'ssl_exporter'
    static_configs:
      - targets:
        - example.com:443
        - api.example.com:443
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: ssl_exporter:9219

Grafana告警规则

复制代码
# 证书过期告警
- alert: SSLCertExpiringSoon
  expr: ssl_cert_not_after - time() < 86400 * 14  # 14天内过期
  for: 1h
  labels:
    severity: warning
  annotations:
    summary: "SSL certificate expiring soon"
    description: "Certificate for {{ $labels.instance }} expires in {{ $value | humanizeDuration }}"

- alert: SSLCertExpired
  expr: ssl_cert_not_after < time()
  for: 0m
  labels:
    severity: critical
  annotations:
    summary: "SSL certificate expired"
    description: "Certificate for {{ $labels.instance }} has expired"

5.3 备份与恢复

完整备份脚本

复制代码
#!/bin/bash
# backup_letsencrypt.sh

BACKUP_DIR="/backup/letsencrypt"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/letsencrypt_$DATE.tar.gz"

mkdir -p $BACKUP_DIR

# 备份Let's Encrypt目录
tar -czf $BACKUP_FILE \
    /etc/letsencrypt \
    /etc/nginx/conf.d/*ssl*.conf \
    /etc/nginx/ssl

# 记录证书信息
certbot certificates > $BACKUP_DIR/certificates_$DATE.txt

# 保留30天
find $BACKUP_DIR -name "letsencrypt_*.tar.gz" -mtime +30 -delete
find $BACKUP_DIR -name "certificates_*.txt" -mtime +30 -delete

echo "Backup completed: $BACKUP_FILE"
ls -lh $BACKUP_FILE

恢复流程

复制代码
#!/bin/bash
# restore_letsencrypt.sh

BACKUP_FILE=$1
if [ -z "$BACKUP_FILE" ]; then
    echo "Usage: $0 <backup_file>"
    exit 1
fi

# 停止Nginx
systemctl stop nginx

# 备份当前配置
mv /etc/letsencrypt /etc/letsencrypt.old.$(date +%Y%m%d)

# 恢复备份
tar -xzf $BACKUP_FILE -C /

# 检查配置
nginx -t
if [ $? -ne 0 ]; then
    echo "Config test failed!"
    exit 1
fi

# 启动Nginx
systemctl start nginx

# 验证证书
certbot certificates

echo "Restore completed"

六、总结

6.1 技术要点回顾

HTTPS配置的核心要点:

  1. 证书管理:使用Let's Encrypt免费证书,配置自动续期

  2. 安全配置:只用TLS 1.2+,启用HSTS,配置安全头

  3. 性能优化:OCSP Stapling,会话缓存,HTTP/2

  4. 监控告警:证书过期监控,TLS握手时间监控

关键配置清单:

复制代码
# 必备配置
ssl_certificate fullchain.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_stapling on;
add_header Strict-Transport-Security "max-age=31536000";

# 自动续期
certbot renew --deploy-hook "nginx -s reload"

6.2 参考资料

附录

A. 命令速查表

命令 说明
certbot --nginx -d domain 自动申请并配置
certbot certonly --webroot 只申请证书
certbot certificates 查看证书状态
certbot renew 续期所有证书
certbot renew --dry-run 测试续期
certbot revoke --cert-path 吊销证书
certbot delete --cert-name 删除证书
openssl s_client -connect host:443 测试SSL连接
openssl x509 -noout -dates 查看证书有效期

B. 配置参数详解

参数 推荐值 说明
ssl_protocols TLSv1.2 TLSv1.3 支持的TLS版本
ssl_ciphers Mozilla Intermediate 加密套件
ssl_prefer_server_ciphers off TLS 1.3下应关闭
ssl_session_cache shared:SSL:10m 会话缓存
ssl_session_timeout 1d 会话超时
ssl_session_tickets off 更安全但影响性能
ssl_stapling on OCSP Stapling
ssl_stapling_verify on 验证OCSP响应
ssl_dhparam 2048位 DH参数

C. 术语表

术语 解释
Let's Encrypt 免费的证书颁发机构(CA)
Certbot Let's Encrypt官方客户端工具
ACME 自动证书管理协议
DV证书 域名验证证书,只验证域名所有权
HSTS HTTP严格传输安全
OCSP 在线证书状态协议
OCSP Stapling 服务器代替客户端查询证书状态
CT 证书透明度,防止错误签发
SNI 服务器名称指示,同IP多证书
TLS 1.3 最新TLS版本,更快更安全
fullchain.pem 包含证书和中间证书的完整链
privkey.pem 私钥文件
相关推荐
一直跑2 小时前
通过所里的服务器连接到组里的服务器,然后可视化组里的文件和代码,并修改等操作(VScode/vscode/mobaxterm)
linux·运维·服务器
mobai73 小时前
Ubuntu环境上安装NTP服务
linux·运维·ubuntu
loong_XL7 小时前
docker gpu容器镜像环境构建
运维·docker·容器
杨了个杨89828 小时前
nginx常见功能部署
运维·服务器·nginx
小天源10 小时前
linux漏洞一键扫描
linux·运维·服务器·漏洞扫描
eWidget10 小时前
InfluxDB迁移至金仓数据库的同城容灾实践:性能显著提升、运维效率优化,某能源企业实现RPO_5秒的高可靠时序数据管理
运维·数据库·能源·时序数据库·kingbase·kingbasees·金仓数据库
m0_6962126811 小时前
个人微信api
运维·服务器
en-route11 小时前
SSH Key 与 GPG Key 区别详解:Git 使用中的身份与签名机制
运维·git·ssh
小白鸽i13 小时前
【LINUX】将源码驱动文件编译并生效
linux·运维·服务器