lolly/docs/32-nginx-acme-ssl.md
xfy d8ac807cb7 docs(nginx): 更新代理与 stream 文档,新增 njs/可观测性/ACME 指南
- 04: 新增 random 负载均衡、upstream 响应时间变量详解
- 10: 新增访问控制、连接限制、地理/真实IP模块、高级日志配置
- 24: 新增 worker_aio_requests、EPOLLEXCLUSIVE 详解
- 30: njs JavaScript 模块完整指南
- 31: OpenTelemetry 可观测性集成指南
- 32: ACME 自动证书管理指南

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-07 17:06:38 +08:00

1237 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ACME 自动证书管理指南
本文档详细介绍如何在 Nginx 中使用 `ngx_http_acme_module` 模块实现 ACME 协议自动证书管理,包括 Let's Encrypt 的配置、自动续期、多域名和通配符证书等高级用法。
---
## 1. ACME 协议概述
### 1.1 什么是 ACME
ACMEAutomatic Certificate Management Environment协议是由 Let's Encrypt 开发的标准协议,用于自动化域名验证和 SSL/TLS 证书颁发。它使服务器能够自动获取和续期证书,无需人工干预。
### 1.2 工作原理
ACME 工作流程分为两个阶段:
```
┌─────────────┐ ┌─────────────┐
│ ACME 客户端 │ ←──────────────→ │ CA 服务器 │
│ (Nginx) │ 1. 账户注册 │ (Let's │
│ │ 2. 订单创建 │ Encrypt) │
│ │ 3. 挑战验证 │ │
│ │ 4. 证书颁发 │ │
└─────────────┘ └─────────────┘
```
**完整流程:**
1. **账户注册**ACME 客户端生成密钥对并向 CA 注册账户
2. **订单创建**:客户端请求为指定域名颁发证书
3. **挑战验证**CA 要求客户端证明对域名的控制权(通过 HTTP-01、DNS-01 或 TLS-ALPN-01
4. **证书颁发**验证通过后CA 签发证书并发布到 Certificate Transparency (CT) 日志
### 1.3 挑战类型对比
| 挑战类型 | 验证方式 | 通配符支持 | 端口 80 要求 | 适用场景 |
|:--------:|:--------:|:----------:|:------------:|:---------|
| HTTP-01 | 在 `/.well-known/acme-challenge/` 放置文件 | ❌ | ✅ 必须可用 | 标准 Web 服务器,最简单 |
| DNS-01 | 添加 `_acme-challenge` TXT 记录 | ✅ | ❌ 不需要 | 通配符证书、内部服务器 |
| TLS-ALPN-01 | 通过 TLS ALPN 扩展验证 | ❌ | ❌ 不需要 | 仅支持 443 端口的环境 |
---
## 2. ngx_http_acme_module 指令详解
### 2.1 指令汇总表
| 指令 | 语法 | 默认值 | 上下文 | 描述 |
|:-----|:-----|:------:|:-------|:-----|
| `acme_issuer` | `acme_issuer name { ... }` | — | http | 定义 ACME 证书颁发机构对象 |
| `uri` | `uri uri;` | — | acme_issuer | ACME 服务器目录 URL必填 |
| `account_key` | `account_key alg[:size] \| file;` | — | acme_issuer | 账户私钥(支持 ecdsa/rsa 或文件路径) |
| `challenge` | `challenge type;` | `http-01` | acme_issuer | 挑战类型:`http-01``tls-alpn-01` |
| `contact` | `contact URL;` | — | acme_issuer | 联系邮箱(建议 `mailto:` 格式) |
| `external_account_key` | `external_account_key kid file;` | — | acme_issuer | 外部账户授权密钥EAB |
| `preferred_chain` | `preferred_chain name;` | — | acme_issuer | 指定首选证书链 |
| `profile` | `profile name [require];` | — | acme_issuer | 请求特定证书配置文件 |
| `ssl_trusted_certificate` | `ssl_trusted_certificate file;` | — | acme_issuer | 验证 ACME 服务器证书的 CA 证书 |
| `ssl_verify` | `ssl_verify on \| off;` | `on` | acme_issuer | 是否验证 ACME 服务器证书 |
| `state_path` | `state_path path \| off;` | `acme_<issuer>` | acme_issuer | 持久化存储路径(`off` 禁用) |
| `accept_terms_of_service` | `accept_terms_of_service;` | — | acme_issuer | 同意服务条款(部分服务器必需) |
| `acme_shared_zone` | `acme_shared_zone zone=name:size;` | `zone=ngx_acme_shared:256k` | http | 共享内存区大小 |
| `acme_certificate` | `acme_certificate issuer [identifier ...] [key=alg[:size]];` | — | server | 定义要请求的证书 |
### 2.2 嵌入式变量
在配置了 `acme_certificate``server` 块中可用:
| 变量 | 说明 | 用途 |
|:-----|:-----|:-----|
| `$acme_certificate` | SSL 证书路径 | `ssl_certificate` 指令 |
| `$acme_certificate_key` | SSL 证书私钥路径 | `ssl_certificate_key` 指令 |
---
## 3. Let's Encrypt 配置步骤
### 3.1 基础配置HTTP-01 挑战)
**步骤 1编译或启用模块**
确保 Nginx 包含 `ngx_http_acme_module` 模块:
```bash
nginx -V 2>&1 | grep -o 'http_acme_module'
```
**步骤 2配置 DNS 解析器**
ACME 模块需要解析 Let's Encrypt 服务器域名:
```nginx
# nginx.conf
# 配置 DNS 解析器(根据你的网络环境调整)
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 10s;
```
**步骤 3定义 ACME 颁发机构**
```nginx
# Let's Encrypt 生产环境
acme_issuer letsencrypt {
uri https://acme-v02.api.letsencrypt.org/directory;
contact mailto:admin@example.com;
state_path /var/cache/nginx/acme-letsencrypt;
accept_terms_of_service;
}
# Let's Encrypt 测试环境(开发调试时使用)
acme_issuer letsencrypt_staging {
uri https://acme-staging-v02.api.letsencrypt.org/directory;
contact mailto:admin@example.com;
state_path /var/cache/nginx/acme-staging;
accept_terms_of_service;
}
```
**步骤 4配置共享内存**
```nginx
# 增大共享内存以支持多个证书
acme_shared_zone zone=ngx_acme_shared:1M;
```
**步骤 5配置 HTTPS 服务器**
```nginx
server {
listen 443 ssl;
server_name www.example.com example.com;
# 启用 ACME 自动证书
acme_certificate letsencrypt;
# 使用 ACME 变量
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
# 证书缓存优化(避免每次请求都解析)
ssl_certificate_cache max=2;
# SSL 优化配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
root /var/www/html;
index index.html;
}
}
```
**步骤 6配置 HTTP 服务器(用于挑战)**
```nginx
server {
listen 80;
server_name www.example.com example.com;
# ACME HTTP-01 挑战需要访问 80 端口
# Nginx ACME 模块会自动处理 /.well-known/acme-challenge/ 路径
location / {
return 301 https://$server_name$request_uri;
}
}
```
### 3.2 首次启动和验证
```bash
# 测试配置语法
nginx -t
# 重载配置
nginx -s reload
# 查看日志确认证书申请状态
tail -f /var/log/nginx/error.log
```
### 3.3 Let's Encrypt 速率限制
| 限制类型 | 限制值 | 时间窗口 |
|:---------|:-------|:---------|
| 账户注册 | 10 个 | 每 IP 每 3 小时 |
| 新订单 | 300 个 | 每账户每 3 小时 |
| 域名证书 | 50 个 | 每域名每 7 天 |
| 相同标识符集 | 5 个 | 每 7 天 |
| 验证失败 | 5 次 | 每标识符每小时 |
**重要提示:**
- 使用 Staging 环境进行开发和测试
- 续期操作通常不会触发速率限制
- 使用 ARIACME Renewal Information可免除速率限制
---
## 4. 自动续期配置
### 4.1 自动续期原理
`ngx_http_acme_module` 模块会自动处理证书续期:
1. **监控证书有效期**:模块持续监控已颁发证书的过期时间
2. **自动续期触发**:在证书过期前自动发起续期请求
3. **无缝替换**:新证书获取后自动替换,无需重启 Nginx
4. **持久化存储**:证书和密钥存储在 `state_path` 指定的目录
### 4.2 状态目录结构
```
/var/cache/nginx/acme-letsencrypt/
├── account/ # 账户密钥和配置
│ ├── private.key # 账户私钥
│ └── registration.json # 账户注册信息
├── orders/ # 订单状态
│ └── *.json
└── certs/ # 颁发的证书
├── example.com/ # 按域名组织
│ ├── cert.pem # 证书
│ ├── chain.pem # 证书链
│ ├── fullchain.pem # 完整证书链
│ └── privkey.pem # 私钥
└── www.example.com/
```
### 4.3 配置证书续期监控
```nginx
# 可选:配置日志监控续期情况
error_log /var/log/nginx/acme.log info;
acme_issuer letsencrypt {
uri https://acme-v02.api.letsencrypt.org/directory;
contact mailto:admin@example.com;
state_path /var/cache/nginx/acme-letsencrypt;
accept_terms_of_service;
# 可选:指定首选证书链
preferred_chain "ISRG Root X1";
}
```
### 4.4 备份和恢复
**备份脚本:**
```bash
#!/bin/bash
# backup-acme.sh
BACKUP_DIR="/backup/nginx-acme/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
# 备份 ACME 状态目录
cp -r /var/cache/nginx/acme-letsencrypt "$BACKUP_DIR/"
# 备份 Nginx 配置
cp -r /etc/nginx "$BACKUP_DIR/nginx-config"
echo "ACME backup completed: $BACKUP_DIR"
```
**恢复脚本:**
```bash
#!/bin/bash
# restore-acme.sh
BACKUP_DIR="$1"
if [ -z "$BACKUP_DIR" ]; then
echo "Usage: $0 <backup-directory>"
exit 1
fi
# 恢复 ACME 状态
systemctl stop nginx
cp -r "$BACKUP_DIR/acme-letsencrypt" /var/cache/nginx/
chown -R nginx:nginx /var/cache/nginx/acme-letsencrypt
systemctl start nginx
echo "ACME restore completed from: $BACKUP_DIR"
```
### 4.5 监控和告警
**检查证书过期时间脚本:**
```bash
#!/bin/bash
# check-cert-expiry.sh
CERT_DIR="/var/cache/nginx/acme-letsencrypt/certs"
WARNING_DAYS=7
for cert_path in $CERT_DIR/*/cert.pem; do
if [ -f "$cert_path" ]; then
domain=$(basename $(dirname "$cert_path"))
expiry=$(openssl x509 -enddate -noout -in "$cert_path" | cut -d= -f2)
expiry_epoch=$(date -d "$expiry" +%s)
now_epoch=$(date +%s)
days_left=$(( ($expiry_epoch - $now_epoch) / 86400 ))
if [ $days_left -lt $WARNING_DAYS ]; then
echo "WARNING: Certificate for $domain expires in $days_left days"
else
echo "OK: Certificate for $domain expires in $days_left days"
fi
fi
done
```
---
## 5. 多域名证书管理
### 5.1 多域名证书SAN 证书)
单个证书可以包含多个域名Subject Alternative Names
```nginx
acme_issuer letsencrypt {
uri https://acme-v02.api.letsencrypt.org/directory;
contact mailto:admin@example.com;
state_path /var/cache/nginx/acme-letsencrypt;
accept_terms_of_service;
}
server {
listen 443 ssl;
# 主域名和多个别名
server_name example.com www.example.com api.example.com;
# 为所有 server_name 申请单个证书
acme_certificate letsencrypt;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
ssl_certificate_cache max=2;
location / {
proxy_pass http://backend;
}
}
```
### 5.2 独立域名证书
为不同域名申请独立证书:
```nginx
acme_issuer letsencrypt {
uri https://acme-v02.api.letsencrypt.org/directory;
contact mailto:admin@example.com;
state_path /var/cache/nginx/acme-letsencrypt;
accept_terms_of_service;
}
# 主站点
server {
listen 443 ssl;
server_name example.com www.example.com;
acme_certificate letsencrypt example.com www.example.com;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
ssl_certificate_cache max=2;
location / {
root /var/www/example;
}
}
# API 站点
server {
listen 443 ssl;
server_name api.example.com;
acme_certificate letsencrypt api.example.com;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
ssl_certificate_cache max=2;
location / {
proxy_pass http://api-backend;
}
}
# 博客站点
server {
listen 443 ssl;
server_name blog.example.com;
acme_certificate letsencrypt blog.example.com;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
ssl_certificate_cache max=2;
location / {
proxy_pass http://blog-backend;
}
}
```
### 5.3 多 ACME 账户配置
不同域名使用不同的 ACME 账户:
```nginx
# 主账户
acme_issuer letsencrypt_main {
uri https://acme-v02.api.letsencrypt.org/directory;
contact mailto:admin@example.com;
state_path /var/cache/nginx/acme-main;
accept_terms_of_service;
account_key ecdsa:384;
}
# 客户项目账户
acme_issuer letsencrypt_client {
uri https://acme-v02.api.letsencrypt.org/directory;
contact mailto:client-projects@example.com;
state_path /var/cache/nginx/acme-client;
accept_terms_of_service;
account_key ecdsa:256;
}
server {
listen 443 ssl;
server_name project1.example.com;
acme_certificate letsencrypt_client project1.example.com;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
}
```
---
## 6. 通配符证书配置DNS 验证)
### 6.1 DNS-01 挑战说明
通配符证书(如 `*.example.com`)必须使用 DNS-01 挑战类型验证。这要求:
1. 能够自动修改 DNS 记录(通过 DNS 提供商 API
2.`_acme-challenge.example.com` 添加 TXT 记录
3. 等待 DNS 传播后验证
**注意:** `ngx_http_acme_module` 本身不直接支持 DNS-01 挑战(需要外部 DNS 管理工具配合),可以使用 `certbot` 等工具获取通配符证书后由 Nginx 使用。
### 6.2 使用 Certbot 获取通配符证书
```bash
# 安装 certbot 和 DNS 插件(以 Cloudflare 为例)
# Ubuntu/Debian
sudo apt install certbot python3-certbot-dns-cloudflare
# CentOS/RHEL
sudo yum install certbot python3-certbot-dns-cloudflare
```
**配置 DNS API 凭证:**
```bash
# 创建 Cloudflare 凭证文件
sudo mkdir -p /etc/letsencrypt
sudo cat > /etc/letsencrypt/dnscloudflare.ini << 'EOF'
dns_cloudflare_api_token = your-api-token-here
EOF
sudo chmod 600 /etc/letsencrypt/dnscloudflare.ini
```
**申请通配符证书:**
```bash
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/dnscloudflare.ini \
-d "example.com" \
-d "*.example.com" \
--preferred-challenges dns-01
```
**自动续期配置:**
```bash
# 测试续期
certbot renew --dry-run
# 添加定时任务
echo "0 3 * * * root certbot renew --quiet --deploy-hook 'nginx -s reload'" | sudo tee -a /etc/crontab
```
### 6.3 Nginx 使用通配符证书
```nginx
server {
listen 443 ssl;
# 使用通配符匹配所有子域名
server_name *.example.com;
# 使用 certbot 获取的证书
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_certificate_cache max=2;
# 子域名路由
location / {
# 提取子域名
set $subdomain "";
if ($host ~* ^([^.]+)\.example\.com$) {
set $subdomain $1;
}
# 根据子域名代理到不同后端
proxy_pass http://$subdomain-backend;
}
}
# 主域名单独配置
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;
location / {
root /var/www/main;
}
}
```
### 6.4 主流 DNS 提供商 Certbot 插件
| 提供商 | 安装命令 | 配置方式 |
|:-------|:---------|:---------|
| Cloudflare | `python3-certbot-dns-cloudflare` | API Token |
| Route53 | `python3-certbot-dns-route53` | AWS IAM 凭证 |
| Alibaba Cloud | `certbot-dns-aliyun` | Access Key |
| Tencent Cloud | `certbot-dns-tencentcloud` | Secret ID/Key |
| GoDaddy | `certbot-dns-godaddy` | API Key/Secret |
---
## 7. 与 Certbot 方案对比
### 7.1 方案对比表
| 特性 | ngx_http_acme_module | Certbot |
|:-----|:--------------------:|:-------:|
| **集成度** | 内置于 Nginx无需外部工具 | 独立程序,需单独安装 |
| **配置复杂度** | 纯 Nginx 配置 | 需要额外配置和定时任务 |
| **HTTP-01 支持** | ✅ 原生支持 | ✅ 支持 |
| **DNS-01 支持** | ❌ 不支持 | ✅ 支持 |
| **通配符证书** | ❌ 不支持 | ✅ 支持 |
| **TLS-ALPN-01** | ✅ 支持 | ❌ 不支持 |
| **自动续期** | ✅ 自动,无需外部任务 | ✅ 需配置 cron/systemd timer |
| **证书热重载** | ✅ 无缝更新 | ⚠️ 需 reload/restart Nginx |
| **多 Web 服务器** | ❌ 仅 Nginx | ✅ Apache, Nginx 等 |
| **外部账户绑定** | ✅ 支持 EAB | ✅ 支持 |
### 7.2 选择建议
**选择 ngx_http_acme_module**
- 纯 Nginx 环境,追求配置简洁
- 使用 HTTP-01 或 TLS-ALPN-01 挑战
- 不需要通配符证书
- 希望证书续期完全自动化
**选择 Certbot**
- 需要通配符证书
- 使用 DNS-01 挑战
- 多 Web 服务器环境
- 需要与外部系统集成
### 7.3 混合方案
结合两者优势:使用 `ngx_http_acme_module` 处理常规证书,使用 `certbot` 处理通配符证书:
```nginx
# 常规域名使用内置 ACME
server {
listen 443 ssl;
server_name www.example.com api.example.com;
acme_certificate letsencrypt;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
}
# 通配符使用 certbot 证书
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;
}
```
---
## 8. 完整配置示例
### 8.1 单站点基础配置
```nginx
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# DNS 解析器配置
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 10s;
# ACME 共享内存
acme_shared_zone zone=ngx_acme_shared:1M;
# Let's Encrypt 配置
acme_issuer letsencrypt {
uri https://acme-v02.api.letsencrypt.org/directory;
contact mailto:admin@example.com;
state_path /var/cache/nginx/acme-letsencrypt;
accept_terms_of_service;
# 使用 ECDSA 账户密钥(更高效)
account_key ecdsa:384;
# 启用证书验证
ssl_verify on;
}
# HTTP 服务器 - 处理 ACME 挑战和重定向
server {
listen 80;
server_name example.com www.example.com;
# ACME 挑战自动处理
# 其他请求重定向到 HTTPS
location / {
return 301 https://$server_name$request_uri;
}
}
# HTTPS 服务器
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# 启用 ACME 自动证书
acme_certificate letsencrypt;
# 使用 ACME 变量
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
ssl_certificate_cache max=2;
# 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;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate $acme_certificate;
# 安全响应头
add_header Strict-Transport-Security "max-age=63072000; 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;
root /var/www/example;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
# 静态文件缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 6M;
access_log off;
}
}
}
```
### 8.2 多站点生产配置
```nginx
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 2048;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
# DNS 配置
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 10s;
# ACME 配置
acme_shared_zone zone=ngx_acme_shared:2M;
# Let's Encrypt 生产环境
acme_issuer letsencrypt {
uri https://acme-v02.api.letsencrypt.org/directory;
contact mailto:ssl@example.com;
state_path /var/cache/nginx/acme-letsencrypt;
accept_terms_of_service;
account_key ecdsa:384;
preferred_chain "ISRG Root X1";
}
# Let's Encrypt 测试环境
acme_issuer letsencrypt_staging {
uri https://acme-staging-v02.api.letsencrypt.org/directory;
contact mailto:ssl@example.com;
state_path /var/cache/nginx/acme-staging;
accept_terms_of_service;
}
# 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;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
# 通用安全响应头
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;
# 站点配置目录
include /etc/nginx/conf.d/*.conf;
}
```
```nginx
# /etc/nginx/conf.d/01-http-default.conf
# HTTP 默认服务器 - 处理所有 80 端口请求
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
# ACME 挑战处理
location /.well-known/acme-challenge/ {
# 由 ngx_http_acme_module 自动处理
root /var/cache/nginx/acme-challenges;
}
# 所有其他请求重定向到 HTTPS
location / {
return 301 https://$host$request_uri;
}
}
```
```nginx
# /etc/nginx/conf.d/10-example.com.conf
# 主站点
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
acme_certificate letsencrypt;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
ssl_certificate_cache max=2;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
root /var/www/example;
index index.html index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP 处理
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
```
```nginx
# /etc/nginx/conf.d/20-api.example.com.conf
# API 站点
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name api.example.com;
acme_certificate letsencrypt api.example.com;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
ssl_certificate_cache max=2;
add_header Strict-Transport-Security "max-age=63072000" always;
# API 限流
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req zone=api burst=20 nodelay;
location / {
proxy_pass http://api_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
proxy_cache_bypass $http_upgrade;
}
}
upstream api_backend {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
keepalive 32;
}
```
### 8.3 测试环境配置
```nginx
# 测试/开发环境使用 Staging
acme_issuer letsencrypt_staging {
uri https://acme-staging-v02.api.letsencrypt.org/directory;
contact mailto:dev@example.com;
state_path /var/cache/nginx/acme-staging;
accept_terms_of_service;
}
server {
listen 443 ssl;
server_name staging.example.com;
# 开发环境使用 Staging
acme_certificate letsencrypt_staging staging.example.com;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
# 注意Staging 证书不会被浏览器信任
# 仅用于测试续期流程
location / {
root /var/www/staging;
}
}
```
---
## 9. 故障排查指南
### 9.1 常见问题及解决方案
#### 问题 1证书申请失败验证失败
**症状:**
```
[error] acme: challenge failed for example.com
```
**排查步骤:**
1. **检查 DNS 解析**
```bash
nslookup example.com
dig example.com A
```
2. **检查 80 端口可访问性**
```bash
# 从外部测试
curl -I http://example.com/
# 检查防火墙
sudo iptables -L -n | grep 80
```
3. **检查 Nginx 错误日志**
```bash
sudo tail -f /var/log/nginx/error.log
```
4. **验证挑战响应**
```bash
# 手动测试挑战 URL
curl http://example.com/.well-known/acme-challenge/test
```
**解决方案:**
- 确保域名 DNS 解析正确且已生效
- 确保防火墙允许 80 端口入站连接
- 确保 `server_name` 包含申请证书的域名
#### 问题 2DNS 解析失败
**症状:**
```
[error] could not resolve acme-v02.api.letsencrypt.org
```
**解决方案:**
```nginx
# 检查 resolver 配置
http {
# 使用可靠的 DNS 服务器
resolver 8.8.8.8 8.8.4.4 1.1.1.1 valid=300s;
resolver_timeout 30s;
...
}
```
**测试 DNS 解析:**
```bash
# 测试系统 DNS
nslookup acme-v02.api.letsencrypt.org
# 测试 Nginx 配置语法
nginx -t
```
#### 问题 3速率限制错误
**症状:**
```
[error] acme: 429 Too Many Requests
```
**排查:**
```bash
# 检查最近申请记录
grep "acme" /var/log/nginx/error.log | tail -20
```
**解决方案:**
- 切换到 Staging 环境进行测试
- 等待当前速率限制窗口重置
- 检查是否有重复申请配置
- 使用现有证书而不是重新申请
#### 问题 4证书不自动续期
**症状:**
证书过期但未自动续期
**排查:**
```bash
# 检查证书状态
openssl x509 -in /var/cache/nginx/acme-letsencrypt/certs/example.com/cert.pem -noout -dates
# 检查 Nginx 错误日志
grep -i "acme\|certificate" /var/log/nginx/error.log
```
**解决方案:**
```nginx
# 确保配置正确
acme_issuer letsencrypt {
uri https://acme-v02.api.letsencrypt.org/directory;
contact mailto:admin@example.com;
state_path /var/cache/nginx/acme-letsencrypt; # 确保有写入权限
accept_terms_of_service;
}
```
**检查权限:**
```bash
# 确保 Nginx 用户可以写入 state_path
sudo chown -R nginx:nginx /var/cache/nginx/acme-letsencrypt
sudo chmod 700 /var/cache/nginx/acme-letsencrypt
```
#### 问题 5$acme_certificate 变量为空
**症状:**
```
[emerg] BIO_new_file("$acme_certificate") failed
```
**解决方案:**
```nginx
# 确保 acme_certificate 指令在 server 块中
server {
listen 443 ssl;
server_name example.com;
# 必须先声明 acme_certificate
acme_certificate letsencrypt;
# 然后才能使用变量
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
}
```
### 9.2 调试配置
**启用详细日志:**
```nginx
# 开发调试时使用
error_log /var/log/nginx/acme-debug.log debug;
acme_issuer letsencrypt_staging {
uri https://acme-staging-v02.api.letsencrypt.org/directory;
contact mailto:debug@example.com;
state_path /var/cache/nginx/acme-staging;
accept_terms_of_service;
# 禁用服务器证书验证(仅测试时使用)
# ssl_verify off;
}
```
### 9.3 诊断脚本
```bash
#!/bin/bash
# diagnose-acme.sh - ACME 诊断脚本
echo "=== Nginx ACME 诊断 ==="
echo
# 检查 Nginx 版本和模块
echo "1. Nginx 版本和模块:"
nginx -V 2>&1 | grep -E "(nginx version|http_acme_module)"
echo
# 检查配置语法
echo "2. 配置语法检查:"
nginx -t
echo
# 检查 DNS 解析
echo "3. DNS 解析测试:"
nslookup acme-v02.api.letsencrypt.org 2>/dev/null || echo "DNS 解析失败"
echo
# 检查证书目录
echo "4. ACME 状态目录:"
for dir in /var/cache/nginx/acme-*; do
if [ -d "$dir" ]; then
echo " 目录: $dir"
echo " 大小: $(du -sh "$dir" 2>/dev/null | cut -f1)"
echo " 权限: $(stat -c %a "$dir" 2>/dev/null || stat -f %A "$dir" 2>/dev/null)"
echo " 所有者: $(stat -c %U:%G "$dir" 2>/dev/null || stat -f %Su:%Sg "$dir" 2>/dev/null)"
fi
done
echo
# 检查现有证书
echo "5. 现有证书:"
for cert in /var/cache/nginx/acme-*/certs/*/cert.pem; do
if [ -f "$cert" ]; then
domain=$(basename $(dirname "$cert"))
echo " 域名: $domain"
openssl x509 -in "$cert" -noout -dates -subject 2>/dev/null | sed 's/^/ /'
echo
fi
done
# 检查端口监听
echo "6. 端口监听:"
ss -tlnp | grep -E ":80|:443" | sed 's/^/ /'
echo
# 检查防火墙
echo "7. 防火墙状态:"
if command -v iptables &> /dev/null; then
iptables -L -n | grep -E "80|443" | sed 's/^/ /'
else
echo " iptables 不可用"
fi
echo
# 检查错误日志
echo "8. 最近的 ACME 相关错误:"
grep -i "acme" /var/log/nginx/error.log 2>/dev/null | tail -10 | sed 's/^/ /'
echo
echo "=== 诊断完成 ==="
```
### 9.4 获取帮助
**官方资源:**
- Nginx ACME 模块文档https://nginx.org/en/docs/http/ngx_http_acme_module.html
- Let's Encrypt 文档https://letsencrypt.org/docs/
- Let's Encrypt 社区https://community.letsencrypt.org/
**测试工具:**
- SSL Labs 测试https://www.ssllabs.com/ssltest/
- Let's Debughttps://letsdebug.net/(诊断域名验证问题)
---
## 附录:快速参考
### 配置检查清单
- [ ] DNS 解析器已配置(`resolver`
- [ ] `acme_shared_zone` 已定义
- [ ] `acme_issuer` 块配置正确
- [ ] `acme_certificate` 指令在 `server` 块中
- [ ] `ssl_certificate` 使用 `$acme_certificate` 变量
- [ ] 80 端口服务器配置正确
- [ ] state_path 目录可写
- [ ] 防火墙允许 80/443 端口
- [ ] 域名 DNS 已生效
### 常用命令
```bash
# 测试配置
nginx -t
# 重载配置
nginx -s reload
# 查看证书信息
openssl x509 -in cert.pem -noout -text
# 查看证书过期时间
openssl x509 -in cert.pem -noout -dates
# 手动清理状态(谨慎使用)
rm -rf /var/cache/nginx/acme-letsencrypt/certs/example.com
```
---
*文档版本1.0*
*最后更新2025年4月*