Привет, Хабр! На связи Алексей Ежков из из Cloud4Y. Один внешний IPv4, десятки пользователей Exchange и растущий трафик портала — звучит как головоломка? В этой статье я покажу, как мы решили её, заведя всё хозяйство за единственным IP и обеспечив максимальную защиту.

Допустим, в компании «Фирмачка» (доменное имя — firmachka.pro) количество пользователей Exchange Server 2019 перевалило за сотню, а трафик к двум фронтендам корпоративного сайта вырос после маркетинговой кампании. У провайдера — только один публичный IPv4.
Требуется:
Один внешний IP-адрес.
SNI-маршрутизация нескольких DNS-имён.
SSL-offload (TLS 1.2 / 1.3).
Защита от DoS, health-checks, отказоустойчивость для DAG-кластера Exchange и двух веб-узлов.
DNS-записи и сама DAG-инфраструктура настроены корректно и в данной статье не обсуждаются.
Перед началом убедитесь, что:
Все необходимые DNS-записи (A-записи для mail, autodiscover, portal, lb) указывают на публичный IP-адрес HAProxy.
Порты 25, 80, 443 открыты на файрволе до HAProxy.
Exchange DAG и веб-портал настроены и работают корректно во внутренней сети.
У вас есть sudo-доступ к серверу с HAProxy.
2. Топология и окружение

HAProxy ≥ 2.4 из backports/debian-bookworm.
Exchange Server 2019 CU13 (две ноды DAG).
Портал на IIS 10 / ASP.NET Core.
3. Откуда берутся файлы *.pem в /etc/ssl/private
3.1. Получаем wildcard-сертификат
Сертификат нужен один — *.
firmachka.pro
, чтобы покрыть mail
, autodiscover
, portal
, lb
и любые будущие имена.
ACME-клиент выпускает wildcard через DNS-01-валидацию:
DOMAIN="firmachka.pro"
CERT_DIR="/etc/ssl/private"
certbot certonly \
--agree-tos \
--manual \
--preferred-challenges dns \
-d "*.${DOMAIN}" -d "${DOMAIN}" \
--manual-public-ip-logging-ok
# склейка в формат HAProxy
cat /etc/letsencrypt/live/${DOMAIN}/privkey.pem \
/etc/letsencrypt/live/${DOMAIN}/fullchain.pem \
> ${CERT_DIR}/wildcard.${DOMAIN}.pem
chmod 600 ${CERT_DIR}/wildcard.${DOMAIN}.pem
Для автоматического продления wildcard-сертификатов через DNS-01 челлендж обычно используются DNS-плагины Certbot
3.2. DH-параметры
openssl dhparam -out /etc/haproxy/certs/dhparam.pem 2048
Файл подключается директивой ssl-dh-param-file
. Без собственных DH-параметров часть сканеров ругается на «weak prime reuse».
После автоматического продления сертификата выполняйте
systemctl reload haproxy
, иначе HAProxy продолжит отдавать старую цепочку
4. Полный haproxy.cfg
4.1. global
global
log /dev/log local0 notice
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
user haproxy
group haproxy
daemon
maxconn 20000
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets prefer-client-ciphers
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384
ssl-default-bind-ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384
ssl-dh-param-file /etc/haproxy/certs/dhparam.pem
tune.ssl.default-dh-param 2048
Пояснения построчно:
log /dev/log local0 notice
— журналируем в rsyslog; уровень notice экономит диск.stats socket
— пригодится для Runtime API и атомарной перезагрузки.maxconn 20000
— лимит по памяти: примерно 50 КБ/conn ⇒ ≈1 ГБ RSS.ssl-default-bind-options
— отключаем TLS 1.0/1.1, запрещаем session tickets (защита от атак resumption-tracking), навязываем клиенту наш порядок шифров.ssl-default-bind-ciphersuites
— suite-ы для TLS 1.3.tune.ssl.default-dh-param
— fallback-значение, еслиssl-dh-param-file
недоступен.
На заметку
Частая ошибка — оставитьssl-default-bind-options
безprefer-client-ciphers
; тогда старые браузеры выберут RC4.
4.2. defaults
defaults
mode http
log global
option httplog
option dontlognull
option forwardfor
option redispatch
option http-server-close
retries 3
http-request set-header X-Forwarded-Proto https if { ssl_fc }
timeout connect 10s
timeout client 300s
timeout server 300s
errorfile 403 /etc/haproxy/errors/403.http
errorfile 503 /etc/haproxy/errors/503.http
Ключевые моменты:
option http-server-close
— клиенту keep-alive, а на внутренние сервера соединение закрываем после ответа; экономия на TIME_WAIT.forwardfor
+set-header X-Forwarded-Proto
— OWA корректно формирует ссылки вида https://.Таймауты 300 s подобраны под длительные загрузки вложений в OWA.
На заметку
Забытыйoption httpclose
ведёт к обрыву крупных загрузок через OWA/OWA Light.
4.3. frontend ft_http — порт 80
frontend ft_http
bind *:80
mode http
acl host_portal hdr(host) -i portal.firmachka.pro
use_backend be_portal if host_portal
redirect scheme https code 301 if !host_portal
Разбираем:
Портал доступен и по HTTP :80 (SEO-требование).
Всё остальное принудительно уходит на HTTPS (301).
4.4. frontend ft_https — порт 443
frontend ft_https
bind *:443 ssl crt /etc/ssl/private/wildcard.firmachka.pro.pem
mode http
http-response set-header X-Frame-Options SAMEORIGIN
http-response set-header X-Content-Type-Options nosniff
http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains"
# Защита от DoS
stick-table type ip size 200k expire 30s store http_req_rate(10s)
http-request track-sc0 src
http-request deny status 429 if { sc_http_req_rate(0) gt 100 }
# ---------- ACL ----------
acl host_portal hdr(host) -i portal.firmachka.pro
acl host_autodiscover hdr(host) -i autodiscover.firmachka.pro
acl url_ecp path_beg -i /ecp
# !!! ВАЖНО: Укажите вашу административную подсеть !!!
acl admin_network src 192.168.20.0/24
http-request deny if url_ecp !admin_network
# ---------- Маршрутизация ----------
use_backend be_portal_https if host_portal
use_backend be_exchange_unified if host_autodiscover
default_backend be_exchange_unified
Пояснения:
Один wildcard-сертификат обслуживает все имена.
autodiscover.firmachka.pro
направляется в тот же backend, что иmail
.Security-заголовки убирают mixed-content и click-jacking.
stick-table
фильтрует > 100 запросов/10 s ⇒ HTTP 429 Too Many Requests.Защита ECP: доступ только из админской подсети. Exchange не умеет ограничивать сам.
На заметку
• Таблица 200 k на 30 s ≈ 14 MB RAM. Статус в Runtime API:show table ft_https
.
• Если нужен IPv6, заведите отдельную stick-таблицу type ipv6.
4.5. frontend ft_smtp — порт 25
frontend ft_smtp
bind *:25
mode tcp
# Ограничение скорости подключений
stick-table type ip size 50k expire 1m store conn_rate(10s)
tcp-request connection track-sc0 src
tcp-request connection reject if { sc_conn_rate(0) gt 10 }
default_backend be_smtp
Layer 4 proxy, никаких STARTTLS-offload; Exchange сам отдаёт сертификат.
10 conns / 10 s → reject (борьба с ботнетами, не влияя на легитимный MX-трафик).
На заметку
Серые листы (greylisting) любят быстро пересоединяться. Подберите лимит под вашу почтовую политику.
4.6. backend be_portal
backend be_portal
mode http
balance roundrobin
option httpchk GET /
http-check expect status 200
server portal01 192.168.20.10:80 check inter 3s rise 2 fall 3
server portal02 192.168.20.11:80 check inter 3s rise 2 fall 3
rise 2 / fall 3
— узел объявляется «здоровым» после двух успешных чеков и «больным» после трёх неудач.Схема round-robin подходит: сессии портала хранятся в Redis-кластер, нет state-stickiness.
4.7. backend be_portal_https
backend be_portal_https
mode http
balance roundrobin
option httpchk GET /
http-check expect status 200
server portal01 192.168.20.10:443 ssl verify none check inter 3s rise 2 fall 3
server portal02 192.168.20.11:443 ssl verify none check inter 3s rise 2 fall 3
Внутренний трафик также шифруется;
verify none
допустим, так как это изолированная VLAN.Если у портала собственный internal-CA, замените на
verify required ca-file /etc/ssl/certs/ca_portal.pem
.
4.8. backend be_exchange_unified
backend be_exchange_unified
mode http
balance source
option httpchk GET /owa/healthcheck.htm
http-check expect status 200
server ex01 192.168.0.100:443 ssl verify none check inter 3s rise 2 fall 3
server ex02 192.168.0.101:443 ssl verify none check inter 3s rise 2 fall 3
Почему balance source
?
Exchange клеит сессию OWA к конкретному ClientAccessServer
. Sticky-cookie тоже решит задачу, но source-hash проще и не ломает ActiveSync с legacy-устройств.
healthcheck.htm
— официальный endpoint; возвращает «200 OK Healthy» без аутентификации.
На заметку
После CU-обновления Microsoft иногда меняет ответ с «Healthy» на «OK». Ловите 503? Сравните тело ответа вcurl -k
.
4.10. backend be_smtp
backend be_smtp
mode tcp
balance roundrobin
option smtpchk EHLO firmachka.pro
server ex01 192.168.0.100:25 check
server ex02 192.168.0.101:25 check
option smtpchk
посылает EHLO и ждёт кода 250. Это держит порт 25 в «green» даже при частичном падении Exchange.
4.11. listen stats
listen stats
bind *:8404
stats enable
stats uri /stats
stats realm "HAProxy Statistics"
stats auth admin:ChangeMeNow!
stats refresh 30s
Веб-панель по HTTP (не HTTPS). Ограничьте внешним файрволом!
stats socket
из блока global позволяет вместо веб-GUI пользоватьсяsocat /run/haproxy/admin.sock -
и собирать метрики в Prometheus-exporter.
5. Безопасность и производительность
TLS 1.3 + строгий список шифров, SessionTickets отключены.
Один wildcard-сертификат упрощает сопровождение и закрывает все поддомены.
stick-table фильтрует 100 req/10 s или 10 conn/10 s на SMTP.
maxconn 20000
+ Linux 5.10 (SO_REUSEPORT) дают 5–6 Гбит/с без CPU-узких мест. Опция SO_REUSEPORT позволяет HAProxy эффективнее использовать несколько ядер CPU, распределяя входящие соединения между несколькими рабочими процессами (worker processes) HAProxy, если они запущены.Health-checks раз в 3 s обеспечивают SLA ≥ 99.9 % без избыточного сетевого шума.
6. Тестирование
haproxy -c -f /etc/haproxy/haproxy.cfg # проверка синтаксиса
curl -k https://lb.firmachka.pro/owa/healthcheck.htm # Проверяем доступность и работоспособность Health Check страницы Exchange OWA
curl -k https://autodiscover.firmachka.pro/autodiscover/autodiscover.xml -H "Content-Type:text/xml" # Имитируем запрос к службе Autodiscover Exchange через балансировщик
swaks --to recipient@example.com --from sender@firmachka.pro --server mail.firmachka.pro --port 25 -ehlo test # Тестирование работы SMTP-сервера
openssl s_client -connect lb.firmachka.pro:443 -servername portal.firmachka.pro \ # Получаем информацию о выбранной версии TLS и наборе шифров
-tlsextdebug -status | grep -E 'Selected|TLSv'
На заметку
Еслиopenssl
сообщаетNew, TLSv1.2
, а неTLSv1.3
, проверьте версию OpenSSL на клиенте.
7. Эксплуатация
Горячий reload:
haproxy -f /etc/haproxy/haproxy.cfg -c && systemctl reload haproxy
.Runtime-API:
echo "show table ft_https" | socat stdio /run/haproxy/admin.sock
.Мониторинг:
github.com/prometheus/haproxy_exporter
читаетstats socket
.
8. Частые проблемы
Симптом | Причина | Решение |
---|---|---|
503 на OWA после продления wildcard-серта | забыли |
|
401/403 при входе в /ecp | ACL | скорректировать CIDR |
Серый список SMTP режет доставку | stick-table слишком строг | увеличить лимит conn_rate |
9. Итог
Мы получили:
Wildcard-сертификат
*.
firmachka.pro
, покрывающий все сервисы.SNI-маршрутизацию четырёх DNS-имён (
mail
,autodiscover
,portal
,lb
) на одном IP.SSL-offload + TLS 1.3 без компромиссов в безопасности.
Отказоустойчивые Exchange 2019 DAG и веб-кластер за одним фронтом.
Базовую DoS-защиту и удобный мониторинг без лишнего железа.
Официальная документация для углубления:
«Load Balancing in Exchange Server»: https://learn.microsoft.com/en-us/exchange/architecture/client-access/load-balancing
Руководство по security-headers: https://owasp.org/www-project-secure-headers/