Skip to content

필사 모드: [DevOps] nginx TLS 配置完全指南:PEM/Key 应用与安全优化

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

1. 理解 PEM、Key、CRT 文件格式

1.1 PEM (Privacy Enhanced Mail)

PEM 是经过 Base64 编码的证书/密钥格式。可以直接用文本编辑器打开查看。

-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
... (Base64 编码的数据)
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7o4qne60TB3pO
... (Base64 编码的数据)
-----END PRIVATE KEY-----

1.2 DER (Distinguished Encoding Rules)

这是二进制格式,等同于把 PEM 的 Base64 编码去掉之后的内容。

1.3 密钥类型

算法密钥长度性能安全性推荐场景
RSA 20482048 bit足够重视兼容性
RSA 40964096 bit非常慢需要高安全性时
ECDSA P-256256 bit足够推荐 (Modern)
ECDSA P-384384 bit高安全性 + 高性能

在相同安全强度下,ECDSA 的密钥长度远小于 RSA,因此 TLS 握手性能更出色。

1.4 Let's Encrypt 签发文件的对应关系

/etc/letsencrypt/live/example.com/
  fullchain.pem  = 服务器证书 + 中间 CA 证书
  privkey.pem    = 私钥
  chain.pem      = 仅中间 CA 证书
  cert.pem       = 仅服务器证书

nginx 中使用的文件:
  ssl_certificate     -> fullchain.pem
  ssl_certificate_key -> privkey.pem

2. nginx 基本 TLS 配置

2.1 最小配置

server {
    listen 443 ssl;
    server_name example.com;

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

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

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

2.2 从 HTTP 重定向到 HTTPS

server {
    listen 80;
    server_name example.com www.example.com;

    # 用于 ACME 挑战 (Let's Encrypt 续期)
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

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

2.3 完整的生产配置示例

# /etc/nginx/conf.d/ssl-params.conf
# 公共 SSL 参数 (在所有 server 块中 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;

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

# HSTS
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;
resolver_timeout 5s;

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

# 安全响应头
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 "strict-origin-when-cross-origin" always;
# /etc/nginx/sites-available/example.com
server {
    listen 80;
    server_name example.com www.example.com;

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

    location / {
        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;

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

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

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

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

3. TLS 版本配置

3.1 各 TLS 版本的安全状态

版本状态建议
SSL 2.0废弃 (1996 年)绝对禁止使用
SSL 3.0废弃 (POODLE 漏洞)绝对禁止使用
TLS 1.0废弃 (2021 年)停用
TLS 1.1废弃 (2021 年)停用
TLS 1.2当前标准使用
TLS 1.3最新标准使用 (推荐)

3.2 仅允许 TLS 1.2/1.3

# 仅允许 TLS 1.2 和 1.3 (推荐)
ssl_protocols TLSv1.2 TLSv1.3;

3.3 仅 TLS 1.3 (Modern)

# 仅允许 TLS 1.3 (只支持较新的客户端)
ssl_protocols TLSv1.3;

TLS 1.3 的优点:

  • 握手往返次数减少 (1-RTT、0-RTT)
  • 移除了脆弱的遗留加密套件
  • 默认启用前向保密 (Forward Secrecy)
  • 握手过程加密(含服务器证书)

4. 加密套件配置

4.1 Mozilla SSL Configuration Generator 配置档

Mozilla 提供三种配置档。

Modern(仅 TLS 1.3):

ssl_protocols TLSv1.3;
# TLS 1.3 无需单独配置 cipher (已内建于协议)
ssl_prefer_server_ciphers off;

Intermediate(TLS 1.2 + 1.3,推荐):

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;

Old(兼容遗留系统,不推荐):

ssl_protocols TLSv1 TLSv1.1 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:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA;
ssl_prefer_server_ciphers on;

4.2 解读加密套件名称

ECDHE-RSA-AES256-GCM-SHA384 的含义:

ECDHE  = 密钥交换算法 (Elliptic Curve Diffie-Hellman Ephemeral)
         → 提供 Forward Secrecy
RSA    = 认证算法 (对服务器证书签名)
AES256 = 对称加密 (256 位 AES)
GCM    = 加密模式 (Galois/Counter Mode, AEAD)
SHA384 = 哈希函数 (完整性校验)

4.3 ssl_prefer_server_ciphers

# TLS 1.2: 设为 on 时优先使用服务器偏好的 cipher
# TLS 1.3: 由客户端决定 (该配置被忽略)
# Mozilla Intermediate 中推荐 off
ssl_prefer_server_ciphers off;

5. DH 参数

这是 Diffie-Hellman 密钥交换所使用的参数。使用 DHE 加密套件时需要它。

# 生成 DH 参数 (建议 2048 位以上)
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048

# 更强的 4096 位 (生成耗时较长)
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096
ssl_dhparam /etc/nginx/ssl/dhparam.pem;

参考:TLS 1.3 只使用 ECDHE 而不用 DHE,因此不需要 DH 参数。若把 TLS 1.2 的 DHE 加密套件移除,DH 参数同样也不再需要。


6. HSTS (HTTP Strict Transport Security)

6.1 什么是 HSTS

HSTS 是一个 HTTP 响应头,用来指示浏览器始终以 HTTPS 访问该域名。

# max-age: HSTS 的保持时长 (单位秒, 63072000 = 2 年)
# includeSubDomains: 对所有子域名同样生效
# preload: 表达登记到浏览器 Preload List 的意愿
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

6.2 HSTS Preload List

一旦登记到 HSTS Preload List,浏览器从第一次访问起就会强制使用 HTTPS。

登记条件:

  • 拥有有效的 TLS 证书
  • 端口 80 重定向到 443
  • 所有子域名都启用 HTTPS
  • HSTS 响应头中 max-age 至少 31536000(1 年),并包含 includeSubDomains 和 preload

登记站点:hstspreload.org

6.3 注意事项

  • 一旦设置 HSTS 就很难回退(在 max-age 期间浏览器会阻止 HTTP 访问)
  • 建议先从较小的 max-age(例如 300 秒)开始,再逐步延长
  • 确认所有子域名都支持 HTTPS 之后,再加上 includeSubDomains

7. OCSP Stapling

7.1 什么是 OCSP Stapling

OCSP(Online Certificate Status Protocol)是用于确认证书是否被吊销(revoke)的协议。OCSP Stapling 则是由服务器预先从 CA 取得 OCSP 响应,并在 TLS 握手时把它传给客户端的技术。

没有 OCSP Stapling 时:
Client → Server (TLS 握手)
Client → CA OCSP Server (确认证书有效性) ← 额外延迟

有 OCSP Stapling 时:
Server → CA OCSP Server (周期性刷新 OCSP 响应)
Client → Server (TLS 握手 + 附带 OCSP 响应) ← 更快

7.2 配置

ssl_stapling on;
ssl_stapling_verify on;

# 中间 CA 证书 (chain.pem)
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

# 用于解析 OCSP 响应服务器域名的 resolver
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

7.3 确认 OCSP Stapling

# 确认 OCSP Stapling 是否生效
openssl s_client -connect example.com:443 -servername example.com -status < /dev/null 2>/dev/null | grep -A 20 "OCSP Response"

# 正常响应示例:
# OCSP Response Status: successful (0x0)
# OCSP Response Data:
#     OCSP Response Status: successful (0x0)
#     Cert Status: good

8. SSL 会话缓存

8.1 会话缓存

TLS 握手的开销很大。使用会话缓存后,重新连接时可以省略完整的握手过程。

# 共享内存缓存 (在多个 worker 进程之间共享)
# 10MB 缓存 = 可存约 40,000 个会话
ssl_session_cache shared:SSL:10m;

# 会话有效期
ssl_session_timeout 1d;

# 停用会话票据 (为了保证 Forward Secrecy)
ssl_session_tickets off;

8.2 为什么要停用 Session Tickets

Session Tickets 把加密后的票据存放在客户端,而不是占用服务器内存。问题在于:

  • 票据加密密钥决定了所有会话的安全性
  • 若密钥轮换做得不到位,Forward Secrecy 就会被破坏
  • 在 TLS 1.3 中可以用 0-RTT 重连来替代

9. HTTP/2 配置

9.1 启用 HTTP/2

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

    # ... SSL 配置 ...
}

HTTP/2 的优点:

  • 多路复用:在一条 TCP 连接上并发处理多个请求
  • 头部压缩:用 HPACK 压缩 HTTP 头
  • 服务器推送:在客户端请求之前先发送资源
  • 优先级:为每个资源设置传输优先级

9.2 HTTP/3 (QUIC) 准备

# nginx 1.25.0+ (或 nginx-quic)
server {
    listen 443 ssl;
    listen 443 quic reuseport;
    http2 on;
    http3 on;

    server_name example.com;

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

    # QUIC 广告响应头
    add_header Alt-Svc 'h3=":443"; ma=86400' always;
}

10. mTLS (Mutual TLS) 配置

10.1 什么是 mTLS

普通 TLS 只有服务器出示证书,而 mTLS 中客户端也要出示证书,从而完成双向认证。

普通 TLS:
Client ──── 校验服务器证书 ───> Server

Mutual TLS:
Client <─── 校验服务器证书 ───> Server
Client ──── 客户端证书 ──────> Server (服务器校验客户端证书)

使用场景:

  • 微服务之间的通信(服务网格)
  • API 认证(API Gateway)
  • IoT 设备认证
  • 内部管理工具的访问控制

10.2 用 CA 证书签发客户端证书

# 1. 生成 CA 私钥
openssl genrsa -out ca.key 4096

# 2. 生成 CA 证书 (自签名)
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
  -subj "/C=KR/ST=Seoul/O=MyOrg/CN=Internal CA"

# 3. 生成客户端私钥
openssl genrsa -out client.key 2048

# 4. 生成客户端 CSR
openssl req -new -key client.key -out client.csr \
  -subj "/C=KR/ST=Seoul/O=MyOrg/CN=service-a"

# 5. 用 CA 为客户端证书签名
openssl x509 -req -days 365 -in client.csr \
  -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out client.crt

# 6. 生成 PFX/PKCS12 包 (供浏览器导入)
openssl pkcs12 -export -out client.pfx \
  -inkey client.key -in client.crt -certfile ca.crt

10.3 nginx mTLS 配置

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

    ssl_certificate     /etc/nginx/ssl/server.fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/server.privkey.pem;

    # 校验客户端证书
    ssl_client_certificate /etc/nginx/ssl/ca.crt;
    ssl_verify_client on;        # on: 必需, optional: 可选
    ssl_verify_depth 2;          # 证书链校验深度

    # CRL (Certificate Revocation List) 配置
    # ssl_crl /etc/nginx/ssl/ca.crl;

    # 把客户端证书信息传给后端
    location / {
        proxy_pass http://backend;
        proxy_set_header X-SSL-Client-Cert $ssl_client_cert;
        proxy_set_header X-SSL-Client-S-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;
    }
}

10.4 ssl_verify_client 选项

# on: 必须提供客户端证书 (没有则返回 400 错误)
ssl_verify_client on;

# optional: 证书可选 (没有也能连接, 有证书则进行校验)
ssl_verify_client optional;

# optional_no_ca: 不做校验只做透传 (由后端校验)
ssl_verify_client optional_no_ca;

10.5 mTLS 测试

# 用客户端证书发起请求
curl --cert client.crt --key client.key \
  --cacert ca.crt \
  https://api.example.com/data

# 不带证书发起请求 (若 ssl_verify_client on 则失败)
curl --cacert ca.crt https://api.example.com/data
# 400 Bad Request - No required SSL certificate was sent

11. 生成自签名证书(开发用)

11.1 单域名

# 一次性生成私钥 + 自签名证书
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout selfsigned.key \
  -out selfsigned.crt \
  -days 365 \
  -subj "/C=KR/ST=Seoul/O=Dev/CN=localhost"

11.2 包含 SAN (Subject Alternative Name)

包含多个域名或 IP 的证书:

# 生成 SAN 配置文件
cat > san.cnf << 'SANEOF'
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req

[dn]
C = KR
ST = Seoul
O = Dev
CN = localhost

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = myapp.local
DNS.3 = *.myapp.local
IP.1 = 127.0.0.1
IP.2 = 192.168.1.100
SANEOF

# 生成自签名证书
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout selfsigned.key \
  -out selfsigned.crt \
  -days 365 \
  -config san.cnf \
  -extensions v3_req

11.3 ECDSA 自签名证书

# 生成 ECDSA P-256 密钥
openssl ecparam -genkey -name prime256v1 -out ecdsa.key

# 自签名证书
openssl req -x509 -new -key ecdsa.key \
  -out ecdsa.crt \
  -days 365 \
  -subj "/C=KR/ST=Seoul/O=Dev/CN=localhost"

12. 证书格式转换

12.1 从 PEM 到 DER

# 转换证书
openssl x509 -in cert.pem -outform DER -out cert.der

# 转换私钥
openssl rsa -in key.pem -outform DER -out key.der

12.2 从 DER 到 PEM

openssl x509 -in cert.der -inform DER -outform PEM -out cert.pem

12.3 从 PFX/PKCS12 到 PEM

# 从 PFX 提取证书
openssl pkcs12 -in certificate.pfx -clcerts -nokeys -out cert.pem

# 从 PFX 提取私钥
openssl pkcs12 -in certificate.pfx -nocerts -nodes -out key.pem

# 从 PFX 提取 CA 链
openssl pkcs12 -in certificate.pfx -cacerts -nokeys -out chain.pem

12.4 从 PEM 到 PFX/PKCS12

openssl pkcs12 -export \
  -out certificate.pfx \
  -inkey key.pem \
  -in cert.pem \
  -certfile chain.pem

13. 证书校验命令

13.1 查看证书内容

# 证书的全部信息
openssl x509 -in cert.pem -text -noout

# 签发者信息
openssl x509 -in cert.pem -issuer -noout

# 主体信息
openssl x509 -in cert.pem -subject -noout

# 过期日期
openssl x509 -in cert.pem -enddate -noout

# SAN (Subject Alternative Names)
openssl x509 -in cert.pem -noout -ext subjectAltName

# 序列号
openssl x509 -in cert.pem -serial -noout

13.2 查看远程服务器证书

# 查询服务器证书
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | \
  openssl x509 -text -noout

# 确认证书链
openssl s_client -connect example.com:443 -servername example.com -showcerts < /dev/null 2>/dev/null

# 确认 TLS 版本与 cipher
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | \
  grep -E "Protocol|Cipher"

13.3 确认证书与密钥是否匹配

# 比较证书与密钥的 modulus (相同才算匹配)
openssl x509 -noout -modulus -in cert.pem | openssl md5
openssl rsa -noout -modulus -in key.pem | openssl md5

# 两个值相同则说明证书与密钥匹配

14. 安全检查工具

14.1 SSL Labs

这是在线检查 TLS 配置的服务。

https://www.ssllabs.com/ssltest/analyze.html?d=example.com

评级标准:
A+  = 最佳 (HSTS + 强配置)
A   = 优秀
B   = 良好 (建议改进)
C   = 不足 (存在安全风险)
F   = 危险 (需要立即处理)

14.2 testssl.sh

这是在命令行检查 TLS 配置的开源工具。

# 安装
git clone --depth 1 https://github.com/drwetter/testssl.sh.git

# 全面检查
./testssl.sh/testssl.sh example.com

# 只检查特定项目
./testssl.sh/testssl.sh --protocols example.com
./testssl.sh/testssl.sh --ciphers example.com
./testssl.sh/testssl.sh --headers example.com
./testssl.sh/testssl.sh --vulnerabilities example.com

14.3 校验 nginx 配置

# 检查 nginx 配置语法
sudo nginx -t

# 输出 nginx 的完整配置
sudo nginx -T

# Gixy (nginx 安全分析工具)
pip install gixy
gixy /etc/nginx/nginx.conf

15. 对接 Let's Encrypt 自动续期

15.1 Certbot + nginx Reload Hook

# certbot 续期后重载 nginx
sudo certbot renew --deploy-hook "systemctl reload nginx"

# 或者直接写进 renewal 配置文件
# /etc/letsencrypt/renewal/example.com.conf
# [renewalparams]
# ...
# renew_hook = systemctl reload nginx

15.2 在 Docker Compose 中自动续期

version: '3.8'
services:
  nginx:
    image: nginx:latest
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
    restart: unless-stopped
    command: '/bin/sh -c ''while :; do sleep 6h & wait; nginx -s reload; done & nginx -g "daemon off;"'''

  certbot:
    image: certbot/certbot
    volumes:
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait; done'"

16. 配置检查清单

上线到生产之前需要确认的项目:

项目命令 / 确认方式标准
TLS 版本ssl_protocols仅 TLSv1.2 TLSv1.3
证书链使用 fullchain.pem含中间 CA
HSTS检查响应头max-age 1 年以上
OCSP Staplingopenssl s_client -statusCert Status: good
HTTP 重定向curl -I http://domain301 to HTTPS
SSL Labs 评级ssllabs.com目标 A+
私钥权限ls -la privkey.pem600 (root only)
自动续期certbot renew --dry-run成功
DH 参数2048 位以上已生成
Session TicketsoffForward Secrecy

17. 结论

nginx 的 TLS 配置不止是把证书挂上去,还需要一整套综合的安全优化。核心要点:

  • 使用 fullchain.pem + privkey.pem 来补全证书链
  • 只允许 TLS 1.2/1.3,停用遗留协议
  • 推荐使用 Mozilla Intermediate 配置档的加密套件
  • HSTS 强制 HTTPS 访问,用 OCSP Stapling 提升性能
  • SSL 会话缓存 减少握手开销
  • HTTP/2 优化 Web 性能
  • mTLS 实现微服务之间的双向认证
  • SSL Labs 的 A+ 评级为目标持续优化配置
  • Let's Encrypt 证书的自动续期务必要配置好

현재 단락 (1/351)

PEM 是经过 Base64 编码的证书/密钥格式。可以直接用文本编辑器打开查看。

작성 글자: 0원문 글자: 13,689작성 단락: 0/351