Nginx чи Caddy чи Traefik: що вибрати для proxy
Порівняння Nginx, Caddy і Traefik для одного VPS: TLS-сертифікати, конфігурація на кожен застосунок, websockets і маршрутизація Docker.
Nginx vs Caddy vs Traefik: коротка відповідь
Nginx, Caddy і Traefik виконують однакову функцію reverse proxy: прослуховують порт 443, читають ім’я хоста в кожному запиті та передають запит потрібному сервісу на вашому VPS. Будь-який із цих трьох інструментів дає змогу розмістити чотири self-hosted застосунки за однією публічною IP-адресою. Усі вони достатньо швидкі, тому повільнішою частиною системи будуть самі застосунки. Відмінності стосуються способу отримання TLS (transport layer security) сертифіката та обсягу конфігурації, потрібного для кожного додаткового застосунку. Інша відмінність проявиться пізніше, коли знадобиться функція, про яку типові tutorials не розповідають.
Виберіть Caddy, якщо хочете автоматично налаштувати HTTPS, а ваші сервіси є звичайними web-застосунками. Виберіть Traefik, якщо все працює в Docker Compose і ви додаєте новий сервіс кожні кілька тижнів. Виберіть Nginx, якщо вже використовуєте його або вам потрібні кешування відповідей, client certificates, пересилання raw TCP чи велика наявна конфігурація, яку ви не хочете переписувати.
Як кожен із них отримує TLS-сертифікат?
Для більшості користувачів саме цей критерій визначає вибір, тому почніть із нього. Усі три зрештою використовують той самий сертифікат від того самого центру сертифікації. Але шлях його отримання відрізняється.
Caddy запитує сертифікат, оскільки ви вказали ім’я хоста. Додайте app.example.com як адресу сайту, і Caddy запитає сертифікат через ACME (automatic certificate management environment) у Let's Encrypt, у разі помилки використає ZeroSSL, налаштує перенаправлення HTTP на HTTPS через порт 80 і самостійно поновлюватиме сертифікат. Додатковий інструмент і таймер для перевірки не потрібні. Сертифікати зберігаються в каталозі даних користувача caddy: /var/lib/caddy/.local/share/caddy під час інсталяції з пакета. Додайте цей шлях до резервних копій або погодьтеся на повторний випуск сертифіката після відновлення сервера. Для непублічного імені хоста Caddy підписує сертифікат власним локальним центром сертифікації: tls internal. У результаті ви отримуєте те саме, що й під час створення самопідписаного сертифіката в Ubuntu, але поновлення виконується автоматично.
Nginx не має ACME-клієнта. Certbot отримує сертифікат, а його плагін --nginx змінює ваш server block, додаючи listener на 443 і перенаправлення. Поновлення виконується через таймер systemd, який встановлює пакет. Тому тут є два компоненти й дві речі, які потрібно перевірити: systemctl list-timers | grep certbot показує, що таймер існує, а sudo certbot renew --dry-run підтверджує, що процедура поновлення все ще працює. Покрокові інструкції наведено в матеріалі Certbot в Ubuntu 24.04 з Nginx. Цей самий інструмент підтримує wildcard-сертифікат через виклик DNS-01, коли субдоменів більше, ніж ви хочете перелічувати окремо.
Traefik має власний ACME-клієнт. Налаштуйте один certificate resolver у статичній конфігурації, після чого кожен router зможе його використовувати. Увесь стан, зокрема ключ облікового запису та сертифікати, зберігається в одному файлі acme.json. Traefik відмовляється використовувати цей файл, якщо його може читати хтось, крім власника, і повідомляє про це перед вимкненням resolver:
The ACME resolver "le" is skipped from the resolvers list because: unable to get ACME account: permissions 660 for /letsencrypt/acme.json are too open, please use 600Змонтуйте каталог і дозвольте Traefik самостійно створити файл. Спочатку створіть його за допомогою touch. Тоді файл успадкує ваш umask, і саме так більшість користувачів виконує цю вимогу.
Для всіх трьох варіантів діє одне правило. Для challenge HTTP-01 порт 80 має бути доступний з інтернету, оскільки центр сертифікації підключається до нього у відповідь. Якщо відкрити лише 443, випуск сертифіката завершиться помилкою, яка виглядає як проблема з DNS.
Одна й та сама маршрутизація для двох застосунків у трьох конфігураціях
Завдання: app.example.com має спрямовуватися до сервісу на 127.0.0.1:8080, а files.example.com — до сервісу на 127.0.0.1:8081. Обидва використовують HTTPS. Нижче наведено повну конфігурацію для кожного проксі, щоб різницю в обсязі було видно, а не лише заявлено.
Nginx
# /etc/nginx/sites-available/app.example.com
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Потім створіть посилання на конфігурацію, перевірте її, перезавантажте Nginx і додайте сертифікат.
sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d app.example.comВиведення syntax is ok і test is successful за допомогою nginx -t — це перевірка, яку потрібно виконувати перед кожним перезавантаженням. Для другого застосунку використовується такий самий блок, але зі зміненими іменем хоста та портом. Рядки proxy_set_header — не формальність: коли proxy_pass указує адресу, nginx за замовчуванням передає Host: 127.0.0.1:8080 на upstream. Тому застосунок, який формує абсолютні URL на основі заголовка Host, надсилатиме користувачів на localhost. Призначення кожного з цих чотирьох заголовків, а також те, чому кінцевий слеш у proxy_pass непомітно змінює шлях, який отримує застосунок, покроково розглянуто в цьому розборі server block nginx.
Caddy
app.example.com {
reverse_proxy 127.0.0.1:8080
}
files.example.com {
reverse_proxy 127.0.0.1:8081
}sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddyЦе весь файл. reverse_proxy самостійно налаштовує X-Forwarded-For, X-Forwarded-Proto і X-Forwarded-Host та за замовчуванням ігнорує значення, які клієнт передав у цих заголовках. Тому запит не може повідомити вашому бекенду неправдиве джерело. Сертифікати, перенаправлення з порту 80 і поновлення сертифікатів випливають із двох адрес сайтів. Інших параметрів для цього у файлі немає.
Traefik
Перед маршрутизацією Traefik потребує статичної конфігурації. Нижче наведено сервіс Compose з актуальним станом image tag станом на August 2026:
services:
traefik:
image: traefik:v3.7
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.le.acme.email=you@example.com"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencryptДалі кожен застосунок отримує власну маршрутизацію через labels у своєму compose-файлі:
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=le"
- "traefik.http.services.app.loadbalancer.server.port=8080"loadbalancer.server.port — це порт усередині контейнера, а не опублікований порт, оскільки Traefik підключається до контейнера через спільну Docker network. Рядок ports: застосунку взагалі не потрібен. У цьому і полягає основна перевага: опублікованим є лише Traefik. Повна конфігурація, зокрема спільна мережа та middleware для перенаправлення, наведена в цьому посібнику з маршрутизації кількох застосунків через Traefik і Docker Compose.
Скільки конфігурації потребує кожен додатковий застосунок?
The data behind this chart
[
{
"tool": "Nginx",
"proxy_setup_lines": 0,
"lines_per_app": 11
},
{
"tool": "Caddy",
"proxy_setup_lines": 0,
"lines_per_app": 3
},
{
"tool": "Traefik",
"proxy_setup_lines": 17,
"lines_per_app": 5
}
]Підрахунок виконано за блоками вище. Блок server в Nginx містить 11 непорожніх рядків, і його потрібно повторювати для кожного імені хоста. Блок site у Caddy містить 3 рядків. Traefik потребує 17 рядків статичної конфігурації, перш ніж зможе обробити хоча б один запит, а потім — 5 labels для кожного застосунку.
Оцінюйте компроміс, а не окремого переможця. Traefik потребує найбільше налаштувань до додавання першого застосунку, але найменше — для кожного наступного. Загальні обсяги конфігурації зрівнюються приблизно на третьому сайті. До цього моменту статична конфігурація є зайвими накладними витратами. Після цього labels дають перевагу й надалі збільшують її, оскільки правила маршрутизації розташовані поруч із сервісом, для якого вони призначені. Видаліть сервіс — і його маршрут також буде видалено. Саме з цим центральний файл конфігурації справляється погано: у ньому залишаються застарілі server blocks для застосунків, які припинили існувати кілька місяців тому.
Кількість рядків також створює сприятливе враження про Nginx. Для кожного з цих блоків потрібні symlink, nginx -t, reload і запуск certbot, тоді як для зміни конфігурації Caddy потрібен один reload, а для зміни Traefik не потрібна жодна команда. Усі три рішення виконують reload без розриву активних з’єднань. Відмінність полягає в кількості окремих кроків, які потрібно пам’ятати о першій годині ночі.
Хто знає про ваші контейнери?
Traefik стежить за Docker socket і створює маршрутизатори на основі labels контейнерів, коли контейнери запускаються та зупиняються. Жоден інший варіант тут цього не робить. Для Nginx і Caddy потрібно змінити конфігурацію та виконати reload, коли з’являється новий контейнер. Також їм потрібна адреса, до якої вони можуть підключитися: або порт, опублікований на loopback, або спільна Docker network, до якої підключений proxy.
Ця функція має ціну, і це потрібно чітко зазначити. Traefik читає /var/run/docker.sock. Будь-хто, хто може підключитися до цього socket, може запустити контейнер із змонтованою файловою системою хоста. Це дає root на хості. Монтування в режимі read only зменшує ризик, але не усуває його. Якщо це важливо для вашої моделі загроз, встановіть між ними socket proxy, який відкриває лише endpoints для отримання списку контейнерів, потрібні Traefik.
Caddy може виконувати discovery на основі labels через сторонній plugin, але plugins Caddy вбудовуються під час компіляції. Тому потрібно створити custom binary або custom image із xcaddy, після чого ви самі відповідаєте за цю збірку та її оновлення. Для трьох або чотирьох сервісів редагувати Caddyfile простіше.
WebSockets і потокова передача: що ламається і чому
Допомоги потребує саме Nginx. WebSocket-з’єднання починається як HTTP-запит із заголовком Upgrade: websocket, а nginx не передає hop-by-hop заголовки upstream, якщо явно не вказати це.
# /etc/nginx/conf.d/upgrade-map.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}Потім у блоці location мають бути присутні всі три рядки:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;Якщо їх пропустити, консоль браузера виведе WebSocket connection to 'wss://app.example.com/ws' failed, а в журналі бекенду з’явиться звичайний GET-запит. Директива map потрібна тому, що жорстко задане Connection: upgrade надсилалося б у кожному запиті, зокрема у звичайних запитах, для яких має бути значення close.
Є ще два параметри Nginx за замовчуванням, які можуть спричинити проблеми. proxy_read_timeout становить 60 секунд і застосовується до тунелю після upgrade, тому WebSocket-з’єднання без трафіку протягом хвилини проксі закриває. Server-sent events надходять із затримкою або пакетами, доки для цієї локації не встановити proxy_buffering off;, оскільки nginx утримує відповідь у буфері, поки сторінка очікує на неї.
Caddy виконує upgrade і без додаткових директив перемикає з’єднання у двонапрямний тунель. Він також одразу передає дані, коли відповідь має значення text/event-stream або її довжина невідома, тому потокова передача працює без додаткових налаштувань. Traefik пропускає upgrade і не буферизує відповіді, якщо ви самостійно не додасте middleware buffering. Якщо ваші сервіси містять чат, web terminal, перегляд кінця журналу або live dashboards, це суттєво впливає на обсяг конфігурації, яку доведеться писати й налагоджувати.
Повний блок сервера Nginx із WebSockets і SSE
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name app.example.com;
client_max_body_size 64m;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_buffering off;
}
}Директива map має бути в контексті http, а не всередині server, тому зберігайте її в окремому файлі під /etc/nginx/conf.d/. Вимикайте proxy_buffering лише для локацій, які передають дані потоком, оскільки буферизація дає nginx змогу раніше звільнити worker-процес бекенду для звичайних відповідей. Certbot переписує цей блок під час запуску, тому після цього знову прочитайте файл.
Що робити, коли потрібна нестандартна функція?
Саме тут Nginx виправдовує додаткові рядки конфігурації.
- Клієнтські сертифікати, також відомі як mTLS (взаємний TLS), коли клієнт також має надати сертифікат. Для Nginx у блоці server потрібні
ssl_client_certificate /etc/ssl/ca.pem;іssl_verify_client on;. У Caddy потрібен блокclient_authусерединіtls. Мітки Traefik взагалі не дають змоги це описати: потрібно визначити параметр TLS у file provider і вказати на нього для router за допомогоюtraefik.http.routers.app.tls.options=mtls@file. Модель, у якій усе налаштовується через мітки, уперше стикається з винятком саме тут. - Великі завантаження. За замовчуванням Nginx обмежує розмір тіла запиту значенням 1 MB. Більше завантаження повертає
413 Request Entity Too Large, а в журналі помилок з’являєтьсяclient intended to send too large body. Збільште значенняclient_max_body_size. Caddy і Traefik за замовчуванням не встановлюють обмеження на тіло запиту, тому запит доходить до вашого застосунку, і обмеження самого застосунку визначає результат. - Кешування відповідей. У Nginx є
proxy_cache, і цей механізм добре розвинений. Для Caddy потрібен скомпільований плагін. У збірці Traefik з відкритим кодом HTTP-кешування взагалі немає. Це дивує користувачів, які вважають, що кожен проксі кешує відповіді. - Необроблений TCP або UDP, наприклад для порту бази даних або ігрового сервера. У Nginx є модуль
stream. У Traefik є TCP- і UDP-router на окремих entrypoint. Для Caddy потрібен інший плагін, а отже, ще одна власна збірка. - Вебсервер уже працює за проксі. Якщо сервіс є класичним PHP-застосунком, то стек LAMP на Ubuntu 24.04 уже містить Apache. Додавання проксі перед ним створює два місця, де можуть встановлюватися заголовки, і два місця, де можна змінювати URL. Визначте, який компонент завершує TLS, а інший залиште на звичайному HTTP, прив’язаному до loopback.
Пастка з firewall, яка виникає через цей вибір
Призначення reverse proxy полягає в тому, що відкритими залишаються лише 80 і 443. Docker непомітно обходить це обмеження. Публікація порту за допомогою -p 8080:80 додає правило DNAT до таблиці nat. Це правило обробляється до правил INPUT, якими керує ufw. Тому ufw deny 8080 не блокує його, і ваш застосунок стає доступним з публічної мережі поруч із proxy, який ви так ретельно налаштували. Прив’яжіть опубліковані порти до loopback за допомогою 127.0.0.1:8080:80 або повністю приберіть ports: і дозвольте proxy підключатися до контейнера через Docker network. Саме так зроблено в наведеному вище прикладі Traefik. Механізм і спосіб виправлення описано в розділі чому опубліковані Docker порти обходять ufw.
Перевірте це з машини, яка не є VPS, оскільки перевірка, виконана безпосередньо на сервері, завжди завершується успішно:
curl --max-time 5 http://your.server.address:8080Результатом має бути Connection refused або timeout. Відповідь HTTP означає, що цей застосунок доступний без проходження через ваш proxy, а все налаштоване вище не забезпечує захисту.
Який проксі-сервер вибрати?
Переважно статичні сайти та один-два застосунки: Caddy. Автоматичний HTTPS усуває найбільшу регулярну рутинну роботу. Конфігурація достатньо коротка, щоб уміститися на одному екрані, а для статичного сайту в одному блоці сайту потрібні рядок root і рядок file_server. Недолік — менше готових відповідей для копіювання, коли щось працює не так.
Домашня лабораторія на docker-compose, до якої ви постійно додаєте сервіси: Traefik. Після третього сервісу працювати з labels простіше, ніж редагувати центральний файл, а видалений сервіс видаляє разом із собою і свій маршрут. На початкове налаштування варто запланувати один день, оскільки entrypoints, routers, services і middlewares будуть для вас новою термінологією. Помилка в label зазвичай проявляється як 404 від Traefik, а не як помилка запуску, тому перед тим, як припустити, що застосунок несправний, перевірте docker logs traefik на наявність помилки розбору.
Наявна конфігурація Nginx або будь-яка вимога з наведеного вище списку: Nginx. Він уже має засоби кешування відповідей і підтримки клієнтських сертифікатів, а майже кожен сторонній посібник передбачає його використання. Недолік у тому, що сертифікати та підтримку websocket потрібно налаштовувати вручну.
Незалежно від вибору діє одне правило. Рівно один процес прослуховує публічний інтерфейс, а всі інші прослуховують loopback або приватну Docker network.
FAQ
Який reverse proxy найкраще підходить для кількох Docker-застосунків на одному VPS?
Для трьох або чотирьох сервісів, які ви час від часу додаєте, Traefik виправдовує себе: кожен застосунок містить власні labels для маршрутизації, і центральний файл не потрібно редагувати. Якщо сервіси стабільні, а ви передусім хочете більше не займатися HTTPS, Caddy простіше вивчити й складніше зламати неправильним налаштуванням. Обирайте Nginx, якщо вже добре його знаєте або якщо потрібна функція, якої немає в інших двох варіантах, наприклад кешування відповідей чи звичайний TCP listener.
Чи справді Caddy не потребує налаштування сертифікатів?
Для типового випадку — так. Достатньо вказати публічне hostname як адресу сайту: Caddy запитує сертифікат через ACME, налаштовує redirect з порту 80 і поновлює сертифікат до завершення строку дії. Водночас мають виконуватися дві умови. Порт 80 має бути доступним з інтернету для challenge HTTP-01, а DNS-запис A або AAAA для hostname вже має вказувати на VPS, оскільки центр сертифікації визначає адресу за цим ім’ям і підключається до неї у відповідь.
Чи можна запускати Nginx і Traefik на одному VPS?
Не на одних і тих самих портах. Той, що запускається другим, не зможе виконати bind, а nginx виведе bind() to 0.0.0.0:443 failed (98: Address already in use); Traefik запише подібну помилку bind у журнал і завершить роботу. Запускайте один proxy на портах 80 і 443, а все інше розміщуйте за ним. Якщо ви виконуєте міграцію, переносіть hostname по одному: нехай front proxy пересилає запити до старого proxy через loopback-порт, доки не буде перенесено останній сайт.
Чому мої websockets розриваються через 60 секунд за Nginx?
proxy_read_timeout за замовчуванням має значення 60 секунд і застосовується до tunnel після завершення upgrade. Тому з’єднання без трафіку протягом хвилини закриває proxy, а не ваш застосунок. Збільште це значення для відповідного location за допомогою proxy_read_timeout 3600s; або налаштуйте застосунок на надсилання ping frame кожні 30 секунд. Caddy і Traefik не закривають idle upgraded connections за таймером в одну хвилину. Тому той самий застосунок може стабільно працювати за Caddy або Traefik і нестабільно — за Nginx.