Let's Encrypt 免费 SSL 证书
概述
Let's Encrypt 是一个免费、自动化和开放的证书颁发机构(CA),提供免费的 SSL/TLS 证书。它通过 ACME(Automated Certificate Management Environment)协议实现证书的自动颁发和续期。
主要特点
- 完全免费:无需支付任何费用
- 自动化:支持自动颁发和续期
- 开放透明:所有证书记录公开可查
- 安全可靠:符合行业标准的加密算法
验证方式
Let's Encrypt 支持多种域名验证方式:
- HTTP-01 挑战:通过 HTTP 验证
- DNS-01 挑战:通过 DNS 记录验证
- TLS-ALPN-01 挑战:通过 TLS 握手验证
Webroot 方式配置
Webroot 方式是使用 HTTP-01 挑战验证的常用方法,特别适合已经运行 Web 服务器的环境。
前提条件
- 拥有域名控制权
- 服务器已安装 Web 服务器(如 Nginx)
- 服务器可以访问互联网
安装 Certbot
Ubuntu/Debian
sudo apt update
sudo apt install certbot
CentOS/RHEL
sudo yum install epel-release
sudo yum install certbot
配置 Web 服务器
Nginx 配置示例
配置目的: 这个 Nginx 配置是为了支持 Let's Encrypt 的 HTTP-01 挑战验证。具体作用包括:
- 提供验证文件访问路径:让 Let's Encrypt 能够通过 HTTP 访问验证文件
- 处理验证请求:将
/.well-known/acme-challenge/路径的请求正确路由到文件系统 - 强制 HTTPS 重定向:将所有其他 HTTP 请求重定向到 HTTPS
- 确保验证成功:通过
try_files指令确保验证文件能被正确访问
在 Nginx 配置文件中添加以下内容:
server {
listen 80;
server_name admin.cqduke.com;
# Webroot 路径配置
location ^~ /.well-known/acme-challenge/ {
root /tools/nginx/html; # 与 --webroot-path 一致
default_type "text/plain";
try_files $uri =404;
}
#return 301 https://$host$request_uri;
}
配置说明:
location ^~ /.well-known/acme-challenge/:匹配验证路径,^~表示优先匹配root /tools/nginx/html:指定验证文件的根目录default_type "text/plain":设置默认 MIME 类型为纯文本try_files $uri =404:尝试访问文件,不存在则返回 404return 301 https://$host$request_uri:将其他请求重定向到 HTTPS
创建 Webroot 目录
创建目的
创建 Webroot 目录是为了支持 Let's Encrypt 的 HTTP-01 挑战验证方式。具体目的包括:
- 支持 HTTP-01 挑战验证:Let's Encrypt 会向你的域名发送特殊的 HTTP 请求来验证域名控制权
- 提供验证文件访问路径:确保
http://your-domain.com/.well-known/acme-challenge/[token]路径可访问 - 确保文件权限正确:让 Web 服务器能够读写验证文件
- 与 Nginx 配置配合:为 Nginx 的
location配置提供对应的文件系统路径 - 实现自动化验证:让 Certbot 能够自动创建、访问和清理验证文件
目录创建命令
sudo mkdir -p /tools/nginx/html/.well-known/acme-challenge
sudo chown -R www-data:www-data /tools/nginx/html/.well-known
sudo chmod -R 755 /tools/nginx/html/.well-known
命令说明:
mkdir -p:创建完整的目录结构,包括父目录chown:设置目录所有者为 Web 服务器用户(www-data)chmod 755:设置适当的访问权限,确保 Web 服务器可读写
申请证书
申请目的: 使用 webroot 方式申请 Let's Encrypt SSL 证书,通过 HTTP-01 挑战验证域名所有权。
申请过程:
- 验证域名控制权:Let's Encrypt 会向你的域名发送验证请求
- 生成证书文件:验证成功后自动生成证书和私钥
- 存储证书:证书文件存储在
/etc/letsencrypt/live/your-domain.com/目录
使用 webroot 方式申请证书:
sudo certbot certonly --webroot \
--webroot-path=/var/www/html \
--email your-email@example.com \
--agree-tos \
--no-eff-email \
-d your-domain.com \
-d www.your-domain.com
命令参数说明:
--webroot:使用 webroot 验证方式--webroot-path:指定验证文件的根目录路径--email:指定联系邮箱(用于证书到期通知)--agree-tos:同意 Let's Encrypt 服务条款--no-eff-email:不订阅 EFF 邮件列表-d:指定要申请证书的域名(可指定多个)
配置 SSL
配置目的: 配置 Web 服务器使用 Let's Encrypt 证书,启用 HTTPS 加密访问,提升网站安全性。
SSL 配置要点:
- 证书路径:指向 Let's Encrypt 生成的证书文件
- 加密协议:使用现代 TLS 协议(TLSv1.2 和 TLSv1.3)
- 加密套件:配置强加密算法
- 会话缓存:优化 SSL 连接性能
- 安全头部:添加安全相关的 HTTP 头部
Nginx SSL 配置
# HTTP 重定向到 HTTPS
server {
listen 80;
server_name admin.cqduke.com;
# Webroot 路径配置
location ^~ /.well-known/acme-challenge/ {
root /tools/nginx/html; # 与 --webroot-path 一致
default_type "text/plain";
try_files $uri =404;
}
return 301 https://$host$request_uri;
}
# HTTPS 服务器配置
server {
listen 443 ssl;
server_name admin.cqduke.com;
# SSL 证书配置
ssl_certificate /etc/letsencrypt/live/admin.cqduke.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/admin.cqduke.com/privkey.pem;
# 现代加密协议
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# HSTS 安全头(推荐)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# 前端静态文件
location / {
root /tools/nginx/html/admin/dist;
index index.html;
try_files $uri $uri/ /index.html;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
# Webroot 路径配置
location /.well-known/acme-challenge/ {
root /tools/nginx/html;
}
# API代理到本地后端
location /api/ {
proxy_pass http://localhost:9999/;
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;
}
#knife4j文档
location /doc.html {
proxy_pass http://localhost:9999;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
access_log /tools/nginx/logs/admin.cqduke.com.access.log main;
error_log /tools/nginx/logs/admin.cqduke.com.error.log error;
}
配置说明:
listen 443 ssl http2:监听 443 端口,启用 SSL 和 HTTP/2ssl_certificate:指定证书文件路径(包含完整证书链)ssl_certificate_key:指定私钥文件路径ssl_protocols:指定支持的 TLS 协议版本ssl_ciphers:指定加密套件,优先使用前向保密算法ssl_session_cache:启用 SSL 会话缓存,提高性能ssl_session_timeout:设置会话超时时间
自动续期
续期目的: Let's Encrypt 证书有效期为 90 天,需要定期续期以确保持续的 HTTPS 访问。
续期机制:
- 自动检测:Certbot 会检查证书到期时间
- 智能续期:只在证书即将过期时才执行续期
- 无缝切换:续期过程中不影响网站访问
- 通知机制:续期失败时发送邮件通知
续期方式:
- 智能续期:检查证书状态,只在需要时续期
- 简单续期:直接执行续期命令(传统方式)
- 手动续期:使用
certbot renew命令 - 测试续期:使用
--dry-run参数测试续期流程
方式一:简单续期(传统方式)
适用场景:希望使用简单直接的续期方式,不关心证书是否即将过期。
创建简单续期脚本:
sudo crontab -e
添加以下内容(每天凌晨 2 点检查续期):
0 2 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"
简单续期说明:
0 2 * * *:每天凌晨 2 点执行certbot renew:续期命令--quiet:静默模式,不输出详细信息--post-hook:续期成功后执行的重载命令systemctl reload nginx:重新加载 Nginx 配置
方式二:智能续期脚本(推荐)
适用场景:希望智能检查证书状态,只在证书即将过期时才执行续期,避免不必要的操作。
创建续期脚本:
sudo nano /etc/letsencrypt/renewal-hook.sh
添加以下智能续期脚本内容:
#!/bin/bash
# 智能续期脚本 - 检查证书状态并只在需要时续期
# 作者:系统管理员
# 日期:$(date +%Y-%m-%d)
# 配置变量
DOMAIN="admin.cqduke.com"
CERT_PATH="/etc/letsencrypt/live/${DOMAIN}/cert.pem"
LOG_FILE="/var/log/letsencrypt-renewal.log"
DAYS_BEFORE_EXPIRY=30 # 证书到期前30天开始续期
# 日志函数
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# 检查证书是否存在
check_cert_exists() {
if [ ! -f "$CERT_PATH" ]; then
log_message "错误:证书文件不存在 - $CERT_PATH"
return 1
fi
return 0
}
# 检查证书是否即将过期
check_cert_expiry() {
if ! check_cert_exists; then
return 1
fi
# 获取证书到期时间
local expiry_date=$(openssl x509 -enddate -noout -in "$CERT_PATH" | cut -d= -f2)
local expiry_timestamp=$(date -d "$expiry_date" +%s)
local current_timestamp=$(date +%s)
local days_until_expiry=$(( (expiry_timestamp - current_timestamp) / 86400 ))
log_message "证书到期时间:$expiry_date"
log_message "距离到期还有:$days_until_expiry 天"
if [ $days_until_expiry -le $DAYS_BEFORE_EXPIRY ]; then
log_message "证书即将过期(${days_until_expiry}天),需要续期"
return 0 # 需要续期
else
log_message "证书还有${days_until_expiry}天到期,无需续期"
return 1 # 无需续期
fi
}
# 执行续期
perform_renewal() {
log_message "开始执行证书续期..."
# 检查 Certbot 是否可用
if ! command -v certbot &> /dev/null; then
log_message "错误:Certbot 未安装或不可用"
return 1
fi
# 执行续期(使用 webroot 方式)
if certbot renew --webroot --webroot-path=/tools/nginx/html --quiet; then
log_message "证书续期成功"
# 重新加载 Nginx 配置
if systemctl reload nginx; then
log_message "Nginx 配置重新加载成功"
return 0
else
log_message "警告:Nginx 重新加载失败"
return 1
fi
else
log_message "错误:证书续期失败"
return 1
fi
}
# 主函数
main() {
log_message "=== 开始证书续期检查 ==="
# 检查证书状态
if check_cert_expiry; then
# 证书即将过期,执行续期
if perform_renewal; then
log_message "证书续期流程完成"
else
log_message "证书续期流程失败"
exit 1
fi
else
# 证书无需续期
log_message "证书状态正常,无需续期"
fi
log_message "=== 证书续期检查完成 ==="
}
# 执行主函数
main "$@"
设置执行权限:
sudo chmod +x /etc/letsencrypt/renewal-hook.sh
配置定时任务
sudo crontab -e
添加以下内容(每天凌晨 2 点检查续期):
# 每天凌晨 2 点执行智能续期检查
0 2 * * * /etc/letsencrypt/renewal-hook.sh
# 每周日凌晨 3 点执行测试续期(可选)
0 3 * * 0 /usr/bin/certbot renew --dry-run --webroot --webroot-path=/tools/nginx/html >> /var/log/letsencrypt-test.log 2>&1
两种续期方式对比
| 特性 | 简单续期(方式一) | 智能续期(方式二) |
|---|---|---|
| 复杂度 | 简单 | 中等 |
| 资源消耗 | 每次都会执行续期 | 只在需要时执行续期 |
| 日志记录 | 基本日志 | 详细日志记录 |
| 错误处理 | 基本 | 完善 |
| 适用场景 | 简单环境 | 生产环境推荐 |
| 维护成本 | 低 | 中等 |
智能续期脚本功能说明
智能检查功能:
- 证书存在性检查:验证证书文件是否存在
- 到期时间检查:计算距离证书到期的天数
- 智能续期判断:只在证书到期前 30 天执行续期
- 详细日志记录:记录所有操作和状态信息
安全特性:
- 错误处理和日志记录
- 证书状态验证
- Nginx 配置重载确认
- 失败通知机制
使用方法:
- 自动运行:通过 crontab 定时执行
- 手动运行:
sudo /etc/letsencrypt/renewal-hook.sh - 查看日志:
tail -f /var/log/letsencrypt-renewal.log
常见问题解决
1. 权限问题
sudo chown -R www-data:www-data /var/www/html/.well-known
sudo chmod -R 755 /var/www/html/.well-known
2. 防火墙设置
确保端口 80 和 443 开放:
sudo ufw allow 80
sudo ufw allow 443
3. 测试配置
# 测试 Nginx 配置
sudo nginx -t
Standalone 方式配置
Standalone 方式是 Let's Encrypt 的另一种验证方法,特别适合以下场景:
- 服务器上没有运行 Web 服务器
- 需要快速获取证书进行测试
- 在容器化环境中使用
- 临时获取证书用于开发环境
工作原理
Standalone 方式会临时启动一个内置的 Web 服务器(默认监听 80 端口),用于完成 HTTP-01 挑战验证。验证完成后,内置服务器会自动关闭。
前提条件
- 拥有域名控制权
- 服务器可以访问互联网
- 端口 80 未被占用(申请证书时)
- 域名已正确解析到服务器 IP
安装 Certbot
安装方法与 Webroot 方式相同:
Ubuntu/Debian
sudo apt update
sudo apt install certbot
CentOS/RHEL
sudo yum install epel-release
sudo yum install certbot
申请证书
使用 standalone 方式申请证书:
sudo certbot certonly --standalone \
--email your-email@example.com \
--agree-tos \
--no-eff-email \
-d your-domain.com \
-d www.your-domain.com
高级配置选项
指定端口
如果 80 端口被占用,可以指定其他端口:
sudo certbot certonly --standalone \
--preferred-challenges http \
--http-01-port 8080 \
--email your-email@example.com \
--agree-tos \
--no-eff-email \
-d your-domain.com
指定证书目录
sudo certbot certonly --standalone \
--cert-path /path/to/custom/cert \
--key-path /path/to/custom/key \
--email your-email@example.com \
--agree-tos \
--no-eff-email \
-d your-domain.com
配置 Web 服务器
申请证书后,需要配置 Web 服务器使用新证书:
Nginx 配置示例
server {
listen 80;
server_name your-domain.com www.your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com www.your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
# SSL 配置优化
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 其他配置...
location / {
root /var/www/html;
index index.html index.htm;
}
}
自动续期
创建续期脚本
sudo nano /etc/letsencrypt/renewal-hook.sh
添加以下内容:
Docker 环境中的 Let's Encrypt 配置
当您的 Nginx 运行在 Docker 容器中时,配置 Let's Encrypt 证书需要特殊的处理方式。以下是几种适用于 Docker 环境的解决方案。
纯 Docker 命令配置方案
1. 创建证书目录
目的:在宿主机上创建必要的目录结构,用于存储 Let's Encrypt 证书和验证文件。
# 创建 Let's Encrypt 证书存储目录
sudo mkdir -p /etc/letsencrypt
# 创建 Let's Encrypt 数据目录
sudo mkdir -p /var/lib/letsencrypt
# 创建 Certbot 验证文件目录
sudo mkdir -p /var/www/certbot
# 创建 Nginx 配置目录
sudo mkdir -p /opt/nginx/conf.d
sudo mkdir -p /opt/nginx/html
sudo mkdir -p /opt/nginx/logs
2. 创建 Docker 网络
# 创建自定义网络
docker network create nginx-ssl-network
3. 创建 Nginx 配置文件
创建 Nginx 配置文件
创建 /opt/nginx/conf.d/default.conf:
# HTTP 服务器配置(用于验证和重定向)
server {
listen 80;
server_name your-domain.com www.your-domain.com;
# Let's Encrypt 验证路径
location /.well-known/acme-challenge/ {
root /var/www/certbot;
try_files $uri =404;
}
# 其他 HTTP 请求重定向到 HTTPS
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS 服务器配置
server {
listen 443 ssl http2;
server_name your-domain.com www.your-domain.com;
# SSL 证书配置
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
# 现代 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_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全头部
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# 网站根目录
root /usr/share/nginx/html;
index index.html index.htm;
# 静态文件处理
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# API 代理(如果需要)
location /api/ {
proxy_pass http://backend: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;
}
# 日志配置
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}
4. 启动 Nginx 容器
# 启动 Nginx 容器
docker run -d \
--name nginx-ssl \
--network nginx-ssl-network \
-p 80:80 \
-p 443:443 \
-v /opt/nginx/conf.d:/etc/nginx/conf.d:ro \
-v /opt/nginx/html:/usr/share/nginx/html:ro \
-v /opt/nginx/logs:/var/log/nginx \
-v /etc/letsencrypt:/etc/letsencrypt:ro \
-v /var/www/certbot:/var/www/certbot:ro \
--restart unless-stopped \
nginx:alpine
5. 申请 SSL 证书
# 申请证书
docker run --rm \
--network nginx-ssl-network \
-v /etc/letsencrypt:/etc/letsencrypt \
-v /var/lib/letsencrypt:/var/lib/letsencrypt \
-v /var/www/certbot:/var/www/certbot \
certbot/certbot certonly \
--webroot \
--webroot-path=/var/www/certbot \
--email your-email@example.com \
--agree-tos \
--no-eff-email \
-d your-domain.com \
-d www.your-domain.com
6. 重新加载 Nginx 配置
# 重新加载 Nginx 配置
docker exec nginx-ssl nginx -s reload
7. 创建自动续期脚本
创建续期脚本
创建 /opt/scripts/renew-certs-docker.sh:
#!/bin/bash
# 纯 Docker 环境中的 Let's Encrypt 自动续期脚本
# 适用于不使用 docker-compose 的环境
# 配置变量
DOMAIN="your-domain.com"
CONTAINER_NAME="nginx-ssl"
NETWORK_NAME="nginx-ssl-network"
LOG_FILE="/var/log/letsencrypt-docker-renewal.log"
DAYS_BEFORE_EXPIRY=30
# 日志函数
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# 检查证书是否存在
check_cert_exists() {
local cert_path="/etc/letsencrypt/live/${DOMAIN}/cert.pem"
if [ ! -f "$cert_path" ]; then
log_message "错误:证书文件不存在 - $cert_path"
return 1
fi
return 0
}
# 检查证书是否即将过期
check_cert_expiry() {
if ! check_cert_exists; then
return 1
fi
local cert_path="/etc/letsencrypt/live/${DOMAIN}/cert.pem"
local expiry_date=$(openssl x509 -enddate -noout -in "$cert_path" | cut -d= -f2)
local expiry_timestamp=$(date -d "$expiry_date" +%s)
local current_timestamp=$(date +%s)
local days_until_expiry=$(( (expiry_timestamp - current_timestamp) / 86400 ))
log_message "证书到期时间:$expiry_date"
log_message "距离到期还有:$days_until_expiry 天"
if [ $days_until_expiry -le $DAYS_BEFORE_EXPIRY ]; then
log_message "证书即将过期(${days_until_expiry}天),需要续期"
return 0
else
log_message "证书还有${days_until_expiry}天到期,无需续期"
return 1
fi
}
# 检查容器是否存在
check_container_exists() {
if ! docker ps -a --format "table {{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then
log_message "错误:容器 ${CONTAINER_NAME} 不存在"
return 1
fi
return 0
}
# 检查容器是否运行
check_container_running() {
if ! docker ps --format "table {{.Names}}" | grep -q "^${CONTAINER_NAME}$"; then
log_message "错误:容器 ${CONTAINER_NAME} 未运行"
return 1
fi
return 0
}
# 执行续期
perform_renewal() {
log_message "开始执行证书续期..."
# 检查容器状态
if ! check_container_exists; then
return 1
fi
if ! check_container_running; then
log_message "尝试启动容器 ${CONTAINER_NAME}..."
docker start ${CONTAINER_NAME}
sleep 5
if ! check_container_running; then
log_message "错误:无法启动容器 ${CONTAINER_NAME}"
return 1
fi
fi
# 执行续期
if docker run --rm \
--network ${NETWORK_NAME} \
-v /etc/letsencrypt:/etc/letsencrypt \
-v /var/lib/letsencrypt:/var/lib/letsencrypt \
-v /var/www/certbot:/var/www/certbot \
certbot/certbot renew \
--webroot \
--webroot-path=/var/www/certbot; then
log_message "证书续期成功"
# 重新加载 Nginx 配置
if docker exec ${CONTAINER_NAME} nginx -s reload; then
log_message "Nginx 配置重新加载成功"
return 0
else
log_message "警告:Nginx 重新加载失败"
return 1
fi
else
log_message "错误:证书续期失败"
return 1
fi
}
# 主函数
main() {
log_message "=== 开始纯 Docker 环境证书续期检查 ==="
# 检查证书状态
if check_cert_expiry; then
# 证书即将过期,执行续期
if perform_renewal; then
log_message "证书续期流程完成"
else
log_message "证书续期流程失败"
exit 1
fi
else
# 证书无需续期
log_message "证书状态正常,无需续期"
fi
log_message "=== 纯 Docker 环境证书续期检查完成 ==="
}
# 执行主函数
main "$@"
设置执行权限和定时任务
# 设置执行权限
sudo chmod +x /opt/scripts/renew-certs-docker.sh
# 添加到 crontab
sudo crontab -e
添加以下内容:
# 每天凌晨 2 点执行纯 Docker 环境证书续期检查
0 2 * * * /opt/scripts/renew-certs-docker.sh
# 每周日凌晨 3 点执行测试续期
0 3 * * 0 docker run --rm --network nginx-ssl-network -v /etc/letsencrypt:/etc/letsencrypt -v /var/lib/letsencrypt:/var/lib/letsencrypt -v /var/www/certbot:/var/www/certbot certbot/certbot renew --dry-run --webroot --webroot-path=/var/www/certbot >> /var/log/letsencrypt-test.log 2>&1
8. 验证配置
检查证书状态
# 查看证书信息
docker run --rm \
--network nginx-ssl-network \
-v /etc/letsencrypt:/etc/letsencrypt \
-v /var/lib/letsencrypt:/var/lib/letsencrypt \
certbot/certbot certificates
# 检查证书有效期
openssl x509 -in /etc/letsencrypt/live/your-domain.com/cert.pem -text -noout | grep -A 2 "Validity"
# 测试 SSL 连接
openssl s_client -connect your-domain.com:443 -servername your-domain.com
检查 Nginx 配置
# 检查 Nginx 配置语法
docker exec nginx-ssl nginx -t
# 查看 Nginx 日志
docker logs nginx-ssl
# 检查容器状态
docker ps -a --filter name=nginx-ssl
9. 管理命令
常用管理命令
# 查看容器状态
docker ps -a --filter name=nginx-ssl
# 查看容器日志
docker logs nginx-ssl
# 进入容器
docker exec -it nginx-ssl sh
# 重启容器
docker restart nginx-ssl
# 停止容器
docker stop nginx-ssl
# 启动容器
docker start nginx-ssl
# 删除容器
docker rm nginx-ssl
# 查看网络
docker network ls
docker network inspect nginx-ssl-network
10. 故障排除
常见问题及解决方案
- 证书申请失败
# 检查域名解析
nslookup your-domain.com
# 检查端口是否开放
sudo netstat -tlnp | grep :80
sudo netstat -tlnp | grep :443
# 查看 Certbot 详细日志
docker run --rm --network nginx-ssl-network -v /etc/letsencrypt:/etc/letsencrypt -v /var/lib/letsencrypt:/var/lib/letsencrypt -v /var/www/certbot:/var/www/certbot certbot/certbot certificates --verbose
- Nginx 无法访问证书文件
# 检查证书文件权限
ls -la /etc/letsencrypt/live/your-domain.com/
# 修复权限
sudo chown -R root:root /etc/letsencrypt
sudo chmod -R 755 /etc/letsencrypt
# 检查容器挂载
docker inspect nginx-ssl | grep -A 10 "Mounts"
- 容器网络问题
# 检查容器网络
docker network ls
docker network inspect nginx-ssl-network
# 重新创建网络
docker network rm nginx-ssl-network
docker network create nginx-ssl-network
# 重新启动容器
docker stop nginx-ssl
docker rm nginx-ssl
# 然后重新运行启动命令
- 续期失败
# 手动测试续期
docker run --rm --network nginx-ssl-network -v /etc/letsencrypt:/etc/letsencrypt -v /var/lib/letsencrypt:/var/lib/letsencrypt -v /var/www/certbot:/var/www/certbot certbot/certbot renew --dry-run --webroot --webroot-path=/var/www/certbot
# 检查证书状态
docker run --rm --network nginx-ssl-network -v /etc/letsencrypt:/etc/letsencrypt -v /var/lib/letsencrypt:/var/lib/letsencrypt certbot/certbot certificates
# 查看续期日志
tail -f /var/log/letsencrypt-docker-renewal.log
11. 安全建议
- 定期备份证书:
# 备份证书目录
sudo tar -czf /backup/letsencrypt-$(date +%Y%m%d).tar.gz /etc/letsencrypt
# 创建备份脚本
cat > /opt/scripts/backup-certs.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d_%H%M%S)
tar -czf ${BACKUP_DIR}/letsencrypt-${DATE}.tar.gz /etc/letsencrypt
find ${BACKUP_DIR} -name "letsencrypt-*.tar.gz" -mtime +30 -delete
EOF
chmod +x /opt/scripts/backup-certs.sh
- 监控证书状态:
# 创建监控脚本
cat > /opt/scripts/monitor-certs.sh << 'EOF'
#!/bin/bash
DOMAIN="your-domain.com"
LOG_FILE="/var/log/cert-monitor.log"
CERT_PATH="/etc/letsencrypt/live/${DOMAIN}/cert.pem"
if [ -f "$CERT_PATH" ]; then
EXPIRY=$(openssl x509 -enddate -noout -in "$CERT_PATH" | cut -d= -f2)
echo "$(date) - 证书到期时间: $EXPIRY" >> $LOG_FILE
else
echo "$(date) - 错误: 证书文件不存在" >> $LOG_FILE
fi
EOF
chmod +x /opt/scripts/monitor-certs.sh
- 设置告警:
# 在续期脚本中添加邮件通知
if [ $? -ne 0 ]; then
echo "证书续期失败 - $(date)" | mail -s "Let's Encrypt 续期失败" admin@your-domain.com
fi
12. 方案对比
| 特性 | Docker Compose 方案 | 纯 Docker 命令方案 |
|---|---|---|
| 复杂度 | 中等 | 简单 |
| 依赖 | 需要 docker-compose | 只需要 Docker |
| 配置管理 | 集中化 | 分散化 |
| 学习成本 | 中等 | 低 |
| 维护性 | 好 | 中等 |
| 适用场景 | 复杂项目 | 简单项目 |
