Deploy Hook 是 Certbot 中用于在证书成功签发或续期后自动执行脚本的一种机制。它常用于自动化部署证书,比如重启服务、复制证书、更新配置等。


一、Certbot Hook 简介

在 Certbot 的生命周期中,主要有三种 Hook:

Hook 类型触发时机
--pre-hook申请/续期前执行
--post-hook申请/续期后执行(无论成功与否)
--deploy-hook仅在证书成功更新后执行

Deploy Hook 是最常用的一种自动化方式,因为它只在证书真正更新时触发,避免不必要操作。


二、Deploy Hook 适用场景

Deploy Hook 常用于:

  • 重启 Web 服务(Nginx / Apache)
  • 重新加载 TLS 配置
  • 复制证书到其他路径
  • 通知其他系统(如 API、负载均衡器)
  • 更新容器或服务中的证书

三、基本使用方法

1. 在命令行中使用

1
certbot renew --deploy-hook "/usr/local/bin/reload-nginx.sh"

或申请证书时:

1
2
3
certbot certonly \
  --deploy-hook "/usr/local/bin/deploy-cert.sh" \
  -d example.com

2. 使用目录自动执行(推荐)

Certbot 支持自动执行 Hook 目录中的脚本:

目录路径 : /etc/letsencrypt/renewal-hooks/deploy/

只需将脚本放入该目录:

1
/etc/letsencrypt/renewal-hooks/deploy/

并赋予执行权限:

1
chmod +x /etc/letsencrypt/renewal-hooks/deploy/your-script.sh

Certbot 在成功续期后会自动执行这些脚本。

四、Deploy Hook 脚本示例

示例 1:重启 Nginx

1
2
#!/bin/bash
systemctl reload nginx

示例 2:复制证书到自定义路径

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/bash

DOMAIN="example.com"
LE_PATH="/etc/letsencrypt/live/$DOMAIN"
TARGET_PATH="/opt/certs"

cp "$LE_PATH/fullchain.pem" "$TARGET_PATH/fullchain.pem"
cp "$LE_PATH/privkey.pem" "$TARGET_PATH/privkey.pem"

echo "Certificates updated for $DOMAIN"

示例 3:通知系统更新(HTTP API)

1
2
3
4
5
6
#!/bin/bash

curl -X POST https://api.example.com/cert-updated \
  -d "domain=$RENEWED_LINEAGE"

echo "Notification sent"

五、工业级 Deploy Hook 架构设计(可维护 + 可扩展)

在实际生产环境中,直接把逻辑写在 /etc/letsencrypt/renewal-hooks/deploy/ 里的单个脚本,很快会变得难以维护。

更推荐采用一种入口调度 + 模块化脚本架构

Deploy Hook 只负责“调度”,具体逻辑拆分到多个独立脚本


一、整体架构设计

推荐目录结构如下:

脚本入口:

/etc/letsencrypt/renewal-hooks/deploy/
└── dispatcher.sh

/usr/local/lib/certbot/
├── deploy/
│   ├── 10-axigen.sh
│   ├── 20-hysteria.sh
│   └── 90-notify.sh
│
├── lib/
│   ├── common.sh
│   └── logger.sh

二、核心设计思想

1. 入口统一

Certbot 只调用 :

1
/etc/letsencrypt/renewal-hooks/deploy/dispatcher.sh

2. 业务解耦

每个服务一个脚本:

  • Nginx → 10-nginx.sh
  • Docker → 20-docker.sh
  • Hysteria → 30-hysteria.sh
  • 通知系统 → 90-notify.sh

用数字排序控制执行顺序

3. 可维护性

单个脚本职责单一 可单独测试 可独立启用/禁用

三、入口调度脚本(Dispatcher)

/etc/letsencrypt/renewal-hooks/deploy/dispatcher.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#!/bin/bash
set -euo pipefail

HOOK_DIR="/usr/local/lib/certbot/deploy"

echo "[certbot] deploy hook started"

# 按顺序执行所有脚本
for script in "$HOOK_DIR"/*.sh; do
    if [ -x "$script" ]; then
        echo "[certbot] running: $script"
        "$script"
    else
        echo "[certbot] skip (not executable): $script"
    fi
done

echo "[certbot] deploy hook finished"

核心点:

  • 自动执行所有模块
  • 自动排序(通过文件名)
  • 可扩展性极强

四、模块脚本示例

示例 1:Nginx Reload /usr/local/lib/certbot/deploy/10-nginx.sh

1
2
3
4
5
6
7
8
#!/bin/bash
set -euo pipefail

echo "[nginx] reloading..."

systemctl reload nginx

echo "[nginx] done"

示例 2:Docker 证书更新 /usr/local/lib/certbot/deploy/20-docker.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/bin/bash
set -euo pipefail

DOMAIN="example.com"
SRC="/etc/letsencrypt/live/$DOMAIN"

TARGET="/opt/docker/certs"

echo "[docker] updating certs..."

cp "$SRC/fullchain.pem" "$TARGET/fullchain.pem"
cp "$SRC/privkey.pem" "$TARGET/privkey.pem"

docker restart my-container

echo "[docker] done"

示例 3:Hysteria / 其他服务 /usr/local/lib/certbot/deploy/30-hysteria.sh

#!/bin/bash
set -euo pipefail

LE_PATH="/etc/letsencrypt/live/www.example.com"

cp "$LE_PATH/fullchain.pem" /etc/hysteria/cert.pem
cp "$LE_PATH/privkey.pem" /etc/hysteria/key.pem

systemctl restart hysteria

echo "[hysteria] restarted"

示例 4:通知系统 /usr/local/lib/certbot/deploy/90-notify.sh

1
2
3
4
5
6
7
8
9
#!/bin/bash
set -euo pipefail

echo "[notify] sending webhook..."

curl -fsS -X POST https://example.com/webhook \
  -d "event=cert_updated&domain=${RENEWED_LINEAGE}"

echo "[notify] done"

五、公共库(可选但推荐)

日志工具层

作用:统一日志输出格式,每个脚本各写各的日志格式

Q:它解决什么问题?

A:没有 logger:

示例 :临时日志(带时间戳的 echo) /usr/local/lib/certbot/lib/logger.sh

1
2
3
log() {
    echo "[$(date +'%F %T')] $*"
}

本质理解

logger.sh = 日志规范层

  • 统一输出格式
  • 方便后续接入:
    • syslog
    • journald
    • ELK / Loki
  • 未来可以轻松升级日志系统,而不用改业务脚本

通用逻辑层

这个脚本通常放:

  • 公共函数
  • 公共变量
  • 常用判断逻辑

示例 : 函数 /usr/local/lib/certbot/lib/common.sh

1
2
3
4
5
#!/bin/bash

get_cert_path() {
    echo "${RENEWED_LINEAGE:-}"
}

Q: 它解决什么问题?

A: 没有 common.sh 每个脚本都要写:

CERT_PATH="/etc/letsencrypt/live/example.com"

或:

if [ -z "$RENEWED_LINEAGE" ]; then
  echo "error"
  exit 1
fi

重复 + 易出错

common.sh

source /usr/local/lib/certbot/lib/common.sh

CERT_PATH=$(get_cert_path)

统一入口

本质理解

common.sh = 业务基础能力层

它提供:

  • 环境变量封装
  • 路径获取
  • 通用判断
  • 公共工具函数

两者关系(重点) 可以用一个简单分层理解:

[ deploy 脚本(业务逻辑) ]
          ↓
[ common.sh(通用逻辑) ]
          ↓
[ logger.sh(基础输出能力) ]

实际使用方式

在你的 deploy 脚本中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#!/bin/bash
set -euo pipefail

# 引入公共库
source /usr/local/lib/certbot/lib/logger.sh
source /usr/local/lib/certbot/lib/common.sh

log "[nginx] start"

CERT_PATH=$(get_cert_path)

log "[nginx] cert path: $CERT_PATH"

systemctl reload nginx

log "[nginx] done"