Nginx, Caddy чи Traefik: що вибрати для proxy
Порівняння Nginx, Caddy і Traefik для одного VPS та public IP: TLS-сертифікати, конфігурація сервісів, WebSocket і маршрутизація Docker.
Nginx проти Caddy проти Traefik: коротка відповідь
Nginx, Caddy і Traefik виконують однакову роботу як reverse proxy: приймають з’єднання на порту 443, читають ім’я хоста в кожному запиті та передають запит потрібному сервісу на вашому VPS. Будь-який із цих трьох варіантів дасть змогу розмістити чотири self-hosted застосунки за однією публічною IP-адресою, а всі вони достатньо швидкі, тому повільною частиною будуть ваші застосунки. Відмінність полягає в тому, як кожен із них отримує сертифікат TLS (transport layer security) і скільки конфігурації потребує кожен додатковий застосунок. Інша відмінність проявиться пізніше, коли вам знадобиться те, про що не розповідають типові інструкції.
Вибирайте Caddy, якщо хочете, щоб HTTPS налаштовувався автоматично, а ваші сервіси були звичайними вебзастосунками. Вибирайте Traefik, якщо все працює в Docker Compose і ви додаєте новий сервіс кожні кілька тижнів. Вибирайте Nginx, якщо ви вже використовуєте його або якщо вам потрібні кешування відповідей, сертифікати клієнта, переспрямування необробленого 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, і саме так більшість користувачів відповідає цій вимозі.
Одне правило стосується всіх трьох варіантів. Для перевірки 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Перевірка nginx -t із виведенням syntax is ok і test is successful — це команда, яку потрібно виконувати перед кожним перезавантаженням. Для другого застосунку використовується такий самий блок, але зі зміненими іменем хоста та портом. Рядки proxy_set_header потрібні для роботи, а не є декоративними: коли proxy_pass визначає адресу, nginx за замовчуванням передає Host: 127.0.0.1:8080 на бекенд. Інакше застосунок, який формує абсолютні URL на основі заголовка Host, надсилатиме користувачів на localhost.
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 з тегом образу, актуальним станом на 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
}
]Підрахунок виконано за блоками вище. Блок сервера Nginx містить 11 непорожніх рядків, і його потрібно повторювати для кожного імені хоста. Блок сайту Caddy містить 3 рядків. Traefik потребує 17 рядків статичної конфігурації, перш ніж обробить хоча б один запит, а потім — 5 labels для кожного застосунку.
Оцінюйте компроміс, а не переможця. Traefik потребує найбільше налаштувань до додавання першого застосунку й найменше — для кожного наступного. Загальні обсяги зрівнюються приблизно на третьому сайті. Для меншої кількості сайтів статична конфігурація є зайвим накладним обсягом. Для більшої кількості labels дають перевагу, і вона продовжує зростати, оскільки правила маршрутизації зберігаються поруч із сервісом, для якого вони призначені. Якщо видалити сервіс, його маршрут видаляється разом із ним. Саме з цим погано справляється централізований файл конфігурації: у ньому залишаються застарілі блоки серверів для застосунків, яких уже кілька місяців не існує.
Кількість рядків також створює надто сприятливе враження про Nginx. Для кожного такого блока потрібні symlink, nginx -t, перезавантаження конфігурації та запуск certbot, тоді як для редагування Caddy потрібне одне перезавантаження, а для редагування Traefik не потрібна жодна команда. Усі три рішення перезавантажують конфігурацію без розриву активних з’єднань. Відрізняється лише кількість окремих кроків, які потрібно пам’ятати о першій годині ночі.
Хто знає про ваші контейнери?
Traefik стежить за Docker socket і створює маршрутизатори на основі міток контейнерів, коли контейнери запускаються та зупиняються. Більше жоден компонент у цій схемі цього не робить. Для Nginx і Caddy потрібно змінити конфігурацію та виконати reload, коли з’являється новий контейнер. Також їм потрібна адреса, до якої вони можуть підключитися: порт, опублікований на loopback, або спільна Docker network, до якої підключено проксі.
Ця функція має свою ціну, і про неї варто сказати прямо. Traefik читає /var/run/docker.sock. Будь-хто, хто може взаємодіяти з цим socket, може запустити контейнер із змонтованою файловою системою хоста. Це дає root на хості. Монтування лише для читання зменшує ризик, але не усуває його. Якщо це важливо для вашої моделі загроз, розмістіть між ними socket proxy, який відкриває лише endpoint-и списку контейнерів, потрібні Traefik.
Caddy може виконувати пошук сервісів за мітками через плагін спільноти, але плагіни Caddy вбудовуються під час компіляції. Тому потрібно створити власний binary або власний image із xcaddy, після чого ви самі відповідаєте за цю збірку та її оновлення. Для трьох або чотирьох сервісів редагувати Caddyfile простіше.
WebSocket і потокова передача: що ламається і чому
Допомоги потребує саме 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 надходять із затримкою або пакетами, доки для цього location не встановити proxy_buffering off;, оскільки nginx утримує відповідь у буфері, поки сторінка очікує на неї.
Caddy виконує upgrade і без додаткових директив перемикає з’єднання у двосторонній тунель. Він також негайно передає дані, коли відповідь має text/event-stream або її довжина невідома, тому потокова передача працює без додаткових налаштувань. Traefik пропускає upgrade і не буферизує відповіді, якщо ви самостійно не додасте middleware buffering. Якщо ваші сервіси містять чат, вебтермінал, перегляд журналів у реальному часі або live-дашборди, це суттєво впливає на обсяг конфігурації, яку доведеться писати й налагоджувати.
Повний блок server для Nginx, включно з WebSocket і 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 лише для location, які передають потокові дані, оскільки буферизація дає nginx змогу раніше звільнити робочий процес бекенду для звичайних відповідей. Certbot переписує цей блок під час запуску, тому після цього ще раз перевірте файл.
Що відбувається, коли потрібна нестандартна функція?
Саме тут Nginx виправдовує додаткові рядки конфігурації.
- Клієнтські сертифікати, також відомі як mTLS (взаємний TLS), коли клієнт також має надати сертифікат. Nginx потребує
ssl_client_certificate /etc/ssl/ca.pem;іssl_verify_client on;у блоці server. 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 потрібен скомпільований плагін. У open source-збірці Traefik взагалі немає HTTP-кешу. Це дивує тих, хто припускає, що кожен проксі кешує відповіді. - Прямий TCP або UDP, наприклад для порту бази даних або ігрового сервера. У Nginx є модуль
stream. Traefik має TCP- і UDP-router на окремих entrypoint. Для Caddy потрібен інший плагін, а отже, ще одна custom build. - Вебсервер уже працює за проксі. Якщо сервіс є класичним 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. У такому разі все налаштоване вище не забезпечує захисту.
Який reverse proxy обрати?
Переважно статичні сайти та один-два застосунки: Caddy. Автоматичний HTTPS усуває найбільше регулярне завдання з обслуговування. Конфігурація достатньо коротка, щоб уміститися на одному екрані, а для статичного сайту в тому самому блоці site потрібні рядок 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 не потребує конфігурації сертифіката?
Для звичайного випадку — так. Достатньо вказати публічне ім’я хоста як адресу сайту: Caddy запитує сертифікат через ACME, налаштовує редирект із порту 80 і поновлює сертифікат до завершення строку його дії. Водночас мають виконуватися дві умови. Порт 80 має бути доступний з інтернету для перевірки HTTP-01, а DNS-запис A або AAAA цього імені хоста вже має вказувати на VPS, оскільки центр сертифікації визначає адресу та підключається до неї.
Чи можна запустити Nginx і Traefik на одному VPS?
Не на тих самих портах. Той, який запускається другим, не може прив’язати порт: nginx виводить bind() to 0.0.0.0:443 failed (98: Address already in use), а Traefik записує подібну помилку прив’язування в журнал і завершує роботу. Запустіть один proxy на портах 80 і 443, а всі інші сервіси розмістіть за ним. Під час міграції переносіть імена хостів по одному: нехай front proxy пересилає запити до старого proxy через loopback-порт, доки не буде перенесено останній сайт.
Чому мої WebSocket-з’єднання розриваються через 60 секунд за Nginx?
proxy_read_timeout має значення за замовчуванням 60 секунд і застосовується до тунелю після завершення upgrade. Тому з’єднання без трафіку протягом хвилини закриває proxy, а не ваш застосунок. Збільште це значення для відповідного location за допомогою proxy_read_timeout 3600s; або налаштуйте застосунок на надсилання ping frame кожні 30 секунд. Caddy і Traefik не закривають неактивні з’єднання після upgrade за таймером в одну хвилину. Тому той самий застосунок може стабільно працювати за Caddy або Traefik і нестабільно — за Nginx.