Nginx SSL/TLS 配置

SSL/TLS Configuration

概述

SSL/TLS是现代Web应用的基础安全协议,Nginx提供了强大的SSL/TLS支持,包括证书管理、协议配置、性能优化等功能。本文将详细介绍Nginx SSL/TLS的配置方法、安全最佳实践和性能优化技巧。

1. SSL/TLS基础概念

1.1 SSL/TLS协议版本

协议版本演进:
├── SSL 1.0 (已废弃)
├── SSL 2.0 (已废弃)
├── SSL 3.0 (已废弃)
├── TLS 1.0 (RFC 2246, 1999) - 建议禁用
├── TLS 1.1 (RFC 4346, 2006) - 建议禁用
├── TLS 1.2 (RFC 5246, 2008) - 推荐
└── TLS 1.3 (RFC 8446, 2018) - 最新推荐

1.2 证书类型

证书类型:
├── 域名验证证书 (DV - Domain Validated)
├── 组织验证证书 (OV - Organization Validated)
├── 扩展验证证书 (EV - Extended Validation)
├── 通配符证书 (Wildcard Certificate)
└── 多域名证书 (SAN Certificate)

2. 基本SSL/TLS配置

2.1 简单HTTPS配置

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

    # SSL证书配置
    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;

    # 基本SSL参数
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;

    # SSL会话缓存
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # 网站根目录
    root /var/www/example.com;
    index index.html index.htm;

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

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

    return 301 https://$server_name$request_uri;
}

2.2 多证书配置

# 主域名证书
server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;

    # SSL配置
    include /etc/nginx/ssl-params.conf;

    location / {
        root /var/www/example.com;
        try_files $uri $uri/ =404;
    }
}

# 子域名证书
server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate /etc/ssl/certs/api.example.com.crt;
    ssl_certificate_key /etc/ssl/private/api.example.com.key;

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

    location / {
        proxy_pass http://api-backend;
        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 https;
    }
}

# 通配符证书
server {
    listen 443 ssl http2;
    server_name *.example.com;

    ssl_certificate /etc/ssl/certs/wildcard.example.com.crt;
    ssl_certificate_key /etc/ssl/private/wildcard.example.com.key;

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

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

    location / {
        root /var/www/subdomains/$subdomain;
        try_files $uri $uri/ =404;
    }
}

3. 高级SSL/TLS配置

3.1 SSL参数优化配置文件

# /etc/nginx/ssl-params.conf
# 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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

# 服务器密码优先级
ssl_prefer_server_ciphers off;

# DH参数
ssl_dhparam /etc/ssl/certs/dhparam.pem;

# ECDH曲线
ssl_ecdh_curve secp384r1;

# SSL会话设置
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;

# OCSP装订
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/certs/ca-chain.crt;

# 安全头部
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;

# 内容安全策略
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;

3.2 生成DH参数

# 生成强DH参数(可能需要很长时间)
openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

# 或者使用4096位(更安全但更慢)
openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096

3.3 客户端证书认证

server {
    listen 443 ssl http2;
    server_name secure.example.com;

    # 服务器证书
    ssl_certificate /etc/ssl/certs/secure.example.com.crt;
    ssl_certificate_key /etc/ssl/private/secure.example.com.key;

    # 客户端证书配置
    ssl_client_certificate /etc/ssl/certs/ca.crt;
    ssl_verify_client on;  # 强制客户端证书
    ssl_verify_depth 2;

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

    location / {
        # 传递客户端证书信息
        proxy_set_header X-SSL-Client-CN $ssl_client_s_dn_cn;
        proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
        proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
        proxy_set_header X-SSL-Client-Serial $ssl_client_serial;

        proxy_pass http://secure-backend;
    }
}

# 可选客户端证书认证
server {
    listen 443 ssl http2;
    server_name optional-cert.example.com;

    ssl_certificate /etc/ssl/certs/optional-cert.example.com.crt;
    ssl_certificate_key /etc/ssl/private/optional-cert.example.com.key;

    # 可选客户端证书
    ssl_client_certificate /etc/ssl/certs/ca.crt;
    ssl_verify_client optional;

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

    location / {
        # 基于证书验证结果的访问控制
        if ($ssl_client_verify != SUCCESS) {
            return 401 "Client certificate required";
        }

        proxy_pass http://backend;
        proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
        proxy_set_header X-SSL-Client-CN $ssl_client_s_dn_cn;
    }
}

4. Let's Encrypt自动化配置

4.1 Certbot基本配置

# 安装Certbot
sudo apt update
sudo apt install certbot python3-certbot-nginx

# 获取证书并自动配置Nginx
sudo certbot --nginx -d example.com -d www.example.com

# 测试自动续期
sudo certbot renew --dry-run

4.2 手动证书获取和配置

# ACME挑战配置
server {
    listen 80;
    server_name example.com www.example.com;

    # Let's Encrypt验证路径
    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
        try_files $uri =404;
    }

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

4.3 自动续期脚本

#!/bin/bash
# /usr/local/bin/certbot-renew.sh

WEBROOT="/var/www/letsencrypt"
DOMAINS="example.com,www.example.com"
EMAIL="admin@example.com"

# 创建webroot目录
mkdir -p $WEBROOT

# 获取或续期证书
certbot certonly \
    --webroot \
    --webroot-path=$WEBROOT \
    --email $EMAIL \
    --agree-tos \
    --no-eff-email \
    --domains $DOMAINS

# 检查续期结果
if [ $? -eq 0 ]; then
    echo "Certificate renewed successfully"

    # 重新加载Nginx
    nginx -t && systemctl reload nginx

    # 发送成功通知
    echo "SSL certificate renewed for $DOMAINS" | mail -s "SSL Renewal Success" $EMAIL
else
    echo "Certificate renewal failed"

    # 发送失败通知
    echo "SSL certificate renewal failed for $DOMAINS" | mail -s "SSL Renewal Failed" $EMAIL
fi

4.4 Crontab自动续期

# 添加到crontab
0 12 * * * /usr/local/bin/certbot-renew.sh >/dev/null 2>&1

# 或者使用certbot的内置续期
0 12 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"

5. SSL/TLS性能优化

5.1 会话复用优化

http {
    # SSL会话缓存
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;

    # 禁用会话票据(避免密钥轮换问题)
    ssl_session_tickets off;

    # 或者启用会话票据(适用于多服务器环境)
    # ssl_session_tickets on;
    # ssl_session_ticket_key /etc/ssl/private/ticket.key;

    server {
        listen 443 ssl http2;
        server_name performance.example.com;

        ssl_certificate /etc/ssl/certs/performance.example.com.crt;
        ssl_certificate_key /etc/ssl/private/performance.example.com.key;

        # 启用OCSP装订减少客户端验证时间
        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_stapling_responder http://ocsp.example.com;

        # 预加载HSTS
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

        location / {
            root /var/www/performance;

            # 启用gzip压缩
            gzip on;
            gzip_vary on;
            gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
        }
    }
}

5.2 HTTP/2优化

server {
    listen 443 ssl http2;
    server_name http2.example.com;

    ssl_certificate /etc/ssl/certs/http2.example.com.crt;
    ssl_certificate_key /etc/ssl/private/http2.example.com.key;

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

    # HTTP/2推送
    location / {
        root /var/www/http2;

        # 推送关键资源
        location = /index.html {
            http2_push /css/style.css;
            http2_push /js/app.js;
            try_files $uri =404;
        }

        # 静态文件缓存
        location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
            expires 1y;
            add_header Cache-Control "public, immutable";

            # 启用Brotli压缩(如果模块可用)
            brotli on;
            brotli_comp_level 6;
            brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
        }
    }
}

5.3 SSL缓冲区优化

server {
    listen 443 ssl http2;
    server_name buffered.example.com;

    ssl_certificate /etc/ssl/certs/buffered.example.com.crt;
    ssl_certificate_key /etc/ssl/private/buffered.example.com.key;

    # SSL缓冲区大小优化
    ssl_buffer_size 4k;  # 减少延迟,适用于小文件
    # ssl_buffer_size 16k;  # 提高吞吐量,适用于大文件

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

    location / {
        root /var/www/buffered;

        # 根据文件大小动态调整缓冲区
        location ~* \.(jpg|jpeg|png|gif|mp4|avi)$ {
            ssl_buffer_size 16k;  # 大文件使用大缓冲区
            expires 1y;
        }

        location ~* \.(css|js|html)$ {
            ssl_buffer_size 4k;   # 小文件使用小缓冲区
            expires 1d;
        }
    }
}

6. SSL/TLS安全强化

6.1 高安全级别配置

server {
    listen 443 ssl http2;
    server_name secure.example.com;

    # 证书配置
    ssl_certificate /etc/ssl/certs/secure.example.com.crt;
    ssl_certificate_key /etc/ssl/private/secure.example.com.key;

    # 只允许TLS 1.3
    ssl_protocols TLSv1.3;

    # 严格的密码套件
    ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;
    ssl_prefer_server_ciphers off;

    # 强DH参数
    ssl_dhparam /etc/ssl/certs/dhparam-4096.pem;

    # 严格传输安全
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # 内容安全策略
    add_header Content-Security-Policy "default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline';" always;

    # 其他安全头部
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header Referrer-Policy no-referrer always;
    add_header Feature-Policy "geolocation 'none'; microphone 'none'; camera 'none'" always;

    location / {
        root /var/www/secure;

        # 禁用服务器信息泄露
        server_tokens off;

        # 禁用不安全的HTTP方法
        if ($request_method !~ ^(GET|HEAD|POST)$ ) {
            return 405;
        }
    }
}

6.2 SSL Labs A+评级配置

# 获得SSL Labs A+评级的配置
server {
    listen 443 ssl http2;
    server_name aplus.example.com;

    # 证书链配置
    ssl_certificate /etc/ssl/certs/aplus.example.com-fullchain.crt;
    ssl_certificate_key /etc/ssl/private/aplus.example.com.key;

    # 协议版本
    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;

    # DH参数
    ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;

    # 会话配置
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # OCSP装订
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/ssl/certs/ca-bundle.crt;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # HSTS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    location / {
        root /var/www/aplus;
        try_files $uri $uri/ =404;
    }
}

7. SSL证书管理

7.1 证书监控脚本

#!/bin/bash
# ssl-monitor.sh

CERTIFICATES=(
    "/etc/ssl/certs/example.com.crt"
    "/etc/ssl/certs/api.example.com.crt"
    "/etc/ssl/certs/secure.example.com.crt"
)

WARNING_DAYS=30
CRITICAL_DAYS=7
EMAIL="admin@example.com"

check_certificate() {
    local cert_file=$1
    local domain=$(basename $cert_file .crt)

    if [ ! -f "$cert_file" ]; then
        echo "ERROR: Certificate file $cert_file not found"
        return 1
    fi

    # 获取证书过期时间
    local expiry_date=$(openssl x509 -enddate -noout -in "$cert_file" | cut -d= -f2)
    local expiry_epoch=$(date -d "$expiry_date" +%s)
    local current_epoch=$(date +%s)
    local days_until_expiry=$(( (expiry_epoch - current_epoch) / 86400 ))

    echo "Domain: $domain"
    echo "Expires: $expiry_date"
    echo "Days until expiry: $days_until_expiry"

    if [ $days_until_expiry -le $CRITICAL_DAYS ]; then
        echo "CRITICAL: Certificate expires in $days_until_expiry days!" | \
            mail -s "CRITICAL: SSL Certificate Expiring - $domain" $EMAIL
    elif [ $days_until_expiry -le $WARNING_DAYS ]; then
        echo "WARNING: Certificate expires in $days_until_expiry days!" | \
            mail -s "WARNING: SSL Certificate Expiring - $domain" $EMAIL
    fi

    echo "---"
}

echo "=== SSL Certificate Monitoring ==="
echo "Date: $(date)"
echo

for cert in "${CERTIFICATES[@]}"; do
    check_certificate "$cert"
done

7.2 证书更新自动化

#!/bin/bash
# ssl-update.sh

CERT_DIR="/etc/ssl/certs"
KEY_DIR="/etc/ssl/private"
BACKUP_DIR="/etc/ssl/backup/$(date +%Y%m%d)"

update_certificate() {
    local domain=$1
    local new_cert=$2
    local new_key=$3

    echo "Updating certificate for $domain..."

    # 创建备份目录
    mkdir -p $BACKUP_DIR

    # 备份现有证书
    if [ -f "$CERT_DIR/$domain.crt" ]; then
        cp "$CERT_DIR/$domain.crt" "$BACKUP_DIR/$domain.crt.bak"
    fi

    if [ -f "$KEY_DIR/$domain.key" ]; then
        cp "$KEY_DIR/$domain.key" "$BACKUP_DIR/$domain.key.bak"
    fi

    # 验证新证书
    if ! openssl x509 -in "$new_cert" -noout; then
        echo "ERROR: Invalid certificate file"
        return 1
    fi

    if ! openssl rsa -in "$new_key" -check -noout; then
        echo "ERROR: Invalid private key file"
        return 1
    fi

    # 检查证书和私钥匹配
    cert_modulus=$(openssl x509 -noout -modulus -in "$new_cert" | openssl md5)
    key_modulus=$(openssl rsa -noout -modulus -in "$new_key" | openssl md5)

    if [ "$cert_modulus" != "$key_modulus" ]; then
        echo "ERROR: Certificate and private key do not match"
        return 1
    fi

    # 安装新证书
    cp "$new_cert" "$CERT_DIR/$domain.crt"
    cp "$new_key" "$KEY_DIR/$domain.key"

    # 设置权限
    chmod 644 "$CERT_DIR/$domain.crt"
    chmod 600 "$KEY_DIR/$domain.key"

    # 测试Nginx配置
    if nginx -t; then
        systemctl reload nginx
        echo "Certificate updated and Nginx reloaded successfully"
        return 0
    else
        echo "ERROR: Nginx configuration test failed, rolling back..."

        # 回滚
        if [ -f "$BACKUP_DIR/$domain.crt.bak" ]; then
            cp "$BACKUP_DIR/$domain.crt.bak" "$CERT_DIR/$domain.crt"
        fi

        if [ -f "$BACKUP_DIR/$domain.key.bak" ]; then
            cp "$BACKUP_DIR/$domain.key.bak" "$KEY_DIR/$domain.key"
        fi

        return 1
    fi
}

# 使用示例
# update_certificate "example.com" "/tmp/new_cert.crt" "/tmp/new_key.key"

8. 故障排除

8.1 SSL配置测试

#!/bin/bash
# ssl-test.sh

DOMAIN="example.com"
PORT="443"

echo "=== SSL/TLS Configuration Test ==="
echo "Domain: $DOMAIN"
echo "Port: $PORT"
echo

# 基本连接测试
echo "1. Basic connection test:"
if openssl s_client -connect $DOMAIN:$PORT -servername $DOMAIN </dev/null 2>/dev/null | grep -q "CONNECTED"; then
    echo "✓ SSL connection successful"
else
    echo "✗ SSL connection failed"
fi

# 证书有效期检查
echo -e "\n2. Certificate expiry check:"
expiry=$(echo | openssl s_client -connect $DOMAIN:$PORT -servername $DOMAIN 2>/dev/null | openssl x509 -noout -dates | grep notAfter)
echo $expiry

# 支持的协议版本
echo -e "\n3. Supported TLS versions:"
for version in ssl2 ssl3 tls1 tls1_1 tls1_2 tls1_3; do
    if echo | openssl s_client -connect $DOMAIN:$PORT -$version 2>/dev/null | grep -q "CONNECTED"; then
        echo "✓ $version supported"
    else
        echo "✗ $version not supported"
    fi
done

# 密码套件测试
echo -e "\n4. Cipher suite test:"
echo | openssl s_client -connect $DOMAIN:$PORT -cipher HIGH 2>/dev/null | grep "Cipher is"

# OCSP装订检查
echo -e "\n5. OCSP stapling check:"
echo | openssl s_client -connect $DOMAIN:$PORT -status 2>/dev/null | grep -A1 "OCSP response"

# HTTP/2支持检查
echo -e "\n6. HTTP/2 support check:"
if curl -I --http2 -s https://$DOMAIN/ | grep -q "HTTP/2"; then
    echo "✓ HTTP/2 supported"
else
    echo "✗ HTTP/2 not supported"
fi

8.2 常见SSL问题诊断

#!/bin/bash
# ssl-diagnose.sh

diagnose_ssl_issues() {
    local domain=$1

    echo "=== SSL Diagnosis for $domain ==="

    # 检查DNS解析
    echo "1. DNS Resolution:"
    if nslookup $domain >/dev/null 2>&1; then
        echo "✓ DNS resolution successful"
    else
        echo "✗ DNS resolution failed"
        return 1
    fi

    # 检查端口连通性
    echo -e "\n2. Port connectivity:"
    if nc -zv $domain 443 2>&1 | grep -q "succeeded"; then
        echo "✓ Port 443 is accessible"
    else
        echo "✗ Port 443 is not accessible"
        return 1
    fi

    # 检查证书链
    echo -e "\n3. Certificate chain:"
    cert_chain=$(echo | openssl s_client -connect $domain:443 -showcerts 2>/dev/null)
    if echo "$cert_chain" | grep -q "Verify return code: 0"; then
        echo "✓ Certificate chain is valid"
    else
        echo "✗ Certificate chain verification failed"
        echo "$cert_chain" | grep "Verify return code"
    fi

    # 检查SNI支持
    echo -e "\n4. SNI support:"
    if echo | openssl s_client -connect $domain:443 -servername $domain 2>/dev/null | grep -q "SSL handshake has read"; then
        echo "✓ SNI is working"
    else
        echo "✗ SNI may not be working"
    fi

    # 检查混合内容
    echo -e "\n5. Mixed content check:"
    if curl -s https://$domain/ | grep -qi "http://"; then
        echo "⚠ Potential mixed content detected"
    else
        echo "✓ No obvious mixed content issues"
    fi
}

# 使用示例
diagnose_ssl_issues "example.com"

小结

通过本文的学习,你应该掌握:

  1. SSL/TLS协议的基础概念和配置方法
  2. 多种证书类型的配置和管理
  3. Let's Encrypt自动化证书获取和续期
  4. SSL/TLS性能优化技巧
  5. 高级安全配置和强化方法
  6. 证书监控和管理自动化
  7. 常见SSL问题的诊断和解决

下一篇文章将介绍Nginx的安全配置与防护。

powered by Gitbook© 2025 编外计划 | 最后修改: 2025-08-29 15:40:15

results matching ""

    No results matching ""