盘大叔叔logo

一个SSL证书过期,一年多没有维护

2026年02月02日     / 0评 / 0

应该是24年,博客的SSL过期了,正好赶在了创业最忙的时候,懒得更新,一晃一年多过去了,今天用Deepseek帮助写了一个自动化更新SSL证书的脚本,真的好用,牛。

使用方法:
1、创建脚本文件,如pankuu-ssl-setup.sh,复制脚本内容;
2、设置执行权限:chmod +x pankuu-ssl-setup.sh;
3、运行脚本:sudo ./pankuu-ssl-setup.sh

脚本

#!/bin/bash
# pankuu-ssl-auto-setup.sh
# LNMP环境SSL证书自动配置脚本

set -e  # 遇到错误立即退出

# 配置参数
DOMAIN="pankuu.com"
WWW_ROOT="/home/wwwroot/pankuu.com"
EMAIL="admin@${DOMAIN}"
LNMP_PATH="/usr/local/nginx"  # LNMP默认安装路径

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# 日志函数
log() {
    echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] $1${NC}"
}

error() {
    echo -e "${RED}[ERROR] $1${NC}"
}

warning() {
    echo -e "${YELLOW}[WARNING] $1${NC}"
}

# 检查是否以root运行
check_root() {
    if [ "$EUID" -ne 0 ]; then
        error "请使用sudo或以root用户运行此脚本"
        exit 1
    fi
}

# 检查LNMP环境
check_lnmp() {
    log "检查LNMP环境..."
    
    # 检查Nginx
    if [ ! -f "${LNMP_PATH}/sbin/nginx" ]; then
        error "未找到Nginx,请确认LNMP已正确安装"
        exit 1
    fi
    
    # 检查网站目录
    if [ ! -d "${WWW_ROOT}" ]; then
        warning "网站目录不存在: ${WWW_ROOT}"
        read -p "是否创建此目录? (y/n): " -n 1 -r
        echo
        if [[ $REPLY =~ ^[Yy]$ ]]; then
            mkdir -p "${WWW_ROOT}"
            chown -R www:www "${WWW_ROOT}"
        else
            exit 1
        fi
    fi
    
    # 检查Nginx配置
    if [ ! -f "${LNMP_PATH}/conf/vhost/${DOMAIN}.conf" ]; then
        error "未找到域名配置文件: ${LNMP_PATH}/conf/vhost/${DOMAIN}.conf"
        create_nginx_conf
    fi
}

# 创建Nginx配置文件(如果需要)
create_nginx_conf() {
    log "创建Nginx配置文件..."
    
    cat > "${LNMP_PATH}/conf/vhost/${DOMAIN}.conf" << EOF
server {
    listen 80;
    server_name ${DOMAIN} www.${DOMAIN};
    
    access_log /home/wwwlogs/${DOMAIN}.log;
    error_log /home/wwwlogs/${DOMAIN}.error.log;
    
    root ${WWW_ROOT};
    index index.html index.htm index.php;
    
    location / {
        try_files \$uri \$uri/ =404;
    }
    
    location ~ \.php$ {
        fastcgi_pass unix:/tmp/php-cgi.sock;
        fastcgi_index index.php;
        include fastcgi.conf;
    }
    
    location ~ /\.ht {
        deny all;
    }
}
EOF
    
    log "Nginx配置文件已创建,请重启Nginx后继续"
    echo "重启命令: ${LNMP_PATH}/sbin/nginx -s reload"
    exit 1
}

# 安装Certbot
install_certbot() {
    log "安装Certbot..."
    
    apt update
    apt install -y certbot python3-certbot-nginx
    
    # 验证安装
    if ! command -v certbot &> /dev/null; then
        error "Certbot安装失败"
        exit 1
    fi
    
    log "Certbot安装成功"
}

# 获取SSL证书
get_ssl_certificate() {
    log "开始获取SSL证书..."
    
    # 先停止Nginx,确保80端口可用(LNMP的certbot插件可能需要)
    ${LNMP_PATH}/sbin/nginx -s stop 2>/dev/null || true
    
    # 获取证书(使用standalone模式)
    certbot certonly --standalone \
        -d ${DOMAIN} \
        -d www.${DOMAIN} \
        --email ${EMAIL} \
        --agree-tos \
        --non-interactive \
        --force-renewal
    
    # 启动Nginx
    ${LNMP_PATH}/sbin/nginx
    
    # 检查证书是否获取成功
    if [ -f "/etc/letsencrypt/live/${DOMAIN}/fullchain.pem" ]; then
        log "✅ SSL证书获取成功"
        log "证书位置: /etc/letsencrypt/live/${DOMAIN}/"
    else
        error "SSL证书获取失败"
        exit 1
    fi
}

# 配置Nginx SSL
configure_nginx_ssl() {
    log "配置Nginx SSL..."
    
    # 备份原配置文件
    CONF_FILE="${LNMP_PATH}/conf/vhost/${DOMAIN}.conf"
    BACKUP_FILE="${CONF_FILE}.backup.$(date +%Y%m%d%H%M%S)"
    cp "${CONF_FILE}" "${BACKUP_FILE}"
    log "配置文件已备份: ${BACKUP_FILE}"
    
    # 创建SSL配置文件
    cat > "${LNMP_PATH}/conf/ssl.conf" << 'EOF'
# SSL配置
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

# 现代加密套件配置
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;

# HSTS (可选,启用后谨慎)
# add_header Strict-Transport-Security "max-age=63072000" always;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 5s;
EOF
    
    # 更新域名配置文件
    cat > "${CONF_FILE}" << EOF
# HTTP 重定向到 HTTPS
server {
    listen 80;
    server_name ${DOMAIN} www.${DOMAIN};
    
    # 用于certbot续期验证
    location ^~ /.well-known/acme-challenge/ {
        alias ${WWW_ROOT}/.well-known/acme-challenge/;
        try_files \$uri =404;
    }
    
    location / {
        return 301 https://\$host\$request_uri;
    }
    
    access_log /home/wwwlogs/${DOMAIN}.http.log;
}

# HTTPS 服务器
server {
    listen 443 ssl http2;
    server_name ${DOMAIN} www.${DOMAIN};
    
    ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
    include ${LNMP_PATH}/conf/ssl.conf;
    
    root ${WWW_ROOT};
    index index.html index.htm index.php;
    
    access_log /home/wwwlogs/${DOMAIN}.https.log;
    error_log /home/wwwlogs/${DOMAIN}.https.error.log;
    
    # 安全头部
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
    
    # 网站内容
    location / {
        try_files \$uri \$uri/ =404;
    }
    
    # PHP处理
    location ~ \.php$ {
        fastcgi_pass unix:/tmp/php-cgi.sock;
        fastcgi_index index.php;
        include fastcgi.conf;
        fastcgi_param HTTPS on;
    }
    
    # 禁止访问隐藏文件
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
    
    # 静态文件缓存
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 365d;
        add_header Cache-Control "public, immutable";
    }
    
    # certbot续期验证目录
    location ^~ /.well-known/acme-challenge/ {
        alias ${WWW_ROOT}/.well-known/acme-challenge/;
        try_files \$uri =404;
    }
}
EOF
    
    log "Nginx SSL配置完成"
}

# 配置自动续期
setup_auto_renewal() {
    log "配置自动续期..."
    
    # 创建续期脚本
    cat > /usr/local/bin/renew-ssl.sh << EOF
#!/bin/bash
# SSL证书自动续期脚本

DOMAIN="${DOMAIN}"
LOG_FILE="/var/log/ssl-renewal.log"

echo "\$(date): 开始检查证书续期" >> "\$LOG_FILE"

# 停止Nginx使用80端口
${LNMP_PATH}/sbin/nginx -s stop 2>/dev/null

# 续期证书
if certbot renew --standalone --pre-hook "${LNMP_PATH}/sbin/nginx -s stop" --post-hook "${LNMP_PATH}/sbin/nginx"; then
    echo "\$(date): 证书续期成功" >> "\$LOG_FILE"
    
    # 检查证书有效期
    EXPIRY=\$(openssl x509 -enddate -noout -in /etc/letsencrypt/live/\${DOMAIN}/fullchain.pem | cut -d= -f2)
    echo "\$(date): 新证书到期时间: \$EXPIRY" >> "\$LOG_FILE"
    
    # 发送通知(需要配置邮件)
    # echo "SSL证书续期成功,到期时间: \$EXPIRY" | mail -s "SSL证书续期通知" ${EMAIL}
else
    echo "\$(date): 证书续期失败" >> "\$LOG_FILE"
    # 发送告警
    # echo "SSL证书续期失败,请手动检查" | mail -s "SSL证书续期告警" ${EMAIL}
fi

# 确保Nginx启动
if ! pgrep -x nginx > /dev/null; then
    ${LNMP_PATH}/sbin/nginx
fi
EOF
    
    chmod +x /usr/local/bin/renew-ssl.sh
    
    # 创建续期钩子目录
    mkdir -p /etc/letsencrypt/renewal-hooks/{pre,post,deploy}
    
    # 创建Nginx重启钩子
    cat > /etc/letsencrypt/renewal-hooks/deploy/restart-nginx.sh << EOF
#!/bin/bash
${LNMP_PATH}/sbin/nginx -s reload
EOF
    chmod +x /etc/letsencrypt/renewal-hooks/deploy/restart-nginx.sh
    
    # 添加到crontab(每周日凌晨3点检查续期)
    (crontab -l 2>/dev/null; echo "0 3 * * 0 /usr/bin/certbot renew --quiet --pre-hook \"${LNMP_PATH}/sbin/nginx -s stop\" --post-hook \"${LNMP_PATH}/sbin/nginx\"") | crontab -
    
    # 添加续期检查脚本到cron(每天检查一次)
    (crontab -l 2>/dev/null; echo "0 2 * * * /usr/local/bin/renew-ssl.sh >/dev/null 2>&1") | crontab -
    
    log "自动续期配置完成"
    log "续期日志: /var/log/ssl-renewal.log"
}

# 创建证书监控脚本
create_monitoring_script() {
    log "创建证书监控脚本..."
    
    cat > /usr/local/bin/check-ssl-expiry.sh << EOF
#!/bin/bash
# 证书过期监控脚本

DOMAIN="${DOMAIN}"
DAYS_WARNING=30
CERT_FILE="/etc/letsencrypt/live/\${DOMAIN}/fullchain.pem"
LOG_FILE="/var/log/ssl-check.log"

if [ -f "\$CERT_FILE" ]; then
    EXPIRY_DATE=\$(openssl x509 -enddate -noout -in "\$CERT_FILE" | cut -d= -f2)
    EXPIRY_SECONDS=\$(date -d "\$EXPIRY_DATE" +%s)
    CURRENT_SECONDS=\$(date +%s)
    DAYS_LEFT=\$(( (EXPIRY_SECONDS - CURRENT_SECONDS) / 86400 ))
    
    echo "\$(date): 证书 \${DOMAIN} 剩余 \$DAYS_LEFT 天" >> "\$LOG_FILE"
    
    if [ "\$DAYS_LEFT" -lt "\$DAYS_WARNING" ]; then
        echo "\$(date): 警告! 证书 \${DOMAIN} 将在 \$DAYS_LEFT 天后过期" >> "\$LOG_FILE"
        
        # 这里可以添加通知逻辑,例如发送邮件
        # echo "证书 \${DOMAIN} 将在 \$DAYS_LEFT 天后过期" | mail -s "证书过期警告" ${EMAIL}
        
        # 如果少于7天,尝试自动续期
        if [ "\$DAYS_LEFT" -lt 7 ]; then
            echo "\$(date): 尝试紧急续期证书..." >> "\$LOG_FILE"
            /usr/local/bin/renew-ssl.sh
        fi
    fi
else
    echo "\$(date): 错误: 证书文件不存在: \${CERT_FILE}" >> "\$LOG_FILE"
fi
EOF
    
    chmod +x /usr/local/bin/check-ssl-expiry.sh
    
    # 添加到crontab(每天凌晨1点检查)
    (crontab -l 2>/dev/null; echo "0 1 * * * /usr/local/bin/check-ssl-expiry.sh") | crontab -
    
    log "监控脚本已安装"
}

# 设置防火墙规则
setup_firewall() {
    log "配置防火墙..."
    
    # 检查UFW
    if command -v ufw &> /dev/null; then
        ufw allow 80/tcp
        ufw allow 443/tcp
        ufw reload
        log "UFW防火墙已配置"
    fi
    
    # 检查iptables
    if command -v iptables &> /dev/null && ! command -v ufw &> /dev/null; then
        iptables -A INPUT -p tcp --dport 80 -j ACCEPT
        iptables -A INPUT -p tcp --dport 443 -j ACCEPT
        log "iptables防火墙已配置"
    fi
}

# 测试SSL配置
test_ssl_configuration() {
    log "测试SSL配置..."
    
    # 测试Nginx配置
    if ${LNMP_PATH}/sbin/nginx -t; then
        log "✅ Nginx配置测试通过"
    else
        error "Nginx配置测试失败"
        exit 1
    fi
    
    # 重启Nginx
    ${LNMP_PATH}/sbin/nginx -s reload
    sleep 2
    
    # 测试SSL连接
    log "测试SSL连接..."
    if curl -s -o /dev/null -w "%{http_code}" "https://${DOMAIN}" | grep -q "200\|301\|302"; then
        log "✅ SSL连接测试成功"
    else
        warning "SSL连接测试可能有问题,请手动检查"
    fi
    
    # 显示证书信息
    log "证书信息:"
    openssl x509 -in /etc/letsencrypt/live/${DOMAIN}/fullchain.pem -text -noout | grep -A2 "Not Before\|Not After"
}

# 显示完成信息
show_completion() {
    echo ""
    echo "================================================"
    echo "✅ SSL证书自动配置完成!"
    echo "================================================"
    echo ""
    echo "📋 配置摘要:"
    echo "   域名: ${DOMAIN}"
    echo "   网站目录: ${WWW_ROOT}"
    echo "   证书位置: /etc/letsencrypt/live/${DOMAIN}/"
    echo "   Nginx配置: ${LNMP_PATH}/conf/vhost/${DOMAIN}.conf"
    echo ""
    echo "⚙️  自动续期设置:"
    echo "   每周日3:00自动检查续期"
    echo "   每天1:00检查证书过期状态"
    echo "   续期日志: /var/log/ssl-renewal.log"
    echo ""
    echo "🔧 手动操作命令:"
    echo "   重启Nginx: ${LNMP_PATH}/sbin/nginx -s reload"
    echo "   手动续期: certbot renew"
    echo "   查看证书: certbot certificates"
    echo ""
    echo "📊 验证SSL配置:"
    echo "   https://www.ssllabs.com/ssltest/analyze.html?d=${DOMAIN}"
    echo ""
    echo "⚠️  注意事项:"
    echo "   1. 确保DNS已正确解析到本服务器"
    echo "   2. 首次访问可能需要清除浏览器缓存"
    echo "   3. 建议在ssllabs.com测试SSL评分"
    echo "   4. 原配置文件已备份: ${LNMP_PATH}/conf/vhost/${DOMAIN}.conf.backup.*"
    echo ""
    echo "================================================"
}

# 主函数
main() {
    log "开始为 ${DOMAIN} 配置SSL证书"
    log "LNMP路径: ${LNMP_PATH}"
    log "网站目录: ${WWW_ROOT}"
    
    check_root
    check_lnmp
    install_certbot
    get_ssl_certificate
    configure_nginx_ssl
    setup_auto_renewal
    create_monitoring_script
    setup_firewall
    test_ssl_configuration
    show_completion
    
    log "所有配置完成!"
}

# 执行主函数
main

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

鲁ICP备2021023915号