Як встановити 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 все ще потребує інструкцій щодо того, де надходить трафік, куди він повинен йти та як перевіряється здоров’я backend. Створіть 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, тому поведінка listener та backend залишаються послідовними та читаними.
✏️ ПРИМІТКА: Одна деталь варта уваги перед розбором розділу: balance roundrobin встановлюється явно, оскільки новіші версії HAProxy змінили алгоритм backend за замовчуванням на random, а roundrobin легше навчати передбачувано з першого разу.
Ось простий розбір кожного розділу:
| Розділ | Ключові рядки | Що він робить |
|---|---|---|
| global | log stdout format raw local0 | Надсилає журнали до stdout, щоб логування Docker залишалося простим. |
| defaults | mode http, timeouts | Встановлює базову поведінку HTTP та розумні значення timeout. |
| frontend http | bind :80, default_backend demo_backend | Створює публічний listener та підключає його до визначення backend. |
| backend demo_backend | balance roundrobin, server demo1 demo:5678 check | Повідомляє HAProxy, який сервіс використовувати та моніторити його здоров’я. |
| frontend stats | bind :8404, stats enable, stats uri /stats | Додає необов’язкову локальну сторінку перевірки, щоб ви могли бачити статус під час виконання пізніше. |
Ви можете помітити одну відсутню річ: option forwardfor. Це пропущення навмисне в базовому шляху. Збереження оригінальної IP-адреси клієнта корисно пізніше, але це перше розгортання стосується доведення маршрутизації та здоров’я backend, а не навчання поведінці заголовків із демо-контейнером, який не робить цей сигнал особливо цінним.
💡 Порада: Завжди перевіряйте конфігурацію HAProxy перед запуском повного стека. Оскільки ця конфігурація посилається на backend за його назвою сервісу Compose (demo), спочатку запустіть цей backend, щоб 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 може правильно розпарсити файл та розпізнати ціль backend перед запуском будь-якого активного listener.
Запустіть Stack і доведіть, що Proxy працює
Після валідації конфігурації запустіть stack у відокремленому режимі:
Оскільки крок валідації вже запустив demo, ця команда в основному запускає HAProxy та узгоджує повний стек з двома сервісами:
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, спочатку перевірте, а потім плавно перезавантажте 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 без втрати контролю над конфігурацією.
на всіх хостингових послугах