SSL证书基础知识
什么是SSL证书
SSL(Secure Sockets Layer)证书是一种数字证书,用于:
验证网站身份
加密客户端与服务器之间的数据传输
防止中间人攻击
提升网站SEO排名和用户信任度
SSL证书类型
域名验证型(DV):验证域名所有权
组织验证型(OV):验证组织身份
扩展验证型(EV):最高级别验证
通配符证书:保护主域名及所有子域名
多域名证书(SAN):保护多个不同域名
证书格式
PEM:文本格式,Base64编码(.pem, .crt, .cer)
DER:二进制格式(.der, .cer)
PKCS#12:包含私钥和证书(.p12, .pfx)
JKS:Java密钥库格式
免费SSL证书提供商
主要免费SSL证书来源
Let's Encrypt详细教程
Let's Encrypt是目前最受欢迎的免费SSL证书提供商。
使用Certbot(推荐)
1. 安装Certbot
Ubuntu/Debian:
# 更新包管理器
sudo apt update
# 安装snapd
sudo apt install snapd
# 安装certbot
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
# 创建软链接
sudo ln -s /snap/bin/certbot /usr/bin/certbot
CentOS/RHEL/Rocky Linux:
# 安装EPEL仓库
sudo dnf install epel-release
# 安装certbot
sudo dnf install certbot python3-certbot-nginx python3-certbot-apache
使用包管理器(较旧方法):
# Ubuntu/Debian
sudo apt install certbot python3-certbot-nginx
# CentOS/RHEL
sudo yum install certbot python3-certbot-nginx
2. 获取SSL证书
方法一:Nginx自动配置
# 自动获取证书并配置Nginx
sudo certbot --nginx -d example.com -d www.example.com
# 仅获取证书,不自动配置
sudo certbot certonly --nginx -d example.com -d www.example.com
方法二:Apache自动配置
# 自动获取证书并配置Apache
sudo certbot --apache -d example.com -d www.example.com
方法三:独立模式(Standalone)
# 停止Web服务器
sudo systemctl stop nginx
# 获取证书
sudo certbot certonly --standalone -d example.com -d www.example.com
# 重启Web服务器
sudo systemctl start nginx
方法四:Webroot模式
# 使用现有Web服务器的文档根目录
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
方法五:DNS验证(通配符证书)
# 手动DNS验证
sudo certbot certonly --manual --preferred-challenges dns -d example.com -d *.example.com
# 使用DNS插件(以Cloudflare为例)
sudo snap install certbot-dns-cloudflare
sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials ~/.secrets/cloudflare.ini -d example.com -d *.example.com
3. 自动续期配置
Let's Encrypt证书有效期为90天,需要定期续期。
# 测试续期
sudo certbot renew --dry-run
# 手动续期
sudo certbot renew
# 强制续期
sudo certbot renew --force-renewal
# 续期特定证书
sudo certbot renew --cert-name example.com
设置自动续期:
# 查看现有的cron任务
sudo crontab -l
# 编辑cron任务
sudo crontab -e
# 添加以下行(每天检查两次)
0 12,0 * * * /usr/bin/certbot renew --quiet
使用systemd定时器:
# 检查certbot定时器状态
sudo systemctl status snap.certbot.renew.timer
# 启用定时器
sudo systemctl enable snap.certbot.renew.timer
# 查看定时器详情
sudo systemctl list-timers snap.certbot.renew.timer
使用acme.sh
acme.sh是一个纯Shell实现的ACME客户端。
1. 安装acme.sh
# 安装acme.sh
curl https://get.acme.sh | sh
# 或者使用git安装
git clone https://github.com/acmesh-official/acme.sh.git
cd acme.sh
./acme.sh --install
# 重新加载shell
source ~/.bashrc
2. 获取证书
HTTP验证:
# 使用Nginx
acme.sh --issue -d example.com -d www.example.com --nginx
# 使用Apache
acme.sh --issue -d example.com -d www.example.com --apache
# 使用Webroot
acme.sh --issue -d example.com -d www.example.com --webroot /var/www/html
DNS验证:
# 手动DNS验证
acme.sh --issue --dns -d example.com -d www.example.com
# 自动DNS验证(Cloudflare)
export CF_Key="your-cloudflare-api-key"
export CF_Email="your-email@example.com"
acme.sh --issue --dns dns_cf -d example.com -d *.example.com
# 阿里云DNS
export Ali_Key="your-ali-key"
export Ali_Secret="your-ali-secret"
acme.sh --issue --dns dns_ali -d example.com -d *.example.com
3. 安装证书
# 安装到Nginx
acme.sh --install-cert -d example.com \
--key-file /etc/nginx/ssl/example.com.key \
--fullchain-file /etc/nginx/ssl/example.com.pem \
--reloadcmd "systemctl reload nginx"
# 安装到Apache
acme.sh --install-cert -d example.com \
--cert-file /etc/httpd/ssl/example.com.cer \
--key-file /etc/httpd/ssl/example.com.key \
--fullchain-file /etc/httpd/ssl/example.com.pem \
--reloadcmd "systemctl reload httpd"
使用Docker获取Let's Encrypt证书
1. 使用官方Certbot Docker镜像
# 获取证书(独立模式)
docker run -it --rm --name certbot \
-p 80:80 -p 443:443 \
-v "/etc/letsencrypt:/etc/letsencrypt" \
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
certbot/certbot certonly --standalone -d example.com
# 使用Webroot模式
docker run -it --rm --name certbot \
-v "/etc/letsencrypt:/etc/letsencrypt" \
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
-v "/var/www/html:/var/www/html" \
certbot/certbot certonly --webroot -w /var/www/html -d example.com
# 续期证书
docker run -it --rm --name certbot \
-v "/etc/letsencrypt:/etc/letsencrypt" \
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
certbot/certbot renew
2. 使用docker-compose
创建 docker-compose.yml:
version: '3.8'
services:
certbot:
image: certbot/certbot:latest
container_name: certbot
volumes:
- ./certbot/etc:/etc/letsencrypt
- ./certbot/var:/var/lib/letsencrypt
- ./certbot/logs:/var/log/letsencrypt
- ./html:/var/www/html
command: certonly --webroot -w /var/www/html -d example.com --email your-email@example.com --agree-tos --no-eff-email
nginx:
image: nginx:alpine
container_name: nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./html:/var/www/html
- ./certbot/etc:/etc/letsencrypt:ro
restart: unless-stopped
其他免费SSL证书获取方法
ZeroSSL
1. 使用ZeroSSL网页界面
注册账号并登录
点击"New Certificate"
输入域名
选择验证方式(HTTP、DNS、Email)
完成验证
下载证书
2. 使用acme.sh客户端
# 设置ZeroSSL为默认CA
acme.sh --set-default-ca --server zerossl
# 获取证书
acme.sh --register-account -m your-email@example.com --server zerossl
acme.sh --issue -d example.com --nginx --server zerossl
Cloudflare SSL
1. 启用Cloudflare SSL
将域名添加到Cloudflare
更改域名DNS服务器到Cloudflare
在SSL/TLS设置中启用SSL
选择加密模式(推荐"Full (strict)")
2. 获取Origin证书
# 在Cloudflare面板中:
# SSL/TLS -> Origin Server -> Create Certificate
云服务商免费SSL证书
阿里云免费SSL证书
登录阿里云控制台
进入"SSL证书"服务
选择"免费证书"
填写域名信息
选择验证方式
完成验证并下载证书
腾讯云免费SSL证书
登录腾讯云控制台
进入"SSL证书"服务
申请免费证书
提交域名验证
下载证书
使用ACME客户端脚本
创建简单的证书获取脚本
#!/bin/bash
# get-ssl-cert.sh
DOMAIN="example.com"
EMAIL="your-email@example.com"
WEBROOT="/var/www/html"
# 停止Web服务器
systemctl stop nginx
# 获取证书
certbot certonly \
--standalone \
--email $EMAIL \
--agree-tos \
--no-eff-email \
--domains $DOMAIN
# 启动Web服务器
systemctl start nginx
# 检查证书
openssl x509 -in /etc/letsencrypt/live/$DOMAIN/fullchain.pem -text -noout
自签名证书
自签名证书适用于开发环境和内网使用。
使用OpenSSL创建自签名证书
1. 生成私钥
# 生成RSA私钥
openssl genrsa -out private.key 2048
# 生成带密码保护的私钥
openssl genrsa -aes256 -out private.key 2048
# 生成椭圆曲线私钥
openssl ecparam -genkey -name secp384r1 -out private.key
2. 创建证书签名请求(CSR)
# 交互式创建CSR
openssl req -new -key private.key -out certificate.csr
# 非交互式创建CSR
openssl req -new -key private.key -out certificate.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=example.com"
3. 生成自签名证书
# 简单自签名证书
openssl x509 -req -days 365 -in certificate.csr -signkey private.key -out certificate.crt
# 包含SAN(Subject Alternative Names)的证书
openssl req -x509 -newkey rsa:2048 -keyout private.key -out certificate.crt -days 365 -nodes -config <(
cat <<EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
C = CN
ST = Beijing
L = Beijing
O = MyCompany
CN = example.com
[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = api.example.com
IP.1 = 192.168.1.100
EOF
)
4. 一步生成私钥和证书
# 生成自签名证书和私钥
openssl req -x509 -newkey rsa:2048 -keyout private.key -out certificate.crt -days 365 -nodes
# 通配符证书
openssl req -x509 -newkey rsa:2048 -keyout wildcard.key -out wildcard.crt -days 365 -nodes -subj "/CN=*.example.com"
创建本地CA
1. 创建根CA
# 生成CA私钥
openssl genrsa -aes256 -out ca.key 4096
# 生成CA证书
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/C=CN/ST=Beijing/L=Beijing/O=Local CA/CN=Local CA"
2. 使用CA签发证书
# 生成服务器私钥
openssl genrsa -out server.key 2048
# 生成证书签名请求
openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=example.com"
# 使用CA签发证书
openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
# 验证证书
openssl verify -CAfile ca.crt server.crt
SSL证书配置
Nginx配置
基础SSL配置
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# SSL证书配置
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# 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 10m;
ssl_session_tickets off;
# 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;
# 安全头部
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
# DH参数
ssl_dhparam /etc/nginx/dhparam.pem;
location / {
root /var/www/html;
index index.html index.htm;
}
}
生成DH参数
# 生成强DH参数(可能需要较长时间)
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
Apache配置
基础SSL配置
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
Redirect permanent / https://example.com/
</VirtualHost>
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/html
# SSL配置
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem
# SSL协议和加密套件
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
SSLHonorCipherOrder off
SSLSessionTickets off
# HSTS
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
# OCSP Stapling
SSLUseStapling on
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
</VirtualHost>
Docker容器SSL配置
Nginx + SSL Docker配置
FROM nginx:alpine
# 复制SSL证书
COPY certs/ /etc/nginx/certs/
# 复制Nginx配置
COPY nginx.conf /etc/nginx/nginx.conf
# 暴露端口
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]
docker-compose with SSL
version: '3.8'
services:
nginx:
image: nginx:alpine
container_name: nginx-ssl
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./html:/var/www/html
- /etc/letsencrypt:/etc/letsencrypt:ro
restart: unless-stopped
certbot:
image: certbot/certbot
container_name: certbot
volumes:
- /etc/letsencrypt:/etc/letsencrypt
- ./html:/var/www/html
command: ["--version"]
profiles: ["certbot"]
证书管理与维护
证书信息查看
# 查看证书详细信息
openssl x509 -in certificate.crt -text -noout
# 查看证书有效期
openssl x509 -in certificate.crt -noout -dates
# 查看证书主题
openssl x509 -in certificate.crt -noout -subject
# 查看证书指纹
openssl x509 -in certificate.crt -noout -fingerprint -sha256
# 检查远程服务器证书
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | openssl x509 -text -noout
# 检查证书和私钥是否匹配
openssl x509 -noout -modulus -in certificate.crt | openssl md5
openssl rsa -noout -modulus -in private.key | openssl md5
证书格式转换
# PEM转DER
openssl x509 -in certificate.crt -outform DER -out certificate.der
# DER转PEM
openssl x509 -in certificate.der -inform DER -outform PEM -out certificate.crt
# 创建PKCS#12文件
openssl pkcs12 -export -in certificate.crt -inkey private.key -out certificate.p12
# 从PKCS#12提取证书和私钥
openssl pkcs12 -in certificate.p12 -clcerts -nokeys -out certificate.crt
openssl pkcs12 -in certificate.p12 -nocerts -nodes -out private.key
批量证书管理脚本
#!/bin/bash
# ssl-manager.sh - SSL证书管理脚本
CERT_DIR="/etc/letsencrypt/live"
LOG_FILE="/var/log/ssl-manager.log"
EMAIL="admin@example.com"
# 日志函数
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
}
# 检查证书过期时间
check_expiry() {
local domain=$1
local cert_file="$CERT_DIR/$domain/cert.pem"
if [[ -f $cert_file ]]; then
local expiry_date=$(openssl x509 -in $cert_file -noout -enddate | cut -d= -f2)
local expiry_epoch=$(date -d "$expiry_date" +%s)
local current_epoch=$(date +%s)
local days_left=$(( (expiry_epoch - current_epoch) / 86400 ))
log "$domain 证书剩余 $days_left 天"
if [[ $days_left -lt 30 ]]; then
log "警告: $domain 证书将在 $days_left 天内过期"
echo "$domain 证书将在 $days_left 天内过期" | mail -s "SSL证书过期警告" $EMAIL
fi
else
log "错误: 未找到 $domain 的证书文件"
fi
}
# 自动续期
renew_certificates() {
log "开始自动续期证书"
certbot renew --quiet --no-self-upgrade
if [[ $? -eq 0 ]]; then
log "证书续期成功"
systemctl reload nginx
else
log "证书续期失败"
echo "证书续期失败,请检查日志" | mail -s "SSL证书续期失败" $EMAIL
fi
}
# 获取新证书
get_certificate() {
local domain=$1
local webroot=$2
log "为 $domain 获取新证书"
certbot certonly --webroot -w $webroot -d $domain --email $EMAIL --agree-tos --no-eff-email
if [[ $? -eq 0 ]]; then
log "$domain 证书获取成功"
systemctl reload nginx
else
log "$domain 证书获取失败"
fi
}
# 主函数
main() {
case $1 in
check)
if [[ -n $2 ]]; then
check_expiry $2
else
for domain_dir in $CERT_DIR/*/; do
domain=$(basename $domain_dir)
check_expiry $domain
done
fi
;;
renew)
renew_certificates
;;
get)
if [[ -n $2 && -n $3 ]]; then
get_certificate $2 $3
else
echo "用法: $0 get <domain> <webroot>"
exit 1
fi
;;
*)
echo "用法: $0 {check|renew|get} [domain] [webroot]"
echo " check [domain] - 检查证书过期时间"
echo " renew - 续期所有证书"
echo " get <domain> <webroot> - 获取新证书"
exit 1
;;
esac
}
main "$@"
故障排除
常见问题及解决方案
1. Let's Encrypt速率限制
# 错误信息:too many certificates already issued
# 解决方案:
# 1. 使用测试环境
certbot --staging -d example.com
# 2. 等待一周后重试
# 3. 使用不同的域名变体
2. DNS验证失败
# 检查DNS记录
dig _acme-challenge.example.com TXT
# 等待DNS传播
host _acme-challenge.example.com
# 手动添加DNS记录后重试
certbot --manual --preferred-challenges dns -d example.com
3. 证书链不完整
# 检查证书链
openssl s_client -connect example.com:443 -servername example.com
# 使用完整证书链
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
4. 权限问题
# 设置正确的证书权限
sudo chown -R root:ssl-cert /etc/letsencrypt/
sudo chmod -R 640 /etc/letsencrypt/archive/
sudo chmod -R 644 /etc/letsencrypt/live/
证书验证工具
SSL Labs测试
# 使用SSL Labs API测试
curl "https://api.ssllabs.com/api/v3/analyze?host=example.com"
本地证书测试
#!/bin/bash
# test-ssl.sh - SSL配置测试脚本
DOMAIN=$1
if [[ -z $DOMAIN ]]; then
echo "用法: $0 <domain>"
exit 1
fi
echo "测试 $DOMAIN 的SSL配置..."
# 检查证书有效性
echo "=== 证书信息 ==="
echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -text | grep -A3 "Validity"
# 检查证书链
echo "=== 证书链验证 ==="
echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -issuer
# 检查支持的TLS版本
echo "=== TLS版本支持 ==="
for version in ssl3 tls1 tls1_1 tls1_2 tls1_3; do
echo -n "Testing $version: "
echo | timeout 3 openssl s_client -connect $DOMAIN:443 -servername $DOMAIN -$version 2>/dev/null
if [[ $? -eq 0 ]]; then
echo "支持"
else
echo "不支持"
fi
done
# 检查证书过期时间
echo "=== 证书过期时间 ==="
echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -dates
# 检查HSTS头部
echo "=== HSTS检查 ==="
curl -I https://$DOMAIN 2>/dev/null | grep -i strict-transport-security
echo "SSL配置测试完成"
最佳实践
安全配置建议
1. 使用强加密算法
# 推荐的Nginx 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;
ssl_prefer_server_ciphers off;
2. 启用安全头部
# 安全头部配置
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
3. 证书透明度监控
#!/bin/bash
# ct-monitor.sh - 证书透明度监控脚本
DOMAIN="example.com"
CT_LOG_URL="https://crt.sh/?q=${DOMAIN}&output=json"
# 获取最新证书信息
latest_cert=$(curl -s "$CT_LOG_URL" | jq -r '.[0] | .id')
if [[ $latest_cert != "null" ]]; then
echo "发现新证书: $latest_cert"
# 发送通知
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"域名 $DOMAIN 发现新证书: $latest_cert\"}" \
YOUR_WEBHOOK_URL
fi
自动化部署流程
完整的SSL自动化脚本
#!/bin/bash
# ssl-automation.sh - SSL证书自动化部署脚本
set -euo pipefail
# 配置变量
DOMAIN="${1:-example.com}"
EMAIL="${2:-admin@example.com}"
WEBROOT="${3:-/var/www/html}"
NGINX_CONFIG="/etc/nginx/sites-available/$DOMAIN"
BACKUP_DIR="/backup/ssl"
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a /var/log/ssl-automation.log
}
# 创建备份
backup_config() {
log "备份当前配置..."
mkdir -p $BACKUP_DIR
cp $NGINX_CONFIG $BACKUP_DIR/nginx_${DOMAIN}_$(date +%Y%m%d_%H%M%S).conf
}
# 检查域名解析
check_dns() {
log "检查域名解析..."
if ! dig +short $DOMAIN | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ > /dev/null; then
log "错误: 域名 $DOMAIN 解析失败"
exit 1
fi
log "域名解析正常"
}
# 获取SSL证书
get_certificate() {
log "获取SSL证书..."
# 检查是否已有证书
if [[ -f "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" ]]; then
log "证书已存在,检查是否需要续期..."
certbot renew --cert-name $DOMAIN --deploy-hook "systemctl reload nginx"
else
log "获取新证书..."
certbot certonly \
--webroot \
-w $WEBROOT \
-d $DOMAIN \
-d www.$DOMAIN \
--email $EMAIL \
--agree-tos \
--no-eff-email \
--deploy-hook "systemctl reload nginx"
fi
}
# 配置Nginx
configure_nginx() {
log "配置Nginx SSL..."
cat > $NGINX_CONFIG << EOF
# HTTP重定向到HTTPS
server {
listen 80;
server_name $DOMAIN www.$DOMAIN;
return 301 https://\$server_name\$request_uri;
}
# HTTPS配置
server {
listen 443 ssl http2;
server_name $DOMAIN www.$DOMAIN;
# 网站根目录
root $WEBROOT;
index index.html index.htm index.php;
# SSL证书配置
ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/$DOMAIN/chain.pem;
# 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;
ssl_prefer_server_ciphers off;
# SSL会话
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# 安全头部
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
# Let's Encrypt验证
location /.well-known/acme-challenge/ {
root $WEBROOT;
}
# 主要内容
location / {
try_files \$uri \$uri/ =404;
}
# PHP支持(如果需要)
location ~ \.php\$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
}
# 静态文件缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
EOF
# 测试配置
nginx -t
if [[ $? -eq 0 ]]; then
log "Nginx配置测试通过"
# 启用站点
ln -sf $NGINX_CONFIG /etc/nginx/sites-enabled/
systemctl reload nginx
log "Nginx配置已重载"
else
log "错误: Nginx配置测试失败"
exit 1
fi
}
# 验证SSL配置
verify_ssl() {
log "验证SSL配置..."
# 等待服务启动
sleep 5
# 检查HTTPS连接
if curl -s --max-time 10 https://$DOMAIN > /dev/null; then
log "HTTPS连接正常"
else
log "错误: HTTPS连接失败"
exit 1
fi
# 检查证书有效性
expiry_date=$(echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
log "证书有效期至: $expiry_date"
}
# 设置监控
setup_monitoring() {
log "设置证书监控..."
# 创建监控脚本
cat > /usr/local/bin/ssl-monitor-$DOMAIN.sh << EOF
#!/bin/bash
DOMAIN="$DOMAIN"
CERT_FILE="/etc/letsencrypt/live/\$DOMAIN/cert.pem"
EMAIL="$EMAIL"
if [[ -f \$CERT_FILE ]]; then
EXPIRY=\$(openssl x509 -in \$CERT_FILE -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=\$(date -d "\$EXPIRY" +%s)
CURRENT_EPOCH=\$(date +%s)
DAYS_LEFT=\$(( (EXPIRY_EPOCH - CURRENT_EPOCH) / 86400 ))
if [[ \$DAYS_LEFT -lt 30 ]]; then
echo "警告: \$DOMAIN SSL证书将在 \$DAYS_LEFT 天内过期" | mail -s "SSL证书过期警告" \$EMAIL
fi
fi
EOF
chmod +x /usr/local/bin/ssl-monitor-$DOMAIN.sh
# 添加到crontab
(crontab -l 2>/dev/null; echo "0 6 * * * /usr/local/bin/ssl-monitor-$DOMAIN.sh") | crontab -
log "证书监控已设置"
}
# 主函数
main() {
log "开始SSL自动化部署: $DOMAIN"
# 检查依赖
command -v certbot >/dev/null 2>&1 || { log "错误: certbot未安装"; exit 1; }
command -v nginx >/dev/null 2>&1 || { log "错误: nginx未安装"; exit 1; }
# 执行部署步骤
backup_config
check_dns
get_certificate
configure_nginx
verify_ssl
setup_monitoring
log "SSL自动化部署完成!"
log "访问 https://$DOMAIN 验证配置"
}
# 脚本入口
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
多域名批量部署
#!/bin/bash
# batch-ssl-deploy.sh - 批量SSL部署脚本
DOMAINS_FILE="domains.txt"
EMAIL="admin@example.com"
WEBROOT="/var/www/html"
LOG_FILE="/var/log/batch-ssl-deploy.log"
# 域名配置文件格式:
# domain.com,/var/www/domain1
# example.org,/var/www/domain2
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE
}
deploy_domain() {
local domain=$1
local webroot=$2
log "开始部署 $domain"
# 获取证书
certbot certonly \
--webroot \
-w $webroot \
-d $domain \
-d www.$domain \
--email $EMAIL \
--agree-tos \
--no-eff-email \
--non-interactive
if [[ $? -eq 0 ]]; then
log "$domain 证书获取成功"
# 生成Nginx配置
generate_nginx_config $domain $webroot
# 测试并重载配置
if nginx -t; then
systemctl reload nginx
log "$domain Nginx配置已更新"
else
log "错误: $domain Nginx配置测试失败"
fi
else
log "错误: $domain 证书获取失败"
fi
}
generate_nginx_config() {
local domain=$1
local webroot=$2
local config_file="/etc/nginx/sites-available/$domain"
cat > $config_file << EOF
server {
listen 80;
server_name $domain www.$domain;
return 301 https://\$server_name\$request_uri;
}
server {
listen 443 ssl http2;
server_name $domain www.$domain;
root $webroot;
index index.html index.htm;
ssl_certificate /etc/letsencrypt/live/$domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/$domain/privkey.pem;
include /etc/nginx/snippets/ssl-params.conf;
location / {
try_files \$uri \$uri/ =404;
}
}
EOF
ln -sf $config_file /etc/nginx/sites-enabled/
}
# 创建SSL参数片段
create_ssl_snippet() {
cat > /etc/nginx/snippets/ssl-params.conf << EOF
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 off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
EOF
}
main() {
log "开始批量SSL部署"
if [[ ! -f $DOMAINS_FILE ]]; then
log "错误: 域名配置文件 $DOMAINS_FILE 不存在"
exit 1
fi
create_ssl_snippet
while IFS=',' read -r domain webroot; do
# 跳过空行和注释
[[ $domain =~ ^#.*$ ]] && continue
[[ -z $domain ]] && continue
# 使用默认webroot(如果未指定)
[[ -z $webroot ]] && webroot=$WEBROOT
deploy_domain "$domain" "$webroot"
# 等待一段时间避免速率限制
sleep 10
done < $DOMAINS_FILE
log "批量SSL部署完成"
}
main "$@"
生产环境部署检查清单
SSL部署检查清单
## SSL证书部署检查清单
### 部署前检查
- [ ] 域名DNS解析正确
- [ ] Web服务器配置正确
- [ ] 防火墙端口80/443开放
- [ ] 备份现有配置
### 证书获取
- [ ] 选择合适的证书提供商
- [ ] 验证域名所有权
- [ ] 下载完整证书链
- [ ] 验证私钥和证书匹配
### 服务器配置
- [ ] 配置SSL/TLS协议版本
- [ ] 设置安全的加密套件
- [ ] 启用HSTS
- [ ] 配置OCSP Stapling
- [ ] 添加安全头部
### 部署后验证
- [ ] HTTPS连接正常
- [ ] HTTP自动重定向到HTTPS
- [ ] 证书链完整
- [ ] SSL Labs评级A+
- [ ] 移动端兼容性测试
### 监控和维护
- [ ] 设置证书过期监控
- [ ] 配置自动续期
- [ ] 定期安全扫描
- [ ] 备份证书和私钥
总结
本教程涵盖了SSL证书的完整生命周期管理,从获取免费证书到生产环境部署。主要要点包括:
免费SSL证书推荐方案
个人网站: Let's Encrypt + Certbot
企业应用: Let's Encrypt + acme.sh + 自动化脚本
快速部署: 云服务商免费证书
开发测试: 自签名证书
关键最佳实践
自动化: 使用脚本自动获取和续期证书
监控: 设置证书过期提醒和健康检查
安全: 使用强加密配置和安全头部
备份: 定期备份证书和配置文件
常用工具对比
Certbot: 官方推荐,功能完整
acme.sh: 轻量级,支持更多DNS提供商
云服务: 简单易用,与云平台集成好
选择合适的方案并按照本教程的步骤进行部署,可以确保网站的安全性和可靠性。记住定期检查和更新SSL配置,保持与最新安全标准同步。