Caddy: reverse proxy с автоматическим HTTPS

Nginx — это стандарт индустрии. Он быстрый, надёжный и проверен десятилетиями. Но каждый раз, когда я настраиваю на нём reverse proxy с HTTPS, я трачу время на одни и те же вещи: генерация сертификатов через certbot, настройка cron для обновления, правильные ssl-параметры, редирект с HTTP на HTTPS. Это рутина, которую можно автоматизировать.
Caddy делает именно это. Он автоматически получает и обновляет TLS-сертификаты от Let’s Encrypt, настраивает HTTPS по умолчанию и при этом имеет конфигурацию, которая читается как обычный текст. Я использую Caddy на нескольких проектах как основной reverse proxy и расскажу, почему он заслуживает внимания.
Почему Caddy
Три главных преимущества Caddy перед классическими web-серверами:
Автоматический HTTPS. Caddy получает сертификаты от Let’s Encrypt (или ZeroSSL) автоматически. Не нужен certbot, не нужны cron-задачи, не нужно думать об обновлении сертификатов. Caddy сам проходит ACME-challenge, получает сертификат и обновляет его до истечения срока.
Простая конфигурация. Caddyfile — это минималистичный формат, где типичный reverse proxy описывается в 3-4 строки. Никаких proxy_pass, proxy_set_header, ssl_certificate — Caddy делает всё это автоматически.
Безопасность по умолчанию. Caddy включает HTTPS для всех сайтов, использует современные TLS-настройки (TLS 1.3, OCSP stapling, HSTS), автоматически редиректит HTTP на HTTPS. Не нужно копировать сниппеты из Mozilla SSL Configuration Generator.
Установка
На Ubuntu/Debian:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy Или через Docker (мой предпочтительный способ):
docker pull caddy:2-alpine После установки Caddy сразу готов к работе. По умолчанию он обслуживает файлы из текущей директории — можно проверить, что он запускается:
caddy run Caddyfile: основы синтаксиса
Caddyfile — это конфигурационный файл Caddy. Его синтаксис построен вокруг блоков сайтов:
example.com {
respond "Hello, World!"
} Это всё, что нужно, чтобы запустить сайт с HTTPS. Caddy сам получит сертификат для example.com, настроит TLS и начнёт отвечать на запросы. Для сравнения, в Nginx это потребовало бы минимум 15-20 строк конфигурации плюс настройку certbot.
Множественные сайты
api.example.com {
reverse_proxy localhost:8080
}
app.example.com {
reverse_proxy localhost:3000
}
static.example.com {
root * /var/www/static
file_server
} Три сайта, три домена, три TLS-сертификата — и никакой ручной работы с сертификатами.
Глобальные опции
Глобальные параметры задаются в блоке без адреса в начале файла:
{
email admin@example.com
acme_ca https://acme-v02.api.letsencrypt.org/directory
log {
output file /var/log/caddy/access.log
format json
}
} email используется для регистрации в Let’s Encrypt и для уведомлений об истечении сертификатов.
Reverse Proxy
Основная задача Caddy в моих проектах — reverse proxy для бэкенд-сервисов. Базовая настройка:
api.example.com {
reverse_proxy localhost:8080
} Caddy автоматически проксирует заголовки X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host. Не нужно прописывать proxy_set_header как в Nginx.
Проксирование WebSocket
ws.example.com {
reverse_proxy localhost:8080
} Caddy автоматически определяет WebSocket-соединения по заголовку Upgrade и проксирует их. Никакой дополнительной настройки.
Health checks
api.example.com {
reverse_proxy localhost:8080 {
health_uri /health
health_interval 10s
health_timeout 5s
health_status 200
}
} Caddy периодически проверяет доступность бэкенда и перестаёт отправлять на него запросы, если он не отвечает.
Load Balancing
Caddy поддерживает балансировку нагрузки между несколькими бэкендами:
api.example.com {
reverse_proxy localhost:8081 localhost:8082 localhost:8083 {
lb_policy round_robin
health_uri /health
health_interval 15s
}
} Доступные политики балансировки:
round_robin— по очереди (по умолчанию)least_conn— на бэкенд с наименьшим числом активных соединенийrandom— случайный выборfirst— на первый доступныйip_hash— привязка клиента к бэкенду по IPuri_hash— привязка по URIheader— по значению заголовкаcookie— sticky sessions через cookie
Пример sticky sessions:
app.example.com {
reverse_proxy localhost:3001 localhost:3002 {
lb_policy cookie my_sticky_cookie
health_uri /health
health_interval 10s
}
} Headers и Middleware
Caddy использует систему middleware (называемых directives), которые обрабатывают запросы последовательно.
Управление заголовками
api.example.com {
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
-Server
}
reverse_proxy localhost:8080
} Префикс - удаляет заголовок. Здесь мы убираем заголовок Server, чтобы не раскрывать информацию о web-сервере.
CORS
api.example.com {
@cors_preflight method OPTIONS
handle @cors_preflight {
header Access-Control-Allow-Origin "https://app.example.com"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Authorization"
header Access-Control-Max-Age "3600"
respond "" 204
}
header Access-Control-Allow-Origin "https://app.example.com"
reverse_proxy localhost:8080
} Сжатие
app.example.com {
encode gzip zstd
reverse_proxy localhost:3000
} Caddy поддерживает gzip и zstd, автоматически выбирая лучший вариант на основе заголовка Accept-Encoding клиента.
File Server
Caddy отлично подходит для раздачи статических файлов:
static.example.com {
root * /var/www/static
file_server {
hide .git .env
precompressed gzip br
}
} hide — скрывает указанные файлы и директории. precompressed — ищет заранее сжатые версии файлов (например, index.html.gz).
SPA (Single Page Application)
app.example.com {
root * /var/www/app
try_files {path} /index.html
file_server
} try_files — аналог try_files в Nginx. Если файл не найден — отдаёт index.html, что необходимо для SPA с клиентским роутингом.
Сравнение с Nginx

Вот один и тот же сценарий — reverse proxy с HTTPS — в Caddy и Nginx:
Caddy (3 строки):
api.example.com {
reverse_proxy localhost:8080
} Nginx (около 30 строк):
server {
listen 80;
server_name api.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
location / {
proxy_pass http://localhost: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;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
} Плюс отдельно настроенный certbot и cron для обновления сертификатов. Разница в сложности очевидна.
При этом важно понимать: Nginx остаётся лучшим выбором для высоконагруженных систем, где нужна тонкая настройка каждого параметра. Caddy оптимален для проектов, где важнее скорость настройки и простота обслуживания.
Docker-деплой
Мой стандартный docker-compose.yml для Caddy:
services:
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3 (QUIC)
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
- caddy-config:/config
- /var/www:/var/www:ro
networks:
- web
api:
image: myapi:latest
container_name: api
restart: unless-stopped
expose:
- "8080"
networks:
- web
frontend:
image: myfrontend:latest
container_name: frontend
restart: unless-stopped
expose:
- "3000"
networks:
- web
volumes:
caddy-data:
caddy-config:
networks:
web:
driver: bridge Важные моменты:
- Порт
443/udpнужен для HTTP/3 (QUIC) — Caddy поддерживает его из коробки - Volume
caddy-dataхранит TLS-сертификаты — не удаляйте его, иначе Caddy запросит сертификаты заново (Let’s Encrypt имеет rate limits) - Бэкенд-сервисы используют
exposeвместоports— они доступны только внутри Docker-сети
Caddyfile для Docker-сетей:
api.example.com {
reverse_proxy api:8080
}
app.example.com {
reverse_proxy frontend:3000
} В Docker-сетях можно обращаться к контейнерам по имени сервиса. Caddy резолвит DNS автоматически.
Caddy API: динамическая конфигурация
Одна из уникальных возможностей Caddy — полноценный REST API для управления конфигурацией без перезагрузки. Caddyfile на самом деле конвертируется в JSON-конфигурацию, и API работает именно с ней.
# Получить текущую конфигурацию
curl http://localhost:2019/config/
# Загрузить новую конфигурацию
curl -X POST http://localhost:2019/load
-H "Content-Type: application/json"
-d @caddy-config.json
# Добавить новый reverse proxy на лету
curl -X POST http://localhost:2019/config/apps/http/servers/srv0/routes
-H "Content-Type: application/json"
-d '{
"match": [{"host": ["new-service.example.com"]}],
"handle": [{
"handler": "reverse_proxy",
"upstreams": [{"dial": "localhost:9090"}]
}]
}' API работает на порту 2019 (по умолчанию только localhost). Это позволяет интегрировать Caddy с CI/CD: деплой нового сервиса автоматически добавляет маршрут в Caddy без перезагрузки.
Для Caddyfile тоже есть hot reload без API:
caddy reload --config /etc/caddy/Caddyfile Или в Docker:
docker exec caddy caddy reload --config /etc/caddy/Caddyfile Basic Auth
Caddy имеет встроенную поддержку basic authentication:
# Генерация хеша пароля
caddy hash-password --plaintext 'my-secure-password' Это вернёт bcrypt-хеш. Используйте его в Caddyfile:
admin.example.com {
basicauth {
admin $2a$14$... # хеш от caddy hash-password
}
reverse_proxy localhost:8080
} Можно ограничить auth для определённых путей:
app.example.com {
basicauth /admin/* {
admin $2a$14$...
}
reverse_proxy localhost:3000
} Rate Limiting
Для rate limiting в Caddy нужен модуль caddy-ratelimit. Его можно добавить через кастомный билд:
# Установка xcaddy (инструмент для сборки Caddy с плагинами)
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
# Сборка Caddy с модулем rate limit
xcaddy build --with github.com/mholt/caddy-ratelimit Или через Docker с кастомным Dockerfile:
FROM caddy:2-builder AS builder
RUN xcaddy build --with github.com/mholt/caddy-ratelimit
FROM caddy:2-alpine
COPY /usr/bin/caddy /usr/bin/caddy Использование в Caddyfile:
api.example.com {
rate_limit {
zone dynamic_zone {
key {remote_host}
events 100
window 1m
}
}
reverse_proxy localhost:8080
} Это ограничивает каждый IP-адрес до 100 запросов в минуту. При превышении Caddy возвращает 429 Too Many Requests.
Полный пример: production Caddyfile
Вот Caddyfile, который я использую на одном из проектов:
{
email admin@example.com
log {
output file /var/log/caddy/access.log {
roll_size 100mb
roll_keep 5
}
format json
}
}
# API с rate limiting и CORS
api.example.com {
log {
output file /var/log/caddy/api.log {
roll_size 50mb
roll_keep 3
}
}
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
-Server
}
reverse_proxy localhost:8080 {
health_uri /health
health_interval 15s
header_up X-Real-IP {remote_host}
}
}
# Frontend (SPA)
app.example.com {
encode gzip zstd
root * /var/www/app
try_files {path} /index.html
file_server {
hide .git .env
}
}
# Grafana
grafana.example.com {
reverse_proxy localhost:3000
}
# Админка с basic auth
admin.example.com {
basicauth {
admin $2a$14$sOm3hAsH3dPaSsWoRd
}
reverse_proxy localhost:8081
}
# Редирект www на non-www
www.example.com {
redir https://example.com{uri} permanent
}
# Основной сайт
example.com {
encode gzip zstd
root * /var/www/site
file_server
} Подводные камни и советы
DNS должен быть настроен до запуска. Caddy пытается получить сертификат сразу при старте. Если DNS A-запись не указывает на ваш сервер — ACME-challenge провалится. Убедитесь, что DNS настроен до запуска Caddy.
Rate limits Let’s Encrypt. Let’s Encrypt позволяет выпустить до 50 сертификатов на домен в неделю. Если вы часто пересоздаёте контейнер и удаляете volume caddy-data — можете упереться в лимит. Никогда не удаляйте этот volume без необходимости.
Порядок директив. В отличие от Nginx, порядок директив в Caddyfile не всегда соответствует порядку выполнения. Caddy использует предопределённый порядок обработки. Если нужен конкретный порядок — используйте route:
example.com {
route {
basicauth {
admin $2a$14$...
}
reverse_proxy localhost:8080
}
} Wildcard-сертификаты. Для wildcard (*.example.com) Caddy требует DNS-challenge. Поддерживаются Cloudflare, Route53 и другие провайдеры через плагины:
*.example.com {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
reverse_proxy localhost:8080
} Логирование. По умолчанию Caddy пишет логи в stdout. Для production настройте вывод в файл с ротацией, как показано в примере выше.
Когда Caddy — правильный выбор
Caddy идеально подходит для:
- Self-hosted проектов — поднять сервис с HTTPS за 5 минут
- Разработки — автоматический HTTPS даже для localhost (через локальный CA)
- Небольших production-сетапов — до десятка сервисов за одним reverse proxy
- Прототипов и MVP — минимум конфигурации, максимум результата
Для высоконагруженных проектов с тысячами RPS, где нужна тонкая настройка worker-процессов, буферизации и кеширования — Nginx остаётся лучшим выбором. Но для 90% задач, с которыми сталкивается DevOps-инженер в повседневной работе, Caddy экономит время и снижает вероятность ошибки в конфигурации. А автоматический HTTPS — это та вещь, к которой привыкаешь мгновенно и не хочешь возвращаться обратно.