Введение
У меня один физический сервер с Proxmox, на котором крутится несколько виртуалок и LXC-контейнеров.
Там-же я запускаю и разные тестовые продукты. У меня есть мания что даже тестовую систему хочется сразу вывести на поддомен\домен и прикрутить к ней сертификат Let’s Encrypt.
От сюда появилась задача поднять реверс-прокси, который было бы не сложно настраивать под новое условие, при этом хотелось бы без лишней возни передавать реальный IP клиента. Так же стояла задача чтобы было ограничение доступа по IP, для систем которые пока не готовы быть открыты в мир, но нужны мне с действующим сертификатом.
Для большинства проектов, даже в официальной документации ставится NGINX, сначала я подумал на него, но в итоге покопавшись в сети и вспомнив свой опыт с pfSense, решил остановиться на HAProxy.
⚠️ Важно: конфигурация изложенная ниже не поддерживает wildcard-сертификаты. Каждый сайт должен иметь свой собственный сертификат.
1. Архитектура
- Proxmox — основная платформа виртуализации.
- LXC-контейнер с Debian 12 — здесь крутится HAProxy, слушает порты 80 и 443.
- Backend-сервера (виртуалки) — на них установлены веб-сервера с собственными сертификатами.
- Все виртуалки находятся в одной внутренней сети, например
192.168.1.0/24.
2. Установка HAProxy
В LXC-контейнере с Debian 12 устанавливаем HAProxy:
sudo apt update
sudo apt install haproxy -y
3. Базовая конфигурация
Создаём или редактируем /etc/haproxy/haproxy.cfg
# Глобальные настройки
global
log /dev/log local0
stats socket /run/haproxy/admin.sock mode 660 level admin
daemon
maxconn 10000
nbthread 2
tune.ssl.default-dh-param 2048
# Настройки по умолчанию
defaults
log global
mode http
option dontlognull
timeout connect 10s
timeout client 5m
timeout server 5m
# Frontend для HTTPS
# Мы делаем упор на то что у каждой машины свой сертифика и при установки соединения сверяем домен сертификата и запрошеный
frontend HTTPS
bind 0.0.0.0:443
mode tcp
tcp-request inspect-delay 25s
tcp-request content accept if { req.ssl_hello_type 1 }
use_backend HTTPS
backend HTTPS
mode tcp
option tcp-check
# ACL для develop-доменов с ограничением по IP
# в файле allow_list.txt мы храним списко IP с который разрешён вход
acl safe_ip src -n -f /etc/haproxy/allow_list.txt
tcp-request content reject if { req.ssl_sni -i site2.ru } !safe_ip
tcp-request content reject if { req.ssl_sni -i site3.ru } !safe_ip
# Динамическое сопоставление по имени хоста
use-server %[req.ssl_sni] if { req.ssl_sni -m found }
server site1.ru 192.168.1.11:443
server site2.ru 192.168.1.12:443
server site3.ru 192.168.1.13:443
# Frontend для HTTP (редирект на HTTPS)
# В отличии от HTTPS мы можем напрямую получать домен из запроса, так мы можем отдавать правильный сервер без каких-либо проблем
frontend HTTP
bind 0.0.0.0:80
mode http
option forwardfor
http-request redirect scheme https unless { path_beg /.well-known/acme-challenge }
use_backend HTTP if { path_beg /.well-known/acme-challenge }
backend HTTP
mode http
http-request set-var(txn.txnhost) hdr(host)
# Динамическое сопоставление по имени хоста
use-server %[var(txn.txnhost)] if { var(txn.txnhost) -m found }
server site1.ru 192.168.1.11:80
server site2.ru 192.168.1.12:80
server site3.ru 192.168.1.13:80
На этом этапе HAProxy принимает входящие соединения, распределяет трафик по доменам через SNI и ограничивает доступ к develop-сайтам по IP.
4. Передача реального IP клиента
По умолчанию backend-сайты видят IP самого LXC-контейнера.
Чтобы они получали настоящий адрес клиента, добавляем в секцию backend HTTPS строку: source 0.0.0.0 usesrc clientip
Теперь секция выглядит так:
backend HTTPS
mode tcp
option tcp-check
source 0.0.0.0 usesrc clientip
acl safe_ip src -n -f /etc/haproxy/allow_list.txt
tcp-request content reject if { req.ssl_sni -i site2.ru } !safe_ip
tcp-request content reject if { req.ssl_sni -i site3.ru } !safe_ip
use-server %[req.ssl_sni] if { req.ssl_sni -m found }
server site1.ru 192.168.1.11:443
server site2.ru 192.168.1.12:443
server site3.ru 192.168.1.13:443
5. Сетевые нюансы
При включённом source 0.0.0.0 usesrc clientip HAProxy подставляет IP клиента как исходный адрес.
Чтобы такие пакеты корректно проходили, нужно:
- чтобы все backend-сервера использовали контейнер с HAProxy в качестве шлюза по умолчанию;
- чтобы сам контейнер мог маршрутизировать трафик обратно во внешнюю сеть.
⚠️ Иначе ответы не вернутся по правильному пути, и соединения будут рваться.
Также в этом режиме локальные клиенты (из той же подсети) не смогут обращаться к сайтам через HAProxy, потому что подмена исходного IP нарушает локальную маршрутизацию.
6. Настройка iptables и маршрутизации
В LXC-контейнере создаём правила для таблицы mangle, которые обеспечивают обратную маршрутизацию пакетов:
sudo iptables -t mangle -N DIVERT
sudo iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT
sudo iptables -t mangle -A PREROUTING -p udp -m socket -j DIVERT
sudo iptables -t mangle -A DIVERT -j MARK --set-mark 1
sudo iptables -t mangle -A DIVERT -j ACCEPT
sudo ip rule add fwmark 1 lookup 100
sudo ip route add local 0.0.0.0/0 dev lo table 100
Эти команды создают отдельную таблицу маршрутов 100 и направляют туда помеченные пакеты, чтобы ядро разрешало отправку с адреса клиента.
7. Автоматизация маршрутов через systemd
Чтобы после перезагрузки не прописывать маршруты вручную, создаём сервис:
sudo vi /etc/systemd/system/01-static-route.service
[Unit]
Description=Add route table 100
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=-/usr/sbin/ip route add local 0.0.0.0/0 dev lo table 100
ExecStart=-/usr/sbin/ip rule add fwmark 1 lookup 100
[Install]
WantedBy=multi-user.target
Активируем:
sudo systemctl daemon-reload
sudo systemctl enable —now 01-static-route.service
8. Ограничение доступа к develop-доменам
Файл /etc/haproxy/allow_list.txt содержит IP-адреса, которым разрешён доступ:
192.168.1.0/24
10.0.0.0/8
203.0.113.55
HAProxy проверяет этот список и блокирует все остальные подключения к закрытым сайтам.
9. Итог
Теперь вся схема работает стабильно:
✅ HAProxy в LXC принимает весь трафик снаружи.
✅ Каждый backend-сайт использует свой Let’s Encrypt сертификат.
✅ Реальный IP клиента прокидывается до backend-сервера.
✅ develop-домены доступны только с разрешённых IP.
✅ Контейнер с HAProxy выполняет роль шлюза для внутренних серверов.
Если отключить
source 0.0.0.0 usesrc clientip, то iptables и шлюз можно не настраивать —
но backend-сайты будут видеть IP контейнера, а не клиента.
Добавить комментарий