Traefik vs Caddy vs Nginx: сравнение Docker reverse proxy

10 мин чтения·Matthieu·docker-composelets-encryptreverse-proxynginxcaddytraefikdocker|

Три рабочих стека 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, когда:

  1. Много контейнеров, которые часто меняются (добавление/удаление сервисов каждую неделю)
  2. Хочешь маршрутизацию без ручного вмешательства: задеплоил контейнер с лейблами — он в сети
  3. Используешь Docker Swarm или нужен service discovery на нескольких нодах
  4. Принимаешь доступ к Docker-сокету (с прокси сокета для продакшна)

Выбирай Caddy, когда:

  1. Немного сервисов, которые редко меняются
  2. Хочешь самый простой путь к автоматическому HTTPS
  3. Не хочешь монтировать Docker-сокет
  4. Ценишь маленький образ и низкое потребление памяти
  5. Хочешь HTTP/3 без дополнительной настройки

Выбирай Nginx, когда:

  1. Уже знаешь конфигурацию Nginx
  2. Нужен тонкий контроль над поведением прокси (буферы, кэширование, кастомные заголовки по location)
  3. Хочешь минимально возможное потребление памяти
  4. В команде уже есть инструменты и мониторинг для Nginx
  5. Не проблема управлять Certbot отдельно

Дерево решений:

  1. Больше 5 Docker-сервисов, которые регулярно меняются? Да -> Traefik
  2. Нужна тонкая настройка прокси или уже используешь Nginx? Да -> Nginx
  3. Хочешь минимум движущихся частей и самый быстрый старт? Да -> 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-стеках.


Авторское право 2026 Virtua.Cloud. Все права защищены. Данный контент является оригинальным произведением команды Virtua.Cloud. Воспроизведение, повторная публикация или распространение без письменного разрешения запрещены.

Готовы попробовать?

Разверните свой сервер за секунды. Linux, Windows или FreeBSD.

Смотреть тарифы VPS
Traefik vs Caddy vs Nginx: Docker reverse proxy