Nginx, Caddy или Traefik: что выбрать для reverse proxy
Сравнение Nginx, Caddy и Traefik для управления трафиком на одном VPS. Узнайте, как эти инструменты работают с TLS, Docker, WebSocket и какие затраты на настройку каждого.
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 при установке из пакета, поэтому добавьте этот путь в резервные копии или будьте готовы к повторному выпуску после пересборки системы. Для хоста, который не является публичным, tls internal подписывает сертификат собственным локальным центром сертификации Caddy. Вы получите тот же результат, что и при создании самоподписанного сертификата в Ubuntu, но с автоматическим обновлением.
Nginx не имеет встроенного ACME-клиента. Certbot получает сертификат, а его плагин --nginx переписывает блок server, добавляя слушатель 443 порта и редирект. Обновление выполняется через таймер systemd, который устанавливается вместе с пакетом. Таким образом, здесь есть два компонента, требующих контроля: systemctl list-timers | grep certbot показывает наличие таймера, а sudo certbot renew --dry-run подтверждает работоспособность процесса обновления. Пошаговое руководство приведено в Certbot в Ubuntu 24.04 с Nginx, а тот же инструмент позволяет получить wildcard-сертификат через DNS-01 challenge, если у вас больше поддоменов, чем вы хотите перечислять вручную.
Traefik содержит собственный ACME-клиент. Вы настраиваете один резолвер сертификатов в статической конфигурации, после чего его может использовать любой маршрутизатор. Все данные, включая ключ учетной записи и сертификаты, хранятся в одном файле acme.json. Traefik откажется использовать этот файл, если он доступен для чтения кому-либо, кроме владельца, и сообщит об этом перед отключением резолвера:
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 challenge порт 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;
}
}Затем создайте символическую ссылку, проверьте конфигурацию, перезагрузите сервис и добавьте сертификат.
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 вышестоящему серверу (upstream). В результате приложение, формирующее абсолютные 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 требует предварительной статической конфигурации. В качестве сервиса Docker Compose с актуальным на август 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. Приложению вообще не нужна строка 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 block в Nginx занимает 11 непустых строк, и вы пишете его заново для каждого имени хоста. Блок сайта в Caddy занимает 3 строк. Traefik требует 17 строк статической конфигурации перед обработкой первого запроса, а затем по 5 меток (labels) на каждое приложение.
Оценивайте компромисс, а не победителя. Traefik требует больше всего усилий до запуска первого приложения и меньше всего для каждого последующего; показатели выравниваются примерно на третьем сайте. При меньшем количестве сайтов статическая конфигурация — это избыточные накладные расходы. При большем — метки выигрывают, так как маршрутизация находится рядом с сервисом, который она обслуживает. При удалении сервиса его маршрут исчезает вместе с ним, что является слабым местом централизованного файла конфигурации: в нем остаются устаревшие server blocks для приложений, которые перестали существовать несколько месяцев назад.
Количество строк также льстит Nginx. Для каждого такого блока требуется создание символической ссылки, nginx -t, перезагрузка и запуск certbot, в то время как для Caddy достаточно одной перезагрузки, а для Traefik вообще не нужно выполнять команд. Все три инструмента перезагружаются без разрыва активных соединений. Разница заключается в количестве отдельных шагов, которые вам приходится держать в голове в час ночи.
Кто из них знает о ваших контейнерах?
Traefik отслеживает Docker socket и автоматически создает маршруты на основе меток контейнеров при их запуске и остановке. Другие решения этого не делают. Nginx и Caddy требуют внесения изменений в конфигурацию и перезагрузки при появлении нового контейнера. Кроме того, им нужен доступный адрес: либо порт, опубликованный на loopback, либо общая Docker network, к которой подключен прокси.
У этой функции есть цена, и о ней стоит сказать прямо. Traefik считывает /var/run/docker.sock. Любой, кто может взаимодействовать с этим сокетом, способен запустить контейнер с примонтированной файловой системой хоста, что равносильно получению прав root на хосте. Монтирование в режиме read only снижает риск, но не устраняет его полностью. Если это критично для вашей модели угроз, установите между ними socket proxy, который будет предоставлять только те эндпоинты для списка контейнеров, которые необходимы Traefik.
Caddy может выполнять обнаружение на основе меток через сторонний плагин, но плагины Caddy компилируются в бинарный файл. Вам придется собирать собственный бинарный файл или кастомный образ с помощью xcaddy, после чего вы берете на себя ответственность за эту сборку и её обновления. Для трех или четырех сервисов редактирование Caddyfile требует меньше усилий.
Websockets и потоковая передача: что ломается и почему
Nginx требует дополнительной настройки. WebSocket-соединение начинается с HTTP-запроса, содержащего Upgrade: websocket, но nginx не передает заголовки hop-by-hop вышестоящему серверу, если вы явно не укажете это.
# /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 секундам и применяется к туннелю после обновления протокола, поэтому websocket без трафика в течение минуты будет закрыт прокси-сервером. Кроме того, события server-sent events приходят с задержкой или порциями, пока вы не установите proxy_buffering off; для этой локации, так как nginx удерживает ответ в буфере, пока страница ожидает его.
Caddy выполняет обновление протокола и переключает соединение в режим двустороннего туннеля без каких-либо дополнительных директив. Он также немедленно сбрасывает данные, если ответ имеет статус text/event-stream или неизвестную длину, поэтому потоковая передача работает без изменений. Traefik пропускает запросы на обновление протокола и не буферизирует ответы, если вы сами не добавите middleware buffering. Если ваши сервисы включают чаты, веб-терминалы, вывод логов в реальном времени или динамические панели мониторинга, это существенно влияет на объем конфигурации, которую вам придется писать и отлаживать.
Полный блок server для 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 раньше освобождать воркер бэкенда при обычных ответах. Certbot перезаписывает этот блок при запуске, поэтому проверяйте файл после его работы.
Что делать, если требуется нестандартная конфигурация?
Именно здесь Nginx оправдывает дополнительные строки конфигурации.
- Клиентские сертификаты, также известные как mTLS (mutual TLS), требуют, чтобы клиент также предоставил сертификат. В Nginx для этого используются директивы
ssl_client_certificate /etc/ssl/ca.pem;иssl_verify_client on;в блоке server. В Caddy требуется блокclient_authвнутриtls. В Traefik через метки (labels) это реализовать невозможно: необходимо определить TLS-опцию в файловом провайдере и указать её в маршрутизаторе с помощьюtraefik.http.routers.app.tls.options=mtls@file. Модель «всё в метках» даёт сбой при первом же столкновении с такой задачей. - Загрузка больших файлов. По умолчанию Nginx ограничивает размер тела запроса 1 МБ. При попытке загрузить файл большего размера возвращается ошибка
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 маршрутизаторы для своих точек входа (entrypoints). Для Caddy снова потребуется плагин, а значит — сборка кастомной версии. - Веб-сервер, уже находящийся за прокси. Если сервис представляет собой классическое PHP-приложение, то стек LAMP на Ubuntu 24.04 уже включает Apache. Установка прокси перед ним приводит к тому, что заголовки и правила перезаписи URL могут настраиваться в двух местах одновременно. Выберите, какой из них будет выполнять TLS termination, а второй оставьте работать по обычному HTTP, привязав его к интерфейсу loopback.
Ловушка межсетевого экрана при таком выборе
Смысл использования reverse proxy заключается в том, что открытыми остаются только порты 80 и 443. Docker незаметно нарушает это правило. Публикация порта с помощью -p 8080:80 создает правило DNAT в таблице nat, которое обрабатывается до правил INPUT, управляемых через ufw. В результате ufw deny 8080 не блокирует такой трафик, и ваше приложение оказывается доступным из публичного интернета в обход тщательно настроенного прокси. Привязывайте опубликованные порты к loopback-интерфейсу с помощью 127.0.0.1:8080:80 или полностью откажитесь от ports:, позволяя прокси обращаться к контейнеру через Docker network, как это сделано в примере с Traefik выше. Механизм проблемы и способы её устранения описаны в почему опубликованные порты Docker обходят ufw.
Проверьте это с машины, отличной от вашего VPS, так как проверка, запущенная на самом сервере, всегда будет успешной:
curl --max-time 5 http://your.server.address:8080Connection refused или таймаут — это ожидаемый результат. Получение HTTP-ответа означает, что приложение доступно напрямую, минуя ваш прокси, а все настройки, выполненные ранее, не обеспечивают реальной защиты.
Какой прокси выбрать?
Преимущественно статические сайты и пара приложений: Caddy. Автоматический HTTPS избавляет от самой частой рутинной задачи. Конфигурация остается достаточно компактной, чтобы уместиться на одном экране, а статический сайт описывается одной строкой root и одной строкой file_server внутри одного блока сайта. Минус — меньшее количество готовых решений в сети на случай возникновения нестандартных проблем.
Домашняя лаборатория на базе docker-compose, которую вы постоянно расширяете: Traefik. После добавления третьего сервиса использование меток (labels) требует меньше усилий, чем редактирование центрального файла конфигурации, а при удалении сервиса его маршрут исчезает автоматически. Выделите вторую половину дня на первоначальную настройку, так как entrypoints, routers, services и middlewares — это новая терминология. Опечатка в метке обычно приводит к ошибке 404 со стороны Traefik, а не к сбою запуска, поэтому проверяйте docker logs traefik на наличие ошибок парсинга, прежде чем делать вывод о неисправности приложения.
Существующая конфигурация Nginx или любые требования из списка выше: Nginx. В нем уже реализованы механизмы кэширования ответов и работы с клиентскими сертификатами, а почти все сторонние руководства ориентированы именно на него. Минус в том, что сертификаты и поддержку websocket нужно настраивать вручную, они не работают «из коробки».
Независимо от выбора, соблюдайте одно правило. Только один процесс должен слушать публичный интерфейс, а все остальные сервисы должны быть привязаны к loopback или частной сети Docker.
FAQ
Какой reverse proxy лучше выбрать для нескольких Docker-приложений на одном VPS?
Для трёх или четырёх сервисов, которые вы периодически добавляете, Traefik окупает себя, так как каждое приложение содержит собственные метки маршрутизации и не требует правок в центральном файле конфигурации. Если сервисы стабильны и вы хотите просто избавиться от проблем с HTTPS, Caddy проще в изучении и менее подвержен сбоям. Выбирайте Nginx, если вы уже знакомы с ним или если вам нужна функция, которой нет у других, например, кэширование ответов или прослушивание обычного TCP-трафика.
Действительно ли 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 запишет в лог аналогичную ошибку привязки и завершит работу. Запустите один прокси на портах 80 и 443, а всё остальное разместите за ним. Если вы выполняете миграцию, переносите доменные имена по очереди: пусть фронтальный прокси перенаправляет трафик на старый прокси через loopback-порт, пока последний сайт не будет перенесён.
Почему мои websockets обрываются через 60 секунд при работе через Nginx?
proxy_read_timeout по умолчанию составляет 60 секунд, и это значение применяется к туннелю после завершения процесса upgrade. В результате соединение без трафика в течение минуты закрывается прокси-сервером, а не вашим приложением. Увеличьте это значение в соответствующем location с помощью proxy_read_timeout 3600s; или настройте приложение так, чтобы оно отправляло ping-фрейм каждые 30 секунд. Caddy и Traefik не закрывают простаивающие обновлённые соединения по таймеру в одну минуту, поэтому одно и то же приложение может выглядеть стабильным за ними и нестабильным за Nginx.