1. 前言与准备工作
在当今互联网环境中,为网站启用HTTPS加密连接已成为基本安全要求。本文将详细介绍如何使用Let's Encrypt的Certbot工具为Nginx服务器免费申请和配置SSL/TLS证书。
1.1 系统要求
- Linux服务器(本文以Ubuntu 20.04 LTS为例)
- 已安装Nginx Web服务器
- 拥有一个已解析到服务器IP的域名
- 服务器开放80和443端口
1.2 准备工作检查
首先验证系统环境和必要服务状态:
# 检查系统版本
lsb_release -a
# 检查Nginx状态
sudo systemctl status nginx
# 检查端口监听状态
sudo netstat -tulpn | grep -E ':(80|443)'
# 更新系统包
sudo apt update && sudo apt upgrade -y
2. Certbot安装与配置
2.1 安装Certbot和Nginx插件
# 添加Certbot官方仓库
sudo apt install software-properties-common -y
sudo add-apt-repository ppa:certbot/certbot -y
# 更新包列表并安装Certbot
sudo apt update
sudo apt install certbot python3-certbot-nginx -y
# 验证安装
certbot --version
2.2 配置防火墙规则
确保防火墙允许HTTP和HTTPS流量:
# 如果使用ufw
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'
# 如果使用iptables
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
3. Nginx基础配置
3.1 创建Nginx配置文件
创建网站配置文件:/etc/nginx/sites-available/yourdomain.conf
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# 基础安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
# 根目录配置
root /var/www/html;
index index.html index.htm;
# 日志配置
access_log /var/log/nginx/yourdomain.access.log;
error_log /var/log/nginx/yourdomain.error.log;
location / {
try_files $uri $uri/ =404;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
启用网站配置:
# 创建符号链接
sudo ln -s /etc/nginx/sites-available/yourdomain.conf /etc/nginx/sites-enabled/
# 测试Nginx配置
sudo nginx -t
# 重新加载Nginx
sudo systemctl reload nginx
4. SSL证书申请流程
以下是证书申请和配置的完整流程图:
graph TD
A[开始证书申请] --> B[安装Certbot工具]
B --> C[配置Nginx虚拟主机]
C --> D[申请SSL证书]
D --> E{申请是否成功?}
E -->|是| F[配置SSL到Nginx]
E -->|否| G[排查错误]
G --> D
F --> H[测试HTTPS配置]
H --> I{测试是否通过?}
I -->|是| J[设置自动续期]
I -->|否| K[调试配置]
K --> H
J --> L[完成HTTPS部署]
style A fill:#2d5c8a,color:#ffffff
style B fill:#2d5c8a,color:#ffffff
style C fill:#2d5c8a,color:#ffffff
style D fill:#2d5c8a,color:#ffffff
style F fill:#2d5c8a,color:#ffffff
style H fill:#2d5c8a,color:#ffffff
style J fill:#2d5c8a,color:#ffffff
style L fill:#2d5c8a,color:#ffffff
style E fill:#b34045,color:#ffffff
style I fill:#b34045,color:#ffffff
style G fill:#b34045,color:#ffffff
style K fill:#b34045,color:#ffffff
4.1 申请SSL证书
使用Certbot申请证书:
# 使用Nginx插件自动配置(推荐新手)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# 或者使用独立模式(如果Nginx插件有问题)
sudo certbot certonly --standalone -d yourdomain.com -d www.yourdomain.com
申请过程中会提示:
- 输入邮箱地址(用于安全通知和证书恢复)
- 同意服务条款
- 选择是否接收邮件通知
- 选择是否将HTTP流量重定向到HTTPS(推荐选择2)
4.2 手动模式申请证书
如果自动模式遇到问题,可以使用手动模式:
# 手动模式申请
sudo certbot certonly --manual --preferred-challenges dns \
-d yourdomain.com -d www.yourdomain.com \
--manual-public-ip-logging-ok
手动模式需要根据提示在DNS中添加TXT记录验证域名所有权。
5. Nginx SSL配置
5.1 完整的SSL配置示例
创建或更新Nginx SSL配置文件:/etc/nginx/sites-available/yourdomain-ssl.conf
# HTTP到HTTPS重定向
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$server_name$request_uri;
}
# HTTPS服务器配置
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# SSL证书路径
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.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会话设置
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 5s;
# 安全头
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;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# 根目录配置
root /var/www/html;
index index.html index.htm;
# 日志配置
access_log /var/log/nginx/yourdomain.ssl.access.log;
error_log /var/log/nginx/yourdomain.ssl.error.log;
location / {
try_files $uri $uri/ =404;
}
# 安全配置 - 隐藏敏感文件
location ~ /\.(?!well-known) {
deny all;
access_log off;
log_not_found off;
}
location ~ /\.ht {
deny all;
access_log off;
log_not_found off;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|pdf|txt)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding";
}
# 启用Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
application/atom+xml
application/geo+json
application/javascript
application/x-javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/xhtml+xml
application/xml
font/eot
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
}
5.2 应用配置并测试
# 测试Nginx配置
sudo nginx -t
# 重新加载Nginx配置
sudo systemctl reload nginx
# 检查SSL证书状态
sudo certbot certificates
6. SSL配置测试与验证
6.1 在线SSL测试
使用以下命令测试SSL配置:
# 使用openssl测试
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com < /dev/null
# 检查证书链
openssl s_client -connect yourdomain.com:443 -showcerts < /dev/null
# 使用在线工具(需要curl)
curl -I https://yourdomain.com
6.2 创建测试脚本
创建SSL测试脚本:/usr/local/bin/ssl-test.sh
#!/bin/bash
DOMAIN="yourdomain.com"
echo "=== SSL证书测试报告 ==="
echo "测试时间: $(date)"
echo "测试域名: $DOMAIN"
echo "========================="
echo ""
# 检查证书过期时间
echo "1. 证书有效期检查:"
openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -dates
echo ""
echo "2. 证书链完整性检查:"
openssl s_client -connect $DOMAIN:443 -servername $DOMAIN -verify_return_error 2>/dev/null
if [ $? -eq 0 ]; then
echo "✓ 证书链完整"
else
echo "✗ 证书链不完整"
fi
echo ""
echo "3. 支持的协议检查:"
for protocol in ssl2 ssl3 tls1 tls1_1 tls1_2 tls1_3; do
result=$(openssl s_client -connect $DOMAIN:443 -$protocol < /dev/null 2>&1 | grep "CONNECTED")
if [ -n "$result" ]; then
echo "✓ 支持 $protocol"
else
echo "✗ 不支持 $protocol"
fi
done
echo ""
echo "4. HTTP到HTTPS重定向检查:"
http_code=$(curl -s -o /dev/null -w "%{http_code}" http://$DOMAIN)
if [ "$http_code" = "301" ] || [ "$http_code" = "302" ]; then
echo "✓ HTTP重定向正常"
else
echo "✗ HTTP重定向异常,返回码: $http_code"
fi
echo ""
echo "5. HSTS头检查:"
hsts_header=$(curl -s -I https://$DOMAIN | grep -i "strict-transport-security")
if [ -n "$hsts_header" ]; then
echo "✓ HSTS头已设置: $hsts_header"
else
echo "✗ HSTS头未设置"
fi
echo ""
echo "=== 测试完成 ==="
给脚本执行权限:
sudo chmod +x /usr/local/bin/ssl-test.sh
./usr/local/bin/ssl-test.sh
7. 证书自动续期配置
7.1 设置自动续期任务
# 测试证书续期(干跑模式)
sudo certbot renew --dry-run
# 设置自动续期的cron任务
sudo crontab -e
在cron文件中添加以下内容:
# Certbot SSL证书自动续期
0 12 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"
# 每周一检查证书状态并发送通知
0 9 * * 1 /usr/bin/certbot certificates | mail -s "SSL证书状态报告" admin@yourdomain.com
7.2 创建续期监控脚本
创建证书监控脚本:/usr/local/bin/cert-monitor.sh
#!/bin/bash
# 配置参数
DOMAIN="yourdomain.com"
ADMIN_EMAIL="admin@yourdomain.com"
LOG_FILE="/var/log/ssl-renewal.log"
WARNING_DAYS=30
# 获取证书过期时间
expiry_date=$(openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
expiry_timestamp=$(date -d "$expiry_date" +%s)
current_timestamp=$(date +%s)
days_until_expiry=$(( (expiry_timestamp - current_timestamp) / 86400 ))
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> $LOG_FILE
}
# 检查证书状态
check_certificate() {
log "开始检查证书: $DOMAIN"
log "证书过期时间: $expiry_date"
log "距离过期还有: $days_until_expiry 天"
if [ $days_until_expiry -le $WARNING_DAYS ]; then
log "警告: 证书将在 $days_until_expiry 天后过期"
# 发送邮件通知
mail -s "证书过期警告: $DOMAIN" $ADMIN_EMAIL << EOF
域名: $DOMAIN
过期时间: $expiry_date
剩余天数: $days_until_expiry
请及时处理证书续期。
EOF
# 尝试自动续期
/usr/bin/certbot renew --force-renewal --quiet --post-hook "systemctl reload nginx"
if [ $? -eq 0 ]; then
log "证书续期成功"
mail -s "证书续期成功: $DOMAIN" $ADMIN_EMAIL << EOF
域名: $DOMAIN
续期时间: $(date)
状态: 成功
证书已自动续期。
EOF
else
log "证书续期失败"
mail -s "证书续期失败: $DOMAIN" $ADMIN_EMAIL << EOF
域名: $DOMAIN
续期时间: $(date)
状态: 失败
请手动检查证书续期。
EOF
fi
else
log "证书状态正常"
fi
}
# 执行检查
check_certificate
设置脚本权限和执行计划:
sudo chmod +x /usr/local/bin/cert-monitor.sh
# 添加到cron,每天检查一次
echo "0 8 * * * /usr/local/bin/cert-monitor.sh" | sudo crontab -
8. 高级配置与优化
8.1 创建Nginx SSL配置片段
创建可重用的SSL配置片段:/etc/nginx/snippets/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;
# SSL会话设置
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# DH参数(如果需要生成)
# ssl_dhparam /etc/nginx/dhparam.pem;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 5s;
# 安全头
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;
8.2 生成强DH参数
# 生成DH参数(需要较长时间)
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
# 然后在ssl-params.conf中取消注释并指向该文件
8.3 多域名配置
对于多个域名的配置,创建通用模板:/etc/nginx/sites-available/ssl-template.conf
# HTTP重定向模板
server {
listen 80;
server_name DOMAIN_PLACEHOLDER;
return 301 https://$server_name$request_uri;
}
# HTTPS服务器模板
server {
listen 443 ssl http2;
server_name DOMAIN_PLACEHOLDER;
# SSL证书
ssl_certificate /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/DOMAIN_PLACEHOLDER/privkey.pem;
# 包含SSL参数
include snippets/ssl-params.conf;
# 网站特定配置
root /var/www/DOMAIN_PLACEHOLDER/html;
index index.html index.htm;
# 日志
access_log /var/log/nginx/DOMAIN_PLACEHOLDER.access.log;
error_log /var/log/nginx/DOMAIN_PLACEHOLDER.error.log;
location / {
try_files $uri $uri/ =404;
}
}
9. 故障排除与常见问题
9.1 常见问题解决
创建故障排除脚本:/usr/local/bin/ssl-troubleshoot.sh
#!/bin/bash
echo "=== SSL故障排除工具 ==="
echo ""
# 检查服务状态
echo "1. 服务状态检查:"
sudo systemctl status nginx --no-pager -l
echo ""
# 检查证书状态
echo "2. 证书状态检查:"
sudo certbot certificates
echo ""
# 检查Nginx配置
echo "3. Nginx配置检查:"
sudo nginx -t
echo ""
# 检查端口监听
echo "4. 端口监听检查:"
sudo netstat -tulpn | grep -E ':(80|443)'
echo ""
# 检查防火墙状态
echo "5. 防火墙状态检查:"
sudo ufw status verbose
echo ""
# 检查DNS解析
echo "6. DNS解析检查:"
nslookup yourdomain.com
echo ""
echo "=== 检查完成 ==="
9.2 证书更新问题
如果证书更新失败,尝试以下命令:
# 强制更新证书
sudo certbot renew --force-renewal
# 调试模式查看详细错误
sudo certbot renew --debug
# 手动更新特定证书
sudo certbot certonly --force-renewal -d yourdomain.com -d www.yourdomain.com
10. 安全加固与最佳实践
10.1 定期安全扫描
# 安装SSL扫描工具
sudo apt install sslscan -y
# 扫描SSL配置
sslscan yourdomain.com
# 使用nmap进行安全扫描
sudo apt install nmap -y
nmap --script ssl-enum-ciphers -p 443 yourdomain.com
10.2 监控和告警
创建监控脚本:/usr/local/bin/ssl-monitor.sh
#!/bin/bash
DOMAINS=("yourdomain.com" "www.yourdomain.com")
for domain in "${DOMAINS[@]}"; do
expiry_date=$(echo | openssl s_client -servername $domain -connect $domain:443 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
expiry_timestamp=$(date -d "$expiry_date" +%s)
current_timestamp=$(date +%s)
days_until_expiry=$(( (expiry_timestamp - current_timestamp) / 86400 ))
if [ $days_until_expiry -lt 15 ]; then
# 发送紧急告警
echo "紧急: $domain 证书将在 $days_until_expiry 天后过期" | mail -s "SSL证书紧急告警" admin@yourdomain.com
elif [ $days_until_expiry -lt 30 ]; then
# 发送警告
echo "警告: $domain 证书将在 $days_until_expiry 天后过期" | mail -s "SSL证书警告" admin@yourdomain.com
fi
done
11. 总结
通过本文详细的步骤,您已经成功:
- 安装并配置了Certbot工具
- 申请了免费的Let's Encrypt SSL证书
- 配置Nginx支持HTTPS
- 设置了证书自动续期
- 实现了SSL安全加固
- 建立了监控和告警机制
现在您的网站已经具备了企业级的HTTPS安全保障。记得定期检查证书状态和维护系统更新,确保网站持续安全运行。
11.1 维护建议
- 每月检查证书状态
- 定期更新系统和软件包
- 监控SSL/TLS安全公告
- 定期审查和更新SSL配置
- 备份证书和配置文