Nginx, Caddy или Traefik: что выбрать для reverse proxy
Сравнение Nginx, Caddy и Traefik для работы с одним IP. Узнайте, какой прокси лучше справляется с TLS, Docker, WebSocket и настройкой новых сервисов на вашем VPS сервере.
Nginx, Caddy или Traefik: краткий ответ
Nginx, Caddy и Traefik выполняют одну и ту же задачу в качестве reverse proxy: ожидают соединений на 443 порту, считывают имя хоста в каждом запросе и передают его нужному сервису на вашем VPS. Любой из этих инструментов позволит разместить четыре self-hosted приложения за одним публичным IP-адресом, и все они достаточно быстры, чтобы узким местом оставались сами приложения, а не прокси. Разница заключается в том, как каждый из них получает TLS (transport layer security) сертификат и сколько усилий требует настройка каждого нового приложения. Другие различия проявляются позже, когда вам требуется функциональность, которую обычно пропускают в базовых руководствах.
Выбирайте Caddy, если хотите, чтобы HTTPS настраивался автоматически, а ваши сервисы являются обычными веб-приложениями. Выбирайте Traefik, если всё работает в Docker Compose и вы добавляете новый сервис каждые несколько недель. Выбирайте Nginx, если вы уже используете его, или если вам нужны кэширование ответов, клиентские сертификаты, проброс 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 при установке из пакета, поэтому добавьте этот путь в резервные копии или будьте готовы к повторному выпуску после пересборки. Для непубличного имени хоста 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.comnginx -t с выводом syntax is ok и test is successful — это проверка, которую нужно выполнять перед каждой перезагрузкой. Для второго приложения используется такой же блок с измененным именем хоста и портом. Строки proxy_set_header — это не украшение: когда proxy_pass указывает адрес, nginx по умолчанию отправляет Host: 127.0.0.1:8080 на upstream, поэтому приложение, формирующее абсолютные URL на основе заголовка Host, будет перенаправлять пользователей на localhost. Назначение каждого из этих четырех заголовков и причины, по которым завершающий слэш в proxy_pass меняет путь, получаемый приложением, подробно разобраны директива за директивой в этом руководстве по серверному блоку 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, с образом, актуальным на август 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 непустых строк, и его нужно создавать заново для каждого имени хоста. Site block в 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 на 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 секунд и применяется к туннелю после обновления протокола, поэтому 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 в блоке server необходимо указать
ssl_client_certificate /etc/ssl/ca.pem;иssl_verify_client on;. В Caddy для этого используется блокclient_authвнутриtls. В Traefik метки не позволяют настроить это напрямую: необходимо определить TLS-опцию в файловом провайдере и указать её для маршрутизатора через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-кэширование отсутствует вовсе, что часто становится неожиданностью для тех, кто ожидает наличия кэша в любом прокси. - Raw 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-listener.
Действительно ли Caddy не требует настройки сертификатов?
В стандартном случае — да. Указание публичного имени хоста в качестве адреса сайта — это вся конфигурация: Caddy запрашивает сертификат через ACME, выполняет редирект с 80 порта и обновляет сертификат до истечения срока действия. При этом должны соблюдаться два условия. Порт 80 должен быть доступен из Интернета для прохождения HTTP-01 challenge, а DNS A или AAAA запись для хоста уже должна указывать на ваш VPS, так как центр сертификации разрешает имя и подключается к нему.
Могу ли я запустить Nginx и Traefik на одном VPS?
Не на одних и тех же портах. Тот, который запустится вторым, не сможет выполнить bind, и 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.