Как установить 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, на котором сегодня отлично работает одно приложение. Оно отвечает на порт, сайт загружается, и все выглядит хорошо. Проблемы начинаются, когда вам нужна стабильная публичная точка входа, возможность заменить бэкенд позже без изменения публичного адреса или фронтальный слой, который может остановить отправку трафика на неработающий сервис. Прямое открытие приложения начинает казаться хрупким удивительно быстро.

Все эти требования указывают на один отсутствующий слой: контролируемую точку входа между интернетом и вашим приложением. HAProxy предоставляет этот слой. Клиенты сначала подключаются к HAProxy, а затем HAProxy решает, куда дальше направить каждый запрос.
Это разделение полезно даже до того, как у вас появится несколько серверов. Оно дает вам более чистый публичный край сейчас и более безопасный путь к последующим изменениям, таким как замена бэкенда, маршрутизация с учетом здоровья и 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 пары. Это все реальные темы 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 без привилегий привязываться к низким портам, таким как 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 может правильно разобрать файл и разрешить целевой бэкенд перед запуском любого активного слушателя.
Запустите стек и докажите, что прокси работает
После проверки конфигурации запустите стек в режиме отсоединения:
Поскольку шаг проверки уже запустил demo, эта команда в основном запускает HAProxy и согласовывает полный двухсервисный стек:
docker compose up -d
Затем проверьте, живы ли оба контейнера:
docker compose ps
Это представление процесса — только первая контрольная точка. Оно подтверждает, что Docker запустил контейнеры, но еще не то, что HAProxy успешно маршрутизирует трафик на бэкенд. Следующий запрос проверяет фактический путь данных.
Теперь запустите фактический тест маршрутизации с самого 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На странице статистики наиболее полезные сигналы — это фронтенд с именем http, бэкенд с именем demo_backend, строка сервера с именем demo1, статус показан как UP, и обычно значение последней проверки, такое как L4OK in 0ms. Также имейте в виду одну небольшую особенность Docker: краткий синтаксис depends_on управляет порядком запуска, но не ждет, пока сервис станет здоровым. Если самый первый curl один раз не пройдет сразу после запуска, подождите несколько секунд и попробуйте снова, прежде чем предполагать, что конфигурация неправильна.
Разницу между состоянием процесса и реальным успехом легче понять в табличной форме:
| Состояние | Что это вам говорит |
|---|---|
| Контейнеры работают | Docker запустил процессы. |
| curl -i http://127.0.0.1 возвращает 200 OK и демо-фразу | HAProxy фактически маршрутизирует трафик на бэкенд. |
| Страница статистики показывает demo1 как UP | HAProxy видит бэкенд как здоровый. |
8. Распространённые ошибки при первом запуске и быстрые исправления

Если настройка не работает сразу, не поддавайтесь искушению переписать оба файла одновременно. Большинство сбоев при первом запуске на этом стеке предсказуемы, и их намного легче исправить, если менять по одной переменной за раз.
Используйте эту матрицу как быстрый уровень диагностики:
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 без потери контроля над конфигурацией.
на всех хостинговых услугах