SSL: автоматическое обновление

Сервер Linux — nginx, Apache, Caddy, HAProxy, Docker

Скрипт для nginx, Apache, Caddy, HAProxy и Docker раз в сутки забирает свежий сертификат, сверяет его с вашим ключом и перезапускает сервер без простоя.

Обновляется сам

Подходит, если у вас есть доступ к серверу по SSH и права root (VPS, выделенный сервер, свой компьютер в стойке). Нужны curl и openssl — они есть почти в каждом Linux.

Что понадобится

  • Ссылка на сертификат из кабинета: https://api.zrazu.com/ssl-pull/zsp_ВАШ_ТОКЕН.
  • Закрытый ключ, с которым заказывали сертификат (файл key.pem или похожий). Если CSR создавал кабинет ZRAZU — это файл, который вы тогда сохранили.
  • 10 минут.

Шаг 1. Положите ключ на сервер

sudo mkdir -p /etc/ssl/zrazu/example.com
sudo chmod 700 /etc/ssl/zrazu/example.com
sudo nano /etc/ssl/zrazu/example.com/key.pem
sudo chmod 600 /etc/ssl/zrazu/example.com/key.pem

В nano вставьте весь текст ключа вместе со строками -----BEGIN … PRIVATE KEY----- и -----END … PRIVATE KEY-----, сохраните (Ctrl+O, Enter) и выйдите (Ctrl+X). Ключ без пароля: если ваш ключ запаролен, снимите пароль командой openssl pkey -in старый.key -out /etc/ssl/zrazu/example.com/key.pem.

Шаг 2. Скрипт обновления

Создайте файл sudo nano /usr/local/bin/zrazu-ssl-example.com.sh, вставьте текст ниже и измените только строку RELOAD под свой веб-сервер:

  • nginx — nginx -t && systemctl reload nginx (так стоит);
  • Apache (Debian, Ubuntu) — apachectl configtest && systemctl reload apache2; Apache (CentOS, Rocky, Alma) — apachectl configtest && systemctl reload httpd;
  • Caddy — systemctl reload caddy;
  • HAProxy — systemctl reload haproxy и COMBINED=1 (HAProxy читает сертификат и ключ одним файлом combined.pem);
  • nginx в Docker — docker exec ИМЯ_КОНТЕЙНЕРА nginx -s reload (каталог /etc/ssl/zrazu подключите в контейнер как том).
#!/bin/sh
# ZRAZU: обновление SSL-сертификата по личной ссылке. Запускается cron раз в сутки.
set -eu
# cron даёт короткий PATH без /usr/sbin, а nginx и apachectl лежат именно там.
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PULL="https://api.zrazu.com/ssl-pull/zsp_ВАШ_ТОКЕН"
DIR="/etc/ssl/zrazu/example.com"
KEY="$DIR/key.pem"
RELOAD="nginx -t && systemctl reload nginx"
COMBINED=0

umask 077
tmp=$(mktemp "$DIR/.new.XXXXXX")
trap 'rm -f "$tmp"' EXIT
curl -fsS --retry 3 --max-time 60 -o "$tmp" "$PULL/fullchain.pem"
grep -q "BEGIN CERTIFICATE" "$tmp" || { echo "ZRAZU: по ссылке не сертификат"; exit 1; }
if [ -f "$DIR/.applied" ] && cmp -s "$tmp" "$DIR/.applied"; then
  echo "ZRAZU: сертификат не изменился"
  exit 0
fi
cert_pub=$(openssl x509 -in "$tmp" -noout -pubkey | openssl sha256)
key_pub=$(openssl pkey -in "$KEY" -pubout | openssl sha256)
if [ "$cert_pub" != "$key_pub" ]; then
  echo "ZRAZU: сертификат выпущен на другой ключ — ничего не меняю, положите новый ключ в $KEY"
  exit 1
fi
openssl x509 -in "$tmp" -noout -checkend 0 >/dev/null || { echo "ZRAZU: сертификат уже не действует"; exit 1; }
chmod 644 "$tmp"
mv -f "$tmp" "$DIR/fullchain.pem"
if [ "$COMBINED" = 1 ]; then
  cat "$DIR/fullchain.pem" "$KEY" > "$DIR/combined.pem.new"
  mv -f "$DIR/combined.pem.new" "$DIR/combined.pem"
fi
sh -c "$RELOAD"
cp "$DIR/fullchain.pem" "$DIR/.applied"
echo "ZRAZU: новый сертификат установлен, действует до $(openssl x509 -in "$DIR/fullchain.pem" -noout -enddate | cut -d= -f2)"

Скрипт ничего не меняет, если веб-сервер уже работает с этим сертификатом, если файл по ссылке не сертификат, если сертификат не подходит к вашему ключу или уже не действует. Конфигурацию веб-сервера он проверяет перед перезапуском, так что сломанный конфиг сайт не уронит; а если перезапуск не удался, на следующий день скрипт попробует ещё раз.

Сделайте файл исполняемым и запустите один раз вручную:

sudo chmod 700 /usr/local/bin/zrazu-ssl-example.com.sh
sudo /usr/local/bin/zrazu-ssl-example.com.sh

Должно появиться «новый сертификат установлен» и файл /etc/ssl/zrazu/example.com/fullchain.pem.

Шаг 3. Укажите файлы в веб-сервере (один раз)

nginx — в блоке server сайта:

ssl_certificate     /etc/ssl/zrazu/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/zrazu/example.com/key.pem;

Apache 2.4.8 и новее — в <VirtualHost *:443>:

SSLCertificateFile    /etc/ssl/zrazu/example.com/fullchain.pem
SSLCertificateKeyFile /etc/ssl/zrazu/example.com/key.pem

Caddy — в блоке сайта tls /etc/ssl/zrazu/example.com/fullchain.pem /etc/ssl/zrazu/example.com/key.pem. HAProxy — в bind :443 ssl crt /etc/ssl/zrazu/example.com/combined.pem.

Проверьте конфиг (nginx -t или apachectl configtest) и перезапустите веб-сервер.

Шаг 4. Расписание

echo "17 4 * * * root /usr/local/bin/zrazu-ssl-example.com.sh >> /var/log/zrazu-ssl.log 2>&1" | sudo tee /etc/cron.d/zrazu-ssl-example.com

Скрипт будет запускаться каждый день в 04:17 по времени сервера. Минуту можно взять любую — лучше не ровно 00.

Проверка

  1. sudo tail /var/log/zrazu-ssl.log — завтра там появится «сертификат не изменился»: расписание работает.
  2. Сайт отдаёт наш сертификат: echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -issuer -enddate или страница https://zrazu.com/tools/ssl/example.com/.
  3. В кабинете, в карточке сертификата, видно, что сервер забрал действующий сертификат.

Если не получилось

  • curl: (22) … 404 — ссылка отключена или заменена. В карточке сертификата создайте новую и замените строку PULL в скрипте.
  • curl: (22) … 429 — слишком много запросов (больше 60 в час). Раз в сутки — с большим запасом.
  • «сертификат выпущен на другой ключ» — сертификат перевыпускали с новым ключом. Положите новый ключ в key.pem и запустите скрипт ещё раз.
  • nginx: [emerg] после перезапуска — ошибка в конфигурации сайта, не в сертификате; старый сертификат остаётся в работе, пока ошибку не исправите.

Эта страница в Markdown