下面是一个用于批量部署SSL证书到多台服务器的Shell脚本。它通过SSH/SCP将证书和私钥文件分发到远程服务器,设置正确的权限,并重新加载指定的服务(如Nginx、Apache)。脚本支持从文件读取服务器列表、自定义远程路径、用户名和端口,并包含详细的错误处理和日志输出。
bash
!/bin/bash
Script: deploy_ssl_cert.sh
描述: 批量将SSL证书部署到多台远程服务器
用法: ./deploy_ssl_cert.sh -l <服务器列表文件> -c <本地证书文件> -k <本地私钥文件> -d <远程目录> [选项]
示例: ./deploy_ssl_cert.sh -l servers.txt -c example.com.crt -k example.com.key -d /etc/nginx/ssl -s nginx
set -euo pipefail # 遇到错误立即退出,禁止未定义变量,管道错误检测
默认值
REMOTE_USER=""
REMOTE_PORT=22
REMOTE_DIR=""
SERVICE_NAME=""
CERT_SRC=""
KEY_SRC=""
CERT_DEST_NAME=""
KEY_DEST_NAME=""
SERVER_LIST=""
LOG_FILE="./deploy_ssl.log"
显示帮助信息
usage() {
cat <<EOF
用法: $0 -l <服务器列表文件> -c <证书文件> -k <私钥文件> -d <远程目录> [选项]
必需参数:
-l <文件> 服务器列表文件,每行格式: [user@]host[:port] (可省略user/port)
-c <文件> 本地证书文件路径(如 .crt .pem)
-k <文件> 本地私钥文件路径(如 .key)
-d <目录> 远程服务器上的目标目录(如 /etc/nginx/ssl)
可选参数:
-u <用户> 远程服务器SSH用户名(覆盖列表中的用户)
-p <端口> 远程服务器SSH端口(默认22,可被列表中的端口覆盖)
-s <服务> 部署后重新加载的服务名称(如 nginx, httpd, apache2)
-r <名称> 远程证书文件名(默认与本地证书文件同名)
-y <名称> 远程私钥文件名(默认与本地私钥文件同名)
-h 显示此帮助信息
服务器列表文件格式:
注释行以#开头
example.com
admin@192.168.1.10
web1.example.com:2222
user@server.example.com:2222
示例:
$0 -l servers.txt -c cert.crt -k priv.key -d /etc/ssl/certs -s nginx
EOF
exit 1
}
记录日志
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
检查必需命令是否存在
check_command() {
if ! command -v "$1" &>/dev/null; then
log "错误: 未找到命令 '$1',请安装后重试。"
exit 1
fi
}
解析命令行参数
while getopts "l:c:k:d:u:p:s:r:y:h" opt; do
case "$opt" in
l) SERVER_LIST="$OPTARG" ;;
c) CERT_SRC="$OPTARG" ;;
k) KEY_SRC="$OPTARG" ;;
d) REMOTE_DIR="$OPTARG" ;;
u) REMOTE_USER="$OPTARG" ;;
p) REMOTE_PORT="$OPTARG" ;;
s) SERVICE_NAME="$OPTARG" ;;
r) CERT_DEST_NAME="$OPTARG" ;;
y) KEY_DEST_NAME="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
验证必需参数
if [ -z "$SERVER_LIST" ] || [ -z "$CERT_SRC" ] || [ -z "$KEY_SRC" ] || [ -z "$REMOTE_DIR" ]; then
log "错误: 缺少必需参数。"
usage
fi
检查本地文件是否存在
if [ ! -f "$CERT_SRC" ]; then
log "错误: 证书文件 '$CERT_SRC' 不存在。"
exit 1
fi
if [ ! -f "$KEY_SRC" ]; then
log "错误: 私钥文件 '$KEY_SRC' 不存在。"
exit 1
fi
检查服务器列表文件
if [ ! -f "$SERVER_LIST" ]; then
log "错误: 服务器列表文件 '$SERVER_LIST' 不存在。"
exit 1
fi
检查必要的远程命令
check_command "ssh"
check_command "scp"
设置远程文件名(如果未指定则使用本地文件名)
CERT_DEST_NAME="${CERT_DEST_NAME:-$(basename "$CERT_SRC")}"
KEY_DEST_NAME="${KEY_DEST_NAME:-$(basename "$KEY_SRC")}"
log "开始批量部署 SSL 证书"
log "证书文件: $CERT_SRC -> $CERT_DEST_NAME"
log "私钥文件: $KEY_SRC -> $KEY_DEST_NAME"
log "目标目录: $REMOTE_DIR"
log "服务重载: ${SERVICE_NAME:-无}"
读取服务器列表,逐行处理
deploy_fail=0
deploy_success=0
while IFS= read -r line || [ -n "$line" ]; do
跳过空行和注释行
if [[ -z "$line" ]] || [[ "$line" =~ ^[[:space:]]*# ]]; then
continue
fi
去除首尾空格
line="$(echo "$line" | xargs)"
# 解析行中的 user@host:port
user="$REMOTE_USER"
host=""
port="$REMOTE_PORT"
检查是否包含 '@'
if [[ "$line" =~ @ ]]; then
user_part="${line%%@*}"
host_part="${line#*@}"
if [ -n "$user_part" ]; then
user="$user_part"
fi
else
host_part="$line"
fi
检查 host_part 是否包含端口 ':'
if [[ "$host_part" =~ :[0-9]+$ ]]; then
host="${host_part%:*}"
port="${host_part##*:}"
else
host="$host_part"
fi
if [ -z "$host" ]; then
log "警告: 跳过无效行 '$line'"
continue
fi
构建 SSH 连接字符串
if [ -n "$user" ]; then
ssh_target="$user@$host"
else
ssh_target="$host"
fi
ssh_cmd="ssh -p $port -o ConnectTimeout=10 $ssh_target"
scp_cmd="scp -P $port -o ConnectTimeout=10"
log "正在处理服务器: $ssh_target:$port"
测试 SSH 连接
if ! $ssh_cmd "echo OK" &>/dev/null; then
log "错误: 无法连接到 $ssh_target:$port,跳过。"
deploy_fail=$((deploy_fail + 1))
continue
fi
1. 在远程创建临时目录
temp_dir="/tmp/ssl_deploy_$$"
if ! $ssh_cmd "mkdir -p $temp_dir" &>/dev/null; then
log "错误: 无法在 $ssh_target 上创建临时目录 $temp_dir"
deploy_fail=$((deploy_fail + 1))
continue
fi
2. 复制证书和私钥到临时目录
log " 复制证书文件..."
if ! $scp_cmd "$CERT_SRC" "$ssh_target:$temp_dir/$CERT_DEST_NAME" &>/dev/null; then
log " 错误: 证书文件复制失败"
$ssh_cmd "rm -rf $temp_dir" &>/dev/null
deploy_fail=$((deploy_fail + 1))
continue
fi
log " 复制私钥文件..."
if ! $scp_cmd "$KEY_SRC" "$ssh_target:$temp_dir/$KEY_DEST_NAME" &>/dev/null; then
log " 错误: 私钥文件复制失败"
$ssh_cmd "rm -rf $temp_dir" &>/dev/null
deploy_fail=$((deploy_fail + 1))
continue
fi
3. 远程安装文件到目标目录,设置权限
log " 安装文件到 $REMOTE_DIR 并设置权限..."
remote_commands="
set -e
创建目标目录(如果不存在)
sudo mkdir -p $REMOTE_DIR
移动文件
sudo mv $temp_dir/$CERT_DEST_NAME $REMOTE_DIR/
sudo mv $temp_dir/$KEY_DEST_NAME $REMOTE_DIR/
设置所有权(通常为 root)
sudo chown root:root $REMOTE_DIR/$CERT_DEST_NAME $REMOTE_DIR/$KEY_DEST_NAME
设置权限:证书可读,私钥仅所有者可读写
sudo chmod 644 $REMOTE_DIR/$CERT_DEST_NAME
sudo chmod 600 $REMOTE_DIR/$KEY_DEST_NAME
清理临时目录
rmdir $temp_dir 2>/dev/null || true
"
if ! $ssh_cmd "$remote_commands" &>/dev/null; then
log " 错误: 安装文件或设置权限失败"
$ssh_cmd "rm -rf $temp_dir" &>/dev/null
deploy_fail=$((deploy_fail + 1))
continue
fi
4. 如果需要,重新加载服务
if [ -n "$SERVICE_NAME" ]; then
log " 重新加载服务: $SERVICE_NAME"
reload_commands="
if command -v systemctl &>/dev/null && systemctl is-active $SERVICE_NAME &>/dev/null; then
sudo systemctl reload $SERVICE_NAME
elif command -v service &>/dev/null && service $SERVICE_NAME status &>/dev/null; then
sudo service $SERVICE_NAME reload
elif [ -f /etc/init.d/$SERVICE_NAME ]; then
sudo /etc/init.d/$SERVICE_NAME reload
else
echo '无法确定服务管理方式,请手动重载' >&2
exit 1
fi
"
if ! $ssh_cmd "$reload_commands" &>/dev/null; then
log " 警告: 服务重载失败,请手动检查"
# 不影响整体成功计数,但记录
fi
fi
log " 部署成功完成"
deploy_success=$((deploy_success + 1))
done < "$SERVER_LIST"
汇总结果
log "========================================="
log "部署完成: 成功 $deploy_success, 失败 $deploy_fail"
if [ $deploy_fail -gt 0 ]; then
log "部分服务器部署失败,请检查日志: $LOG_FILE"
exit 1
else
log "所有服务器部署成功。"
exit 0
fi
使用说明
1. 保存脚本
将上述代码保存为 `deploy_ssl_cert.sh`,并赋予执行权限:
bash
chmod +x deploy_ssl_cert.sh
2. 准备服务器列表文件
创建一个文本文件(如 `servers.txt`),每行一台服务器,支持以下格式:
注释行
example.com
admin@192.168.1.10
web1.example.com:2222
user@server.example.com:2222
3. 准备证书文件
确保本地已有要分发的证书文件(如 `example.com.crt`)和私钥文件(如 `example.com.key`)。
4. 运行脚本
必需参数:`-l` 列表文件,`-c` 证书文件,`-k` 私钥文件,`-d` 远程目标目录。
可选参数:`-u` 指定默认用户名,`-p` 默认端口,`-s` 服务名称(如 nginx),`-r`/`-y` 远程文件名。
示例:
```bash
./deploy_ssl_cert.sh -l servers.txt -c example.com.crt -k example.com.key -d /etc/nginx/ssl -s nginx
5. 查看日志
脚本执行过程会输出到终端并同时写入 `./deploy_ssl.log` 文件。
注意事项
SSH 免密登录:脚本依赖 `ssh` 和 `scp`,请确保从执行脚本的机器能通过 SSH 密钥登录所有目标服务器(或已配置密码缓存如 `ssh-agent`)。
sudo 权限:远程用户需要拥有 `sudo` 权限,且最好配置为 `NOPASSWD`,否则脚本可能会因等待密码而卡住。如果无法使用无密码 sudo,可以考虑在远程命令中嵌入密码(不推荐)或使用 `expect` 工具,但本脚本未做此处理。
路径和权限:脚本会自动创建目标目录(如果不存在),并将证书权限设为 `644`、私钥设为 `600`,所有者设为 `root:root`。您可根据实际情况修改 `remote_commands` 部分。
服务重载:支持 `systemctl`、`service` 以及 `/etc/init.d/` 三种方式管理服务。如果重载失败,脚本会给出警告但不会中断部署。
错误处理:脚本采用 `set -e`,但通过子shell或命令组避免了部分错误导致整个脚本退出。部署失败的主机会被跳过并计数。
扩展建议
若需要处理通配符SSL证书或多个证书文件(如中间证书),可修改脚本接受多个 `-c` 参数或使用通配符。
对于更复杂的环境,建议使用 Ansible、Salt 等配置管理工具,本脚本适用于轻量级批量部署场景。