Как установить HAProxy с Docker Compose на Ubuntu VPS
One web service on a VPS is easy to expose directly — until you want one clean public front door, the freedom to swap the backend later, or a safer way to stop sending traffic to something broken. That is the point where a proxy stops feeling like “something for big infrastructure teams” and starts feeling practical.

HAProxy fits that role well. Think of it as the traffic manager sitting in front of your application: requests hit HAProxy first, and HAProxy decides where they should go next. You do not need a large cluster to benefit from that. Even on one Ubuntu 24.04 VPS, it gives you a cleaner edge between the internet and the service you are actually running.
This guide keeps the first deployment intentionally structured: one Ubuntu 24.04 VPS, Docker Compose, one HAProxy container, one demo backend, and proof that routing really works.
Почему HAProxy важен до того, как он вам понадобится
Представьте небольшой VPS, на котором сегодня отлично работает одно приложение. Оно отвечает на порт, сайт загружается, и всё выглядит хорошо. Проблемы начинаются, когда вам нужна стабильная публичная точка входа, возможность заменить backend позже без изменения публичного адреса или передний слой, который может остановить отправку трафика на неработающий сервис. Прямое открытие приложения начинает казаться ненадёжным на удивление быстро.

Все эти требования указывают на один и тот же недостающий слой: контролируемую точку входа между интернетом и вашим приложением. HAProxy предоставляет этот слой. Клиенты сначала подключаются к HAProxy, а затем HAProxy решает, куда дальше направить каждый запрос.
Это разделение полезно даже до того, как у вас появится несколько серверов. Оно даёт вам более чистый публичный край прямо сейчас и более безопасный путь к последующим изменениям, таким как замена backend, маршрутизация с учётом здоровья сервиса и HTTPS. Остальная часть руководства показывает этот паттерн в его самой простой рабочей форме и проверяет его с помощью реального пути запроса.
Быстрые термины HAProxy, которые облегчат понимание остального руководства

Вам нужен только небольшой набор терминов, чтобы уверенно следовать первому развертыванию HAProxy. Таблица ниже охватывает термины, которые имеют значение в этом руководстве.
| Термин | Значение на простом языке |
|---|---|
| 🌐 reverse proxy | Фронтальный сервис, который получает запросы первым и передает их другому внутреннему сервису. |
| ⚖️ load balancer | Фронтальный уровень, который может распределять запросы между несколькими целевыми бэкендами. |
| 🚪 frontend | Место, где клиенты подключаются к HAProxy. |
| 🧩 backend | Сервис или сервер, к которому HAProxy отправляет запрос дальше. |
| ❤️ health check | Способ для HAProxy заметить, должен ли бэкенд продолжать получать трафик. |
| 🐳 image | Упакованный шаблон приложения, используемый для создания контейнеров. |
| 📦 container | Запущенный экземпляр образа. |
Для этого руководства reverse proxy — это первая ментальная модель, которую нужно иметь в виду. HAProxy находится перед чем-то еще и контролирует передачу. Load balancing — это расширенная возможность, которая становится полезной, когда вы позже добавляете несколько серверов бэкенда.
Два термина, которые имеют наибольшее значение после открытия конфига — это frontend и backend. Frontend — это место, где прибывает клиент. Backend — это место, куда HAProxy отправляет запрос дальше. Health check важен, потому что он позволяет HAProxy заметить, когда цель должна перестать получать трафик.
Для чего хорош HAProxy — и что это руководство намеренно пропускает

Если представить ваш стек как офисное здание, HAProxy — это стойка регистрации: трафик сначала попадает туда, затем направляется в нужный кабинет и перестает отправляться в кабинет, который явно недоступен.
В этом руководстве это переводится в три задачи, релевантные для начинающих:
- принимать входящие HTTP запросы
- перенаправлять их на демо-бэкенд
- контролировать, достаточно ли здоров бэкенд, чтобы продолжать получать трафик
Это уже полезно с одним бэкендом, потому что дает вам один контролируемый публичный край перед приложением.
Позже этот же паттерн масштабируется чисто. Вы можете заменить бэкенд, добавить больше бэкендов, внедрить HTTPS или позволить HAProxy распределять трафик между несколькими целями вместо одной. Чтобы сохранить первый проход доступным для обучения, это руководство остается в режиме HTTP и намеренно пропускает TLS termination, ACLs, rate limiting, stick tables и HA pairs. Это все реальные темы HAProxy. Они просто не являются правильной отправной точкой для первого рабочего развертывания.
Что вы создаете и что вам нужно в первую очередь

Перед созданием файлов полезно увидеть финальную форму стека. Развертывание в этом руководстве выглядит так:
Client browser or curl
|
v
HAProxy frontend (:80)
|
v
demo backend service (demo:5678)
Optional local-only validation:
HAProxy stats frontend (127.0.0.1:8404/stats)Docker Compose — это основной путь здесь, потому что он делает первую установку воспроизводимой, видимой и легко редактируемой. Вместо создания пользовательского образа в первый день вы сохраняете конфиг HAProxy на хосте, монтируете его в контейнер и запускаете весь стек из одного файла. На самоуправляемом Ubuntu VPS — например, на AlexHost VPS — это хорошо подходит, потому что макет остается легко проверяемым.
💡 Совет: Это руководство использует Docker Compose плюс bind-mounted haproxy.cfg намеренно. Это самый прозрачный путь первой установки, потому что вы можете редактировать конфиг прокси напрямую без добавления этапа сборки образа.
Перед началом убедитесь, что у вас есть эти основы:
- Ubuntu 24.04 VPS
- Docker Engine установлен
- Docker Compose v2 доступен через docker compose
- Доступ к терминалу и разрешение на запуск Docker
- Порт 80 доступен на хосте
- Входящий HTTP разрешен, если вы используете UFW или правила брандмауэра на стороне провайдера
Сначала проверьте версию Ubuntu
lsb_release -a
Далее подтвердите, что Docker и современный Compose доступны:
docker --version
docker compose version
Если обе команды возвращают информацию о версии, сторона контейнерного времени выполнения готова, и вы можете сосредоточиться на HAProxy вместо отклонения в установку Docker.
Далее убедитесь, что порт 80 еще не используется, затем проверьте, активен ли UFW и разрешен ли HTTP:
sudo ss -tlnp | grep -E ':(80)s' || true
sudo ufw status
sudo ufw allow 80/tcp
✏️ ПРИМЕЧАНИЕ: Отсутствие вывода из проверки ss обычно означает, что порт 80 свободен. Если вы видите nginx, apache2, caddy или другой сервис, уже прослушивающий его, сначала это исправьте. Это десятисекундный предварительный шаг, который позже сэкономит много путаницы.
В примере выше sudo ufw status показывает Status: active, и 80/tcp уже присутствует в списке разрешений. Вот почему sudo ufw allow 80/tcp возвращает Skipping adding existing rule вместо добавления нового правила. Этот вывод нормален и просто означает, что правило брандмауэра уже было на месте.
Создайте папку проекта и файл Compose
Начните с создания небольшой папки проекта для двух файлов, необходимых для этого первого развертывания:
mkdir -p ~/haproxy-docker
cd ~/haproxy-docker
После этого макет должен быть максимально компактным:
~/haproxy-docker/
├── compose.yaml
└── haproxy.cfgТеперь создайте compose.yaml и используйте это точное содержимое:
services:
demo:
image: hashicorp/http-echo:1.0
command: ["-listen=:5678", "-text=Hello from the HAProxy demo backend"]
restart: unless-stopped
haproxy:
image: haproxy:3.4.1
depends_on:
- demo
ports:
- "80:80"
- "127.0.0.1:8404:8404"
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
sysctls:
net.ipv4.ip_unprivileged_port_start: "0"
restart: unless-stoppedЭтот файл связывает контейнеры вместе, но еще не определяет логику запросов HAProxy. Он сообщает Docker, какие образы запускать, какие порты публиковать и откуда на хосте будет смонтирована конфигурация HAProxy.
Следующие параметры наиболее важны для чистого первого развертывания:
| Параметр Compose | Зачем это здесь |
|---|---|
| hashicorp/http-echo:1.0 | Предоставляет вам крошечный, предсказуемый демо-бэкенд без необходимости одновременно изучать второй веб-сервер. |
| haproxy:3.4.1 | Использует закрепленный стабильный тег вместо latest, что делает руководство менее хрупким с течением времени. |
| depends_on | Запускает сервис demo перед HAProxy, что полезно для порядка первого запуска. |
| 80:80 | Публикует основной HTTP-слушатель на стандартном веб-порту, который ожидают читатели. |
| 127.0.0.1:8404:8404 | Сохраняет страницу статистики доступной для локальной проверки без публичного раскрытия по умолчанию. |
| ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro | Монтирует видимый файл конфигурации на хосте в официальный образ HAProxy как только для чтения. |
| sysctls с net.ipv4.ip_unprivileged_port_start: “0” | Позволяет контейнеру HAProxy без прав root привязываться к низким портам, таким как 80. |
| restart: unless-stopped | Дает вам практический VPS-стандарт: перезагрузка после сбоя или перезагрузки, но уважение к намеренной ручной остановке. |
Еще один важный момент: в этом файле нет пользовательской сети Docker, потому что Docker Compose автоматически создает сеть по умолчанию. Это дает вам DNS с именем сервиса внутри проекта, поэтому HAProxy сможет достичь бэкенда как demo:5678 без дополнительной настройки.
⚠️ Предупреждение: Порт 80 — это привилегированный порт, поэтому строка sysctls не декоративна. Изменение сопоставления хоста на 8080:80 не устраняет требование привилегированного порта внутри контейнера, если HAProxy все еще привязывается к :80 внутри.
Напишите и проверьте минимальный haproxy.cfg
После настройки контейнеров HAProxy все еще нужны инструкции о том, где поступает трафик, куда он должен идти и как проверяется здоровье бэкенда. Создайте haproxy.cfg далее:
global
log stdout format raw local0
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
frontend http
bind :80
default_backend demo_backend
backend demo_backend
balance roundrobin
server demo1 demo:5678 check
frontend stats
bind :8404
stats enable
stats refresh 10s
stats uri /statsЭто минимальная конфигурация, но не одноразовая. log stdout format raw local0 — это удобный для контейнеров выбор логирования, потому что Docker легко может выводить stdout, а mode http в defaults держит весь пример в режиме HTTP, чтобы поведение слушателя и бэкенда оставалось согласованным и читаемым.
✏️ ПРИМЕЧАНИЕ: Одна деталь стоит упомянуть перед разбором раздела: balance roundrobin установлен явно, потому что более новые версии HAProxy изменили алгоритм бэкенда по умолчанию на random, а roundrobin легче преподавать предсказуемо с первого раза.
Вот разбор каждого раздела на простом английском языке:
| Раздел | Ключевые строки | Что это делает |
|---|---|---|
| global | log stdout format raw local0 | Отправляет логи в stdout, чтобы логирование Docker оставалось простым. |
| defaults | mode http, timeouts | Устанавливает базовое поведение HTTP и разумные значения тайм-аутов. |
| frontend http | bind :80, default_backend demo_backend | Создает публичный слушатель и подключает его к определению бэкенда. |
| backend demo_backend | balance roundrobin, server demo1 demo:5678 check | Указывает HAProxy, какой сервис использовать и контролировать его здоровье. |
| frontend stats | bind :8404, stats enable, stats uri /stats | Добавляет опциональную локальную страницу проверки, чтобы позже увидеть статус выполнения. |
Вы можете заметить одно отсутствующее: option forwardfor. Это пропуск намеренный на базовом уровне. Сохранение исходного IP клиента полезно позже, но это первое развертывание — о доказательстве маршрутизации и здоровья бэкенда, а не о преподавании поведения заголовков с демо-контейнером, который не делает этот сигнал особенно ценным.
💡 Совет: Всегда проверяйте конфигурацию HAProxy перед запуском полного стека. Поскольку эта конфигурация ссылается на бэкенд по имени сервиса Compose (demo), сначала запустите этот бэкенд, чтобы HAProxy мог разрешить его во время проверки.
Запустите проверку из того же каталога проекта:
docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg
Если вторая команда заканчивается на Configuration file is valid, вы уже доказали, что HAProxy может правильно разобрать файл и разрешить целевой бэкенд перед запуском любого живого слушателя.
Запустите Stack и докажите, что Proxy работает
После проверки конфигурации запустите stack в отсоединенном режиме:
Поскольку этап валидации уже запустил demo, эта команда в основном поднимает HAProxy и согласует полный двухсервисный stack:
docker compose up -d
Затем проверьте, живы ли оба контейнера:
docker compose ps
Это представление процесса — только первая контрольная точка. Оно подтверждает, что Docker запустил контейнеры, но еще не то, что HAProxy успешно маршрутизирует трафик на backend. Следующий запрос проверяет фактический путь данных.
Теперь запустите фактический тест маршрутизации с самого VPS:
curl -i http://127.0.0.1
Сигнал успеха — это HTTP/1.1 200 OK плюс тело ответа, содержащее Hello from the HAProxy demo backend. Некоторые сборки http-echo оборачивают этот текст в небольшой HTML-ответ, поэтому сосредоточьтесь на фразе в теле больше, чем на точном форматировании.
Если вам нужно доказательство на уровне браузера, откройте http://YOUR_SERVER_IP с другой машины.

Для второй поверхности валидации проверьте локальную страницу статистики с VPS:
curl http://127.0.0.1:8404/statsНа странице статистики наиболее полезные сигналы — это frontend с именем http, backend с именем demo_backend, строка сервера с именем demo1, статус, показанный как UP, и обычно значение последней проверки, такое как L4OK in 0ms. Также помните об одной небольшой особенности Docker: краткий синтаксис depends_on управляет порядком запуска, но не ждет, пока сервис станет здоровым. Если самый первый curl один раз не срабатывает сразу после запуска, подождите несколько секунд и попробуйте снова, прежде чем предполагать, что конфигурация неправильная.
Разницу между состоянием процесса и реальным успехом легче понять в табличной форме:
| Состояние | Что это вам говорит |
|---|---|
| Контейнеры работают | Docker запустил процессы. |
| curl -i http://127.0.0.1 возвращает 200 OK и фразу demo | HAProxy фактически маршрутизирует трафик на backend. |
| Страница статистики показывает demo1 как UP | HAProxy видит backend как здоровый. |
Распространённые ошибки при первом запуске и быстрые исправления

Если настройка не работает сразу, сопротивляйтесь желанию переписать оба файла одновременно. Большинство сбоев при первом запуске на этом стеке предсказуемы, и их становится намного легче исправить, когда вы меняете одну переменную за раз.
Используйте эту матрицу как слой быстрой диагностики:
HAProxy выходит немедленно
Вероятная причина: haproxy.cfg отсутствует.
Быстрое исправление: Убедитесь, что haproxy.cfg находится рядом с compose.yaml.
Почему это происходит: Официальный образ не поставляется с готовой конфигурацией.
Ошибка говорит, что не может открыть /usr/local/etc/haproxy/haproxy.cfg
Вероятная причина: Неправильный путь bind-mount.
Быстрое исправление: Проверьте ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro точно.
Почему это происходит: HAProxy не может запуститься без действительного файла конфигурации.
Ошибка говорит Permission denied на порту 80
Вероятная причина: Проблема с привязкой привилегированного порта.
Быстрое исправление: Оставьте net.ipv4.ip_unprivileged_port_start: “0” в Compose или переместите HAProxy и опубликованный порт на 8080.
Почему это происходит: Контейнер работает от непривилегированного пользователя haproxy.
Вы изменили маппинг на 8080:80 и всё ещё получаете ошибку привязки
Вероятная причина: HAProxy всё ещё привязывается к :80 внутри контейнера.
Быстрое исправление: Измените как маппинг хоста, так и внутреннюю строку bind, если вы переходите с порта 80.
Почему это происходит: Правило привилегированного порта применяется и внутри контейнера.
Порт 80 уже используется
Вероятная причина: Другой сервис занимает порт хоста.
Быстрое исправление: Повторно запустите проверку ss и остановите или переместите конфликтующий сервис.
Почему это происходит: Только один процесс может прослушивать один и тот же порт хоста.
Проверка синтаксиса сообщает об unknown keyword или ошибках, специфичных для строки
Вероятная причина: Опечатка в конфигурации HAProxy.
Быстрое исправление: Повторно запустите проверку синтаксиса и исправьте точную строку, которую она указывает.
Почему это происходит: Парсер HAProxy строг, что полезно, когда вы используете его намеренно.
Контейнеры работают, но curl не возвращает демо-ответ
Вероятная причина: Неправильный путь маршрутизации.
Быстрое исправление: Перепроверьте default_backend demo_backend, server demo1 demo:5678 check и имя сервиса demo.
Почему это происходит: Работающий контейнер не является доказательством правильного пути от фронтенда к бэкенду.
Локальный curl работает, но сайт недоступен извне
Вероятная причина: Брандмауэр или правило безопасности провайдера.
Быстрое исправление: Откройте порт 80 в UFW и в любом брандмауэре на стороне провайдера.
Почему это происходит: Локальная публикация может работать даже когда публичный доступ всё ещё заблокирован.
⚠️ Предупреждение: Меняйте по одному элементу за раз. Если вы вслепую отредактируете и compose.yaml, и haproxy.cfg, вам будет намного сложнее определить, является ли сбой проблемой пути файла, проблемой порта или проблемой маршрутизации.
Когда вам нужны быстрые доказательства, держите эти команды под рукой:
docker compose logs haproxy
docker compose ps
docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg
sudo ss -tlnp | grep -E ':(80|8404)s' || trueЭто три паттерна оповещений, которые стоит узнавать с первого взгляда:
[ALERT] ... Cannot open configuration file /usr/local/etc/haproxy/haproxy.cfg : No such file or directory
[ALERT] ... Starting frontend http: cannot bind socket (Permission denied) [0.0.0.0:80]
[ALERT] ... parsing [/usr/local/etc/haproxy/haproxy.cfg:12] : unknown keyword 'chekc'; did you mean 'check' maybe?Это обнадёживающая часть небольшого первого развёртывания: формы сбоев обычно тоже небольшие. Вам не нужно начинать заново. Вам нужно определить, какой слой жалуется, и сначала исправить именно этот элемент.
Куда идти после установки
Как только демонстрация с одним бэкендом работает, архитектура уже полезна. Следующий реальный шаг — заменить демонстрационный контейнер вашим фактическим приложением, сохраняя при этом ту же структуру HAProxy. После этого добавьте HTTPS/TLS как отдельный этап, и рассматривайте маршрутизацию на основе домена и ACL как отдельные темы, а не спешите включать их в эту первую установку.

Когда вы будете готовы к более чем одному бэкенду, один и тот же паттерн становится явно значимым:
backend app_backend
balance roundrobin
server app1 app1:8080 check
server app2 app2:8080 checkДля безопасного редактирования конфигурации с bind-mount сначала проверьте, а затем перезагрузите HAProxy корректно:
docker compose up -d demo
docker compose run --rm --no-deps haproxy haproxy -V -c -f /usr/local/etc/haproxy/haproxy.cfg
docker compose kill -s HUP haproxy📝 Примечание: Страница статистики намеренно локальная в этом руководстве. Если вы когда-либо откроете её публично, сначала добавьте аутентификацию и элементы управления доступом.
Это возвращает вас к исходной проблеме: вам нужна одна чистая входная дверь перед сервисом, без превращения первой установки в полноценный проект операций. Теперь у вас есть рабочий путь. Что ещё более важно, у вас также есть правильная ментальная модель: HAProxy получает трафик первым, перенаправляет его туда, где он должен быть, и дает вам более чистый способ расширить стек на самоуправляемом VPS без потери контроля над конфигурацией.
на всех хостинговых услугах