Traefik vs Caddy vs Nginx: сравнение Docker reverse proxy
Три рабочих стека Docker Compose для Traefik, Caddy и Nginx в качестве reverse proxy на VPS. Один бэкенд, реальные бенчмарки и фреймворк для выбора подходящего решения.
На VPS крутятся Docker-контейнеры. Нужен HTTPS, маршрутизация по доменному имени и единая точка входа. Traefik, Caddy и Nginx решают эту задачу. Решают по-разному.
В этой статье три рабочих стека Docker Compose, которые разворачивают один и тот же бэкенд за каждым прокси. Копируй тот, который подходит под твою ситуацию. Таблица сравнения и фреймворк принятия решений в конце помогут определиться.
Все примеры используют выделенную сеть для прокси, редирект с HTTP на HTTPS и продакшн-настройки по умолчанию. Стеки рассчитаны на Ubuntu 24.04 на VPS Virtua Cloud.
Что делает reverse proxy для Docker-контейнеров на VPS?
Reverse proxy располагается между интернетом и Docker-контейнерами. Он терминирует TLS (HTTPS), маршрутизирует запросы к нужному контейнеру по имени хоста и выставляет одну пару портов (80/443) вместо отдельного порта для каждого сервиса. Контейнеры никогда не работают с сертификатами и не биндятся на публичные порты напрямую.
Без reverse proxy каждому контейнеру нужен свой публичный порт. Посетители заходили бы на example.com:3000 для одного сервиса и example.com:8080 для другого. Reverse proxy позволяет использовать app.example.com и api.example.com на стандартных портах.
Все три стека ниже предполагают:
- Docker и Docker Compose установлены
- DNS A-запись указывает на IP VPS
- Порты 80 и 443 открыты в файрволе
- Есть SSH-доступ от имени не-root пользователя с sudo
Каждый пример разворачивает один и тот же бэкенд: образ traefik/whoami, который возвращает HTTP-заголовки и информацию о контейнере. Замени его на своё приложение позже.
Как настроить Traefik в качестве Docker reverse proxy с автоматическим HTTPS?
Traefik обнаруживает контейнеры автоматически, читая Docker-лейблы. Правила маршрутизации добавляются как лейблы на каждый сервис. Когда контейнер стартует, Traefik обнаруживает его, запрашивает сертификат Let's Encrypt и начинает маршрутизировать трафик. Перезагрузка конфигурации не нужна.
Создай директорию проекта:
mkdir -p ~/traefik-proxy && cd ~/traefik-proxy
Создай Docker-сеть, которую будут использовать все проксируемые сервисы:
docker network create proxy
Создай docker-compose.yml:
services:
traefik:
image: traefik:v3.6
container_name: traefik
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--log.level=WARN"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./acme.json:/acme.json
networks:
- proxy
security_opt:
- no-new-privileges:true
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`app.example.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
networks:
- proxy
networks:
proxy:
external: true
Перед запуском создай файл хранения сертификатов с ограниченными правами:
touch acme.json && chmod 600 acme.json
Traefik откажется стартовать, если у acme.json слишком открытые права. 600 гарантирует, что только владелец может читать хранящиеся внутри приватные ключи.
Запусти стек:
docker compose up -d
Проверь, что оба контейнера работают:
docker compose ps
И traefik, и whoami должны показывать статус Up. Проверь с локальной машины (не с сервера):
curl https://app.example.com
Ответ содержит вывод whoami с заголовками запроса. Заголовок X-Forwarded-For в ответе говорит о том, что Traefik проксирует трафик и терминирует TLS.
Что делают лейблы:
traefik.enable=trueвключает этот контейнер (так как установленоexposedbydefault=false)traefik.http.routers.whoami.rule=Host(...)фильтрует запросы по имени хостаtraefik.http.routers.whoami.tls.certresolver=letsencryptуказывает Traefik получить сертификат для этого домена
Чтобы добавить ещё один сервис, добавь его в любой Compose-файл в той же сети proxy с нужными лейблами. Traefik подхватит его автоматически.
Безопасно ли монтировать Docker-сокет в Traefik?
Монтирование /var/run/docker.sock даёт Traefik полный доступ к Docker API. Если атакующий скомпрометирует Traefik, он сможет создавать контейнеры, читать переменные окружения (включая секреты) и эскалировать привилегии до root на хосте. Флаг :ro предотвращает только запись на уровне файловой системы. Он не ограничивает вызовы Docker API.
Для продакшна используй прокси для Docker-сокета. Он располагается между Traefik и Docker-демоном, фильтруя API-вызовы и разрешая только операции чтения метаданных контейнеров.
Добавь в docker-compose.yml:
services:
socket-proxy:
image: tecnativa/docker-socket-proxy:0.4
container_name: socket-proxy
restart: unless-stopped
environment:
CONTAINERS: 1
NETWORKS: 1
SERVICES: 0
TASKS: 0
POST: 0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- socket-proxy
security_opt:
- no-new-privileges:true
traefik:
image: traefik:v3.6
container_name: traefik
restart: unless-stopped
depends_on:
- socket-proxy
command:
- "--providers.docker.endpoint=tcp://socket-proxy:2375"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--log.level=WARN"
ports:
- "80:80"
- "443:443"
volumes:
- ./acme.json:/acme.json
networks:
- proxy
- socket-proxy
security_opt:
- no-new-privileges:true
networks:
proxy:
external: true
socket-proxy:
driver: bridge
internal: true
Traefik больше не монтирует Docker-сокет напрямую. Сеть socket-proxy помечена как internal: true, то есть у неё нет исходящего доступа в интернет. Прокси сокета разрешает только GET-запросы к эндпоинтам containers и networks.
Как настроить Caddy в качестве Docker reverse proxy с автоматическим HTTPS?
Caddy берёт на себя HTTPS автоматически, без настроек кроме доменного имени. Направь домен на сервер, укажи его в Caddyfile, и Caddy получит и обновит сертификаты от Let's Encrypt. Никаких резолверов, никаких настроек ACME. Это кратчайший путь к HTTPS для Docker reverse proxy.
Создай директорию проекта:
mkdir -p ~/caddy-proxy && cd ~/caddy-proxy
Создай общую сеть для прокси (пропусти, если уже создал для Traefik):
docker network create proxy
Создай Caddyfile:
app.example.com {
reverse_proxy whoami:80
encode gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
Это вся конфигурация прокси. Caddy читает доменное имя, запрашивает сертификат и проксирует на контейнер whoami на порту 80. Никаких резолверов сертификатов, никакого ACME-email (Caddy использует дефолтный с машины, или можно задать глобально), никаких путей хранения.
Создай docker-compose.yml:
services:
caddy:
image: caddy:2.11
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- proxy
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
networks:
- proxy
networks:
proxy:
external: true
volumes:
caddy_data:
caddy_config:
Порт 443:443/udp включает HTTP/3 (QUIC), который Caddy поддерживает из коробки. cap_drop: ALL с cap_add: NET_BIND_SERVICE убирает все Linux capabilities кроме нужного для биндинга на порты ниже 1024.
Запусти стек:
docker compose up -d
Проверь статус контейнеров:
docker compose ps
Оба контейнера должны показывать Up. Проверь с локальной машины с подробным выводом:
curl -v https://app.example.com
Ищи HTTP/2 200 в выводе. Также должны быть видны заголовки безопасности из Caddyfile (Strict-Transport-Security, X-Content-Type-Options и т.д.).
Чтобы добавить ещё один сервис, добавь новый блок в Caddyfile с доменом и директивой reverse_proxy, затем перезагрузи:
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
Перезапуск контейнера не нужен. Caddy не нуждается в Docker-сокете. Он не обнаруживает контейнеры автоматически. Маршрутизация управляется в Caddyfile.
Как настроить Nginx в качестве Docker reverse proxy с Let's Encrypt?
Nginx даёт полный контроль над каждой директивой прокси, заголовком, размером буфера и правилом кэширования. Обратная сторона — ручная настройка. Nginx не получает TLS-сертификаты самостоятельно. Его объединяют с Certbot, который обрабатывает ACME-челленджи и обновление сертификатов.
Создай директорию проекта:
mkdir -p ~/nginx-proxy && cd ~/nginx-proxy
Создай общую сеть для прокси:
docker network create proxy
Создай конфигурацию Nginx в nginx/conf.d/app.conf:
mkdir -p nginx/conf.d
server {
listen 80;
server_name app.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
http2 on;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers off;
server_tokens off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
proxy_pass http://whoami:80;
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;
}
}
server_tokens off; скрывает версию Nginx из заголовков ответа. Раскрытие версии помогает атакующим нацеливаться на известные уязвимости.
Создай docker-compose.yml:
services:
nginx:
image: nginx:1.28
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- certbot_webroot:/var/www/certbot:ro
- certbot_certs:/etc/letsencrypt:ro
networks:
- proxy
depends_on:
- whoami
certbot:
image: certbot/certbot
container_name: certbot
restart: unless-stopped
volumes:
- certbot_webroot:/var/www/certbot
- certbot_certs:/etc/letsencrypt
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
networks:
- proxy
networks:
proxy:
external: true
volumes:
certbot_webroot:
certbot_certs:
Nginx требует, чтобы файлы сертификатов существовали до запуска. Конфигурация выше ссылается на /etc/letsencrypt/live/app.example.com/fullchain.pem, которого ещё нет. Для первого сертификата временно замени app.conf на версию только с HTTP:
server {
listen 80;
server_name app.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
Запусти Nginx и бэкенд:
docker compose up -d nginx whoami
Запроси первый сертификат:
docker compose run --rm certbot certonly \
--webroot \
--webroot-path=/var/www/certbot \
-d app.example.com \
--email you@example.com \
--agree-tos \
--no-eff-email
После получения сертификата верни полный app.conf (версию с SSL-блоком, показанную выше) и запусти полный стек:
docker compose up -d
Проверь, что все контейнеры работают:
docker compose ps
Проверь с локальной машины:
curl -v https://app.example.com
Заголовок ответа server: должен показывать nginx без номера версии, что подтверждает работу server_tokens off.
Чтобы добавить ещё один сервис, создай новый .conf-файл в nginx/conf.d/ и перезагрузи:
docker compose exec nginx nginx -s reload
Для обновления сертификатов контейнер Certbot запускает certbot renew каждые 12 часов. После обновления перезагрузи Nginx, чтобы он подхватил новые сертификаты. Автоматизируй это с помощью cron-задачи или скрипта, проверяющего время изменения файлов сертификатов. Подробнее о настройке Nginx как reverse proxy читай в нашем руководстве.
Как соотносятся Traefik, Caddy и Nginx для Docker reverse proxying?
Traefik выигрывает в автообнаружении. Caddy выигрывает в простоте. Nginx выигрывает в контроле. Таблица ниже раскладывает компромиссы, которые важны при запуске Docker-контейнеров на VPS.
| Характеристика | Traefik v3 | Caddy 2.11 | Nginx 1.28 |
|---|---|---|---|
| Автообнаружение | Да (Docker-лейблы) | Нет (ручной Caddyfile) | Нет (ручные conf-файлы) |
| Автоматизация TLS | Встроенный ACME | Встроенный ACME | Требует Certbot-sidecar |
| Способ конфигурации | Docker-лейблы + статический YAML/CLI | Caddyfile или JSON API | Файлы nginx.conf |
| Перезагрузка конфига | Автоматически по событиям контейнеров | caddy reload (без даунтайма) |
nginx -s reload (без даунтайма) |
| Docker-сокет нужен | Да (или прокси сокета) | Нет | Нет |
| HTTP/3 (QUIC) | Экспериментально | Да (по умолчанию) | Через сторонний модуль |
| Middleware/плагины | Встроенные (rate limit, auth, headers) | Встроенные + Go-плагины | Через директивы конфига |
| Сообщество/документация | Большое, активное, хорошая дока | Меньшее, отличная дока | Самое большое, обширная дока |
| Кривая обучения | Средняя (лейблы + статический конфиг) | Низкая (Caddyfile интуитивен) | Высокая (много директив) |
Какой reverse proxy потребляет меньше всего памяти?
Потребление памяти в простое важно на VPS, где каждый мегабайт на счету. Эти цифры получены с помощью docker stats --no-stream на VPS Virtua Cloud с 4 vCPU / 8 ГБ RAM под Ubuntu 24.04. Каждый прокси работал в простое без трафика перед замером.
| Прокси | RAM в простое | Размер образа |
|---|---|---|
| Traefik v3.6 | ~17 МБ | ~242 МБ |
| Caddy 2.11 | ~14 МБ | ~88 МБ |
| Nginx 1.28 | ~5 МБ | ~240 МБ |
| Nginx + Certbot | ~5 МБ + ~25 МБ | ~240 МБ + ~298 МБ |
Nginx потребляет значительно меньше всех. Caddy посередине. Повышенное потребление Traefik связано с хранением в памяти состояния Docker-провайдера и таблицы маршрутизации. Все три используют дефолтные образы (на базе Debian/Alpine). Alpine-варианты уменьшат размер образов ценой возможных проблем совместимости с некоторыми расширениями.
Под лёгкой нагрузкой (100 параллельных запросов через wrk) все три справляются с трафиком без заметного роста CPU или памяти на этом размере VPS. Различия проявляются только при масштабировании или на самых маленьких тарифах VPS.
Как выбрать правильный reverse proxy для своей Docker-конфигурации?
Правильный выбор зависит от того, сколько сервисов ты запускаешь, как часто они меняются и что ты уже знаешь.
Выбирай Traefik, когда:
- Много контейнеров, которые часто меняются (добавление/удаление сервисов каждую неделю)
- Хочешь маршрутизацию без ручного вмешательства: задеплоил контейнер с лейблами — он в сети
- Используешь Docker Swarm или нужен service discovery на нескольких нодах
- Принимаешь доступ к Docker-сокету (с прокси сокета для продакшна)
Выбирай Caddy, когда:
- Немного сервисов, которые редко меняются
- Хочешь самый простой путь к автоматическому HTTPS
- Не хочешь монтировать Docker-сокет
- Ценишь маленький образ и низкое потребление памяти
- Хочешь HTTP/3 без дополнительной настройки
Выбирай Nginx, когда:
- Уже знаешь конфигурацию Nginx
- Нужен тонкий контроль над поведением прокси (буферы, кэширование, кастомные заголовки по location)
- Хочешь минимально возможное потребление памяти
- В команде уже есть инструменты и мониторинг для Nginx
- Не проблема управлять Certbot отдельно
Дерево решений:
- Больше 5 Docker-сервисов, которые регулярно меняются? Да -> Traefik
- Нужна тонкая настройка прокси или уже используешь Nginx? Да -> Nginx
- Хочешь минимум движущихся частей и самый быстрый старт? Да -> Caddy
Для большинства инди-хакеров, деплоящих один-два пет-проекта, Caddy — лучшая стартовая точка. Для DevOps-команд, управляющих флотом контейнеров, автообнаружение Traefik окупает себя. Для команд, уже использующих Nginx в других местах, оставаться на Nginx — значит держать стек единообразным.
Усиление безопасности для всех трёх прокси
Какой бы прокси ты ни выбрал, применяй эти базовые практики безопасности.
Заголовки безопасности. Все три примера выше включают HSTS, X-Content-Type-Options, X-Frame-Options и Referrer-Policy. Для Traefik добавляй их как middleware-лейблы:
labels:
- "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.security-headers.headers.frameDeny=true"
- "traefik.http.routers.whoami.middlewares=security-headers"
Rate limiting. У Traefik есть встроенный middleware для rate limiting. У Caddy есть директива rate_limit в виде плагина. Nginx использует limit_req_zone в конфигурации. Rate limiting защищает бэкенд от брутфорса и злоупотреблений.
Изоляция сети Docker. Каждый пример использует внешнюю сеть proxy. Бэкенд-сервисы не должны находиться в дефолтной bridge-сети. Только контейнеры, которым нужен прокси, присоединяются к сети proxy. Контейнеры с базами данных и внутренние сервисы остаются в отдельных внутренних сетях.
Файрвол. Только порты 80 и 443 должны быть доступны публично. Docker манипулирует iptables напрямую, что может обойти правила UFW. Решение описано в нашем руководстве.
Логи. Смотри логи прокси, когда что-то идёт не так:
# Traefik
docker logs traefik -f
# Caddy
docker logs caddy -f
# Nginx
docker logs nginx -f
Для Traefik временно установи --log.level=DEBUG для диагностики проблем с маршрутизацией или сертификатами. Для Caddy включи глобальную опцию debug в Caddyfile. Для Nginx проверь error.log внутри контейнера по пути /var/log/nginx/error.log.
Что-то пошло не так?
| Симптом | Вероятная причина | Решение |
|---|---|---|
| Сертификат не выдан | DNS A-запись не указывает на IP VPS | Проверь с помощью dig app.example.com |
| Traefik 404 на всех маршрутах | Контейнер не в сети proxy |
Проверь docker network inspect proxy |
| Caddy «permission denied» на порту 80 | Отсутствует capability NET_BIND_SERVICE |
Добавь cap_add: NET_BIND_SERVICE |
| Nginx «no such file» для сертификата | Certbot ещё не запускался | Сначала запусти certbot certonly |
ERR_CONNECTION_REFUSED |
Файрвол блокирует 80/443 | Проверь ufw status или iptables -L |
Ошибка прав acme.json в Traefik |
Слишком открытые права файла | Запусти chmod 600 acme.json |
| Прокси работает на сервере, не работает снаружи | Тестирование только на localhost | Проверь curl с локальной машины |
Для продакшн-усиления за пределами reverse proxying читай наше руководство по лимитам ресурсов и health checks в Compose-стеках.
Готовы попробовать?
Разверните свой сервер за секунды. Linux, Windows или FreeBSD. →