HAProxy давно перестал быть просто балансировщиком: на многих инсталляциях он одновременно и L7 reverse-proxy, и точка терминации TLS, и место, где сходятся десятки доменов с разными сертификатами. В такой схеме вопрос «как именно прикрутить Let's Encrypt» уже не праздный — от него зависят надёжность, удобство автоматизации и даже архитектура.
В этой статье разберём практичный стек:
- HAProxy как точка терминации TLS для множества доменов.
- Let's Encrypt с двумя клиентами: Certbot и acme.sh.
- HTTP-01 и TLS-ALPN-01, встраивание
/.well-known/acme-challengeв конфиг. - SNI и директивы
crt/crt-list/crt-base. - Использование
mapдля динамического выбора backend и логики. - Ротация сертификатов без даунтайма и перезапусков.
Фокус будет на живых примерах конфигурации, а не теории TLS.
Где заканчивается TLS: HAProxy как единственная точка терминации
Первый архитектурный вопрос: где именно терминировать TLS? На уровне приложений (Nginx/Apache/Node.js) или прямо в HAProxy? В большинстве сценариев удобнее вынести TLS в HAProxy:
- Единая точка управления шифрами, версиями TLS и политикой.
- Простая интеграция с Let's Encrypt: весь трафик проходит через одну точку.
- Проще SNI-мультиплексинг десятков доменов.
- Быстрая ротация сертификатов: перезагрузили только HAProxy — и готово.
Дальше будем исходить из того, что TLS-терминация происходит в frontend https-in, а к backend-сервисам ходим по HTTP (внутренний трафик). Если вам нужно TLS и до backend'ов — это отдельная тема, но базовые принципы работы с сертификатами те же.
Если раздаёте несколько проектов с одного сервера (или кластера), существенно упростить жизнь поможет заранее продуманная структура доменов и сертификатов. Для части проектов подойдёт общий wildcard, для части — отдельные DV или SAN. Отдельно можно посмотреть подводные камни мультидоменных и SAN-сертификатов в связке с автоматизацией в материале про автоматизацию цепочки DV/SAN-сертификатов.
Let's Encrypt: Certbot или acme.sh для HAProxy
У Let's Encrypt десятки клиентов, но в реальности админы чаще всего выбирают между Certbot и acme.sh.
Certbot: плюсы и нюансы с HAProxy
Certbot — «официальный» клиент, доступен в репозиториях большинства дистрибутивов и хорошо задокументирован. Однако он заточен под веб-серверы вроде Nginx/Apache, а для HAProxy приходится либо использовать --standalone, либо писать обвязку.
Типичные варианты интеграции:
- standalone: Certbot поднимает временный HTTP-сервер на 80-м порту, HAProxy на время освобождает порт.
- webroot: Certbot кладёт challenge-файлы в webroot backend'а, а HAProxy просто проксирует
/.well-known/acme-challengeдо него. - dns-01: через DNS-плагины, если нужен wildcard и нет нормального HTTP-доступа.
Для HAProxy чаще всего удобен режим webroot, потому что он не требует остановки HAProxy и не лезет в его конфигурацию.
acme.sh: простота, один файл сертификата и cron
acme.sh — лёгкий shell-клиент для ACME, популярный именно в связке с прокси и балансировщиками. Его плюсы в контексте HAProxy:
- Не тянет тяжёлые зависимости (Python и т.п.).
- Легко заставить класть сертификаты сразу в удобном для HAProxy виде
fullchain.pem + privkey.pemили объединённый.pem. - Простой cron-режим без демонов.
- Много готовых хуков для DNS-API.
Поскольку HAProxy умеет работать с «склеенными» файлами, где в одном .pem лежит и сертификат, и цепочка, и ключ, с acme.sh получается особенно аккуратная интеграция: выпускаем сертификат, склеиваем (или настраиваем прямо так), кладём в директорию, на которую смотрит crt/crt-list, делаем reload HAProxy.
Если вы держите пару десятков сайтов на одном экземпляре HAProxy, подумайте сразу, хотите ли вы управлять этим сервером как отдельной машиной или как частью более гибкой инфраструктуры. В сценариях с большим количеством проектов часто удобнее вынести балансировщик на отдельный VDS, а приложения крутить на других узлах.

Базовый TLS-frontend c SNI
Перед тем как трогать Let's Encrypt, стоит привести в порядок базовую TLS-конфигурацию HAProxy. Простой пример для двух доменов:
frontend https-in
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem crt /etc/haproxy/certs/api.example.com.pem alpn h2,http/1.1
mode http
http-request redirect scheme https code 301 if !{ ssl_fc }
acl host_web hdr(host) -i example.com
acl host_api hdr(host) -i api.example.com
use_backend web if host_web
use_backend api if host_api
backend web
server web1 127.0.0.1:8080 check
backend api
server api1 127.0.0.1:8081 check
Когда доменов становится больше, хранить каждый crt в bind-строке неудобно — лучше использовать crt-list или crt-base:
frontend https-in
bind *:443 ssl crt-list /etc/haproxy/crt-list.txt alpn h2,http/1.1
mode http
Содержимое /etc/haproxy/crt-list.txt:
/etc/haproxy/certs/example.com.pem
/etc/haproxy/certs/api.example.com.pem
/etc/haproxy/certs/other-site.com.pem
Либо так:
frontend https-in
bind *:443 ssl crt-base /etc/haproxy/certs alpn h2,http/1.1
В этом случае HAProxy просто берёт все .pem-файлы из каталога.
HTTP-01: как отдать /.well-known/acme-challenge через HAProxy
Для HTTP-01-челленджа Let's Encrypt нужно, чтобы по адресу
http://example.com/.well-known/acme-challenge/<token>
отдавался определённый контент. Если у вас уже есть HAProxy, который слушает 80-й порт, логично просто проксировать этот путь до backend'а с статикой или до встроенного в Certbot/acme.sh webroot.
Пример схемы:
- HAProxy слушает 80/443.
- Есть отдельный backend
acme, который ведёт на маленький HTTP-сервер (например, на Nginx или встроенный в Certbot webroot). - Все запросы
/.well-known/acme-challengeна любом домене уводим в этот backend.
frontend http-in
bind *:80
mode http
acl acme_challenge path_beg /.well-known/acme-challenge/
use_backend acme if acme_challenge
# дальше обычные редиректы на https или проксирование
http-request redirect scheme https code 301 if !acme_challenge
backend acme
server acme1 127.0.0.1:8085 check
На 8085 порту крутится простейший HTTP-сервер, откуда Certbot или acme.sh забирают/кладут challenge-файлы. Например, Nginx с корнем /var/www/acme, а клиент запускаем с --webroot /var/www/acme (Certbot) или с аналогичным параметром webroot у acme.sh.
Если вам важно поддерживать HSTS и жёсткий редирект на HTTPS, аккуратнее отлаживать такую схему заранее и не забывать про особые случаи (например, health-check-и внешних мониторингов на 80-м порту). Про HSTS и связанные с ним подводные камни в Let's Encrypt-инфраструктуре мы отдельно разбирали в статье о Certbot и строгом HTTPS.

Использование map для маршрутизации по доменам
Когда доменов десятки, писать кучу acl host_xxx hdr(host) и use_backend быстро превращается в ад. Для этого в HAProxy есть map-файлы: внешние таблицы соответствий, которые можно менять без переписывания основного конфига.
Простейший пример: есть файл /etc/haproxy/maps/hosts.map с таким содержимым:
example.com web
api.example.com api
static.example.com static
В frontend пишем:
frontend https-in
bind *:443 ssl crt-base /etc/haproxy/certs alpn h2,http/1.1
mode http
http-request set-var(txn.backend_name) map(/etc/haproxy/maps/hosts.map,lower,hdr(host))
use_backend %[var(txn.backend_name)] if { var(txn.backend_name) -m found }
default_backend web
Теперь, чтобы привязать новый домен к backend, достаточно дописать одну строку в hosts.map и перезагрузить HAProxy. Точно так же можно использовать map для разных флагов: включение HSTS, разных timeouts по доменам, логгирования и т.п.
Если вы используете HAProxy как фронт к нескольким приложениям с разными доменами на одном IP, не забывайте и про грамотное управление самими доменами: удобнее вести их в одном аккаунте и держать под рукой автообновление NS и WHOIS. В этом хорошо помогает централизованная регистрация доменов для всех проектов.
Как хранить сертификаты для множества доменов
Стандартная схема хранения сертификатов под HAProxy:
- Один каталог, например
/etc/haproxy/certs. - Для каждого домена один
.pem-файл:example.com.pem,api.example.com.pemи т.д. - Внутри файла: приватный ключ, затем сертификат домена, затем цепочка.
Порядок важен: HAProxy ожидает сначала -----BEGIN PRIVATE KEY----- или аналогичный блок, затем -----BEGIN CERTIFICATE----- (сертификат домена), затем промежуточные.
Если клиент (Certbot/acme.sh) по умолчанию кладёт файлы отдельно, имеет смысл сделать пост-обработку: собирать единый .pem и класть в /etc/haproxy/certs. Это удобно делать в deploy-hook или reload-hook.
Интеграция HAProxy + Certbot на практике
Возьмём пример с Certbot в режиме webroot и одним доменом example.com.
Шаг 1: готовим webroot и backend
Создаём директорию для челленджей и backend в HAProxy, как описано выше:
mkdir -p /var/www/acme/.well-known/acme-challenge
Поднимаем простой HTTP-сервер (любой на ваш вкус) на 8085 с корнем в /var/www/acme, либо используем уже существующий.
Шаг 2: первый выпуск сертификата Certbot
Запускаем Certbot, указав webroot:
certbot certonly --webroot -w /var/www/acme -d example.com -d www.example.com
После успешного выпуска сертификаты появятся в стандартном каталоге /etc/letsencrypt/live/example.com/. Нам нужно склеить их в один .pem для HAProxy:
cat /etc/letsencrypt/live/example.com/privkey.pem /etc/letsencrypt/live/example.com/fullchain.pem > /etc/haproxy/certs/example.com.pem
Не забываем выставить права так, чтобы HAProxy мог читать, но лишние пользователи — нет. Часто достаточно группы haproxy и chmod 640.
Шаг 3: конфиг HAProxy с новым сертификатом
В frontend https-in добавляем путь к файлу (если используем crt-base, он уже подхватится автоматически):
frontend https-in
bind *:443 ssl crt-base /etc/haproxy/certs alpn h2,http/1.1
mode http
# остальная логика
Перезагружаем HAProxy с проверкой конфига:
haproxy -c -f /etc/haproxy/haproxy.cfg
systemctl reload haproxy
Шаг 4: автоматизация продления и релоада HAProxy
Certbot обычно ставит cron/systemd-таймер, который вызывает certbot renew. Нам надо ещё и обновлять .pem-файл HAProxy и делать reload после успешного продления.
Создадим простой скрипт, который будет вызываться как --deploy-hook:
#!/bin/sh
DOMAIN="$CERTBOT_DOMAIN"
LIVE_DIR="/etc/letsencrypt/live/$DOMAIN"
HAPROXY_PEM="/etc/haproxy/certs/$DOMAIN.pem"
if [ -f "$LIVE_DIR/privkey.pem" ] && [ -f "$LIVE_DIR/fullchain.pem" ]; then
cat "$LIVE_DIR/privkey.pem" "$LIVE_DIR/fullchain.pem" > "$HAPROXY_PEM"
chmod 640 "$HAPROXY_PEM"
chown root:haproxy "$HAPROXY_PEM"
haproxy -c -f /etc/haproxy/haproxy.cfg && systemctl reload haproxy
fi
Ставим права на исполнение и прописываем его при выпуске сертификата:
chmod +x /usr/local/bin/certbot-haproxy-hook.sh
certbot certonly --webroot -w /var/www/acme -d example.com -d www.example.com --deploy-hook /usr/local/bin/certbot-haproxy-hook.sh
В дальнейшем certbot renew сам будет вызывать хук при успешном продлении.
Интеграция HAProxy + acme.sh
С acme.sh концепция похожа, но интеграция с HAProxy зачастую получается чище, так как клиент легко настраивается на вывод уже «правильного» формата.
Установка и базовая настройка acme.sh
Устанавливать можно разными способами, но принцип один: скрипт кладётся в домашнюю директорию и регистрируется cron для обновлений.
Далее настраиваем домен с HTTP-01 через webroot, как в примере с Certbot, либо сразу используем DNS-API, если нужен wildcard и есть подходящая интеграция с вашим DNS-провайдером.
Выпуск сертификата и direct-вывод в каталог HAProxy
acme.sh позволяет указать путь и формат установки сертификатов для конкретного домена. Например:
acme.sh --issue -d example.com -d www.example.com --webroot /var/www/acme
После успешного выпуска делаем установку в нужный формат:
acme.sh --install-cert -d example.com --key-file /etc/haproxy/certs/example.com.key --fullchain-file /etc/haproxy/certs/example.com.fullchain.pem --reloadcmd "cat /etc/haproxy/certs/example.com.key /etc/haproxy/certs/example.com.fullchain.pem > /etc/haproxy/certs/example.com.pem && chmod 640 /etc/haproxy/certs/example.com.pem && chown root:haproxy /etc/haproxy/certs/example.com.pem && haproxy -c -f /etc/haproxy/haproxy.cfg && systemctl reload haproxy"
Таким образом, при каждом продлении acme.sh автоматически перегенерирует ключ и цепочку, соберёт .pem и перезагрузит HAProxy.
TLS-ALPN-01 и HAProxy
Помимо HTTP-01, есть челлендж TLS-ALPN-01: Let's Encrypt подключается по 443 порту и ожидает, что сервер ответит специальным сертификатом с расширением ACME. С HAProxy это уже ощутимо сложнее, потому что нужно уметь подменять сертификат на время проверки.
Поддержка TLS-ALPN-01 для HAProxy возможна через использование отдельных bind'ов или динамической подстановки сертификатов, но такой сценарий гораздо более специфичен и сложен в отладке. На практике, если есть возможность использовать HTTP-01 или DNS-01, лучше сделать так и не усложнять конфигурацию.
Для проектов, где 80-й порт принципиально недоступен, а DNS-API по каким-то причинам использовать нельзя, TLS-ALPN-01 действительно может быть единственным вариантом, но это уже тема отдельной, узкоспециализированной статьи.
Wildcard-сертификаты: когда без DNS-01 не обойтись
Если у вас SaaS с десятками субдоменов вроде client1.example.com, client2.example.com и т.п., wildcard-сертификат *.example.com сильно упрощает жизнь. Let's Encrypt выдаёт wildcard только через DNS-01-челлендж, т.е. клиент должен уметь создавать TXT-записи в зоне.
И Certbot, и acme.sh имеют плагины и скрипты для взаимодействия с популярными DNS-провайдерами (Cloudflare, PowerDNS API и т.д.). С HAProxy сценарий выглядит так:
- Клиент выпускает wildcard через DNS-01 (никакой HTTP-трафик не нужен).
- Собирает сертификат и ключ в единый
.pemи кладёт в/etc/haproxy/certs/wildcard-example.com.pem. - HAProxy в
bindилиcrt-baseподхватывает этот файл и обслуживает все субдомены.
Если при этом у вас есть ещё и отдельные сертификаты для конкретных доменов (например, example.com без звёздочки), HAProxy по SNI выберет наиболее подходящий сертификат, поэтому можно спокойно комбинировать wildcard и обычные.
Тонкости reload и zero-downtime
Перезапуск HAProxy ради обновления сертификатов — не лучшая идея в продакшене. К счастью, HAProxy умеет «мягкий» reload без обрыва активных соединений, если правильно настроить systemd-юнит.
Основные моменты:
- Используйте
systemctl reload haproxy, а неrestart. - Перед reload делайте проверку конфигурации
haproxy -c -f /etc/haproxy/haproxy.cfg. - Не меняйте пути к сертификатам динамически во время продления — только содержимое файлов.
При такой схеме Certbot или acme.sh могут безопасно дёргать reload после успешного продления, а HAProxy подхватит новые сертификаты без даунтайма.
Использование map для гибких TLS-настроек
map-файлы полезны не только для маршрутизации по backend, но и для гибкого управления TLS-поведением по доменам. Например, вы можете включать HSTS только для доменов, где уже точно везде работает HTTPS, или задавать разные security-профили.
Пример: карта, где указан режим HSTS по доменам:
/etc/haproxy/maps/hsts.map:
example.com on
secure.example.com on
legacy.example.com off
В frontend:
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" if { hdr(Host),lower -m map(/etc/haproxy/maps/hsts.map) -m str on }
Та же идея применима и к другим заголовкам, redirect-политикам и даже выбору backend'а в зависимости от того, TLS это или нет.
Диагностика проблем TLS и SNI
Когда что-то идёт не так с TLS в HAProxy, чаще всего виноваты:
- Неправильный формат
.pem-файла (не тот порядок блоков, лишние строки). - Отсутствие цепочки (клиент видит self-signed или unknown CA).
- Неправильный SNI (клиент шлёт один hostname, а вы ожидаете другой).
- HAProxy стартует раньше, чем сертификат создан или имеет нужные права.
Для диагностики удобно использовать openssl s_client и указывать нужный SNI:
openssl s_client -connect example.com:443 -servername example.com -showcerts
Смотрите, какой именно сертификат отдаётся, совпадают ли CN/SAN, полна ли цепочка и т.д. Если у вас несколько доменов на одном IP, обязательно проверяйте каждый с правильным -servername, иначе получите «первый попавшийся» сертификат по умолчанию.
Итоги
HAProxy отлично дружит с Let's Encrypt, если заранее продумать схему:
- Определяем, что TLS-терминация живёт в HAProxy, backend'ы получают HTTP.
- Используем HTTP-01 через webroot или DNS-01 для wildcard — TLS-ALPN-01 оставляем для особых случаев.
- Выбираем клиент: Certbot (стабильный и привычный) или acme.sh (лёгкий и очень гибкий).
- Храним сертификаты в виде отдельных
.pemна домен, подключаем черезcrt-baseилиcrt-list. - Автоматизируем склейку и ротацию через deploy-hooks, делая только безопасный reload HAProxy.
- Используем
map-файлы для масштабируемой маршрутизации и пер-доменных политик.
Грамотная интеграция даёт вам десятки (или сотни) доменов на одном HAProxy с полностью автоматическим выпуском и обновлением сертификатов, без ручных правок конфигов при каждом новом домене и без неожиданных даунтаймов из-за истёкшего SSL.


