Привет, Хабр! На связи Алексей Ежков из из 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/
ссылка на оригинал статьи https://habr.com/ru/articles/933070/
Добавить комментарий