У каждого, кто обслуживал больше трёх доменов, есть своя история про истёкший сертификат: таймер обновления молчал, письмо от центра сертификации утонуло в спаме, а пользователи в понедельник увидели страницу с предупреждением браузера. Caddy убирает эту историю целиком: сертификатами занимается сам веб-сервер, без Certbot, без заданий cron и без отдельного этапа в инструкции по установке. Ниже полный путь настройки на актуальной версии: установка в Debian и Ubuntu, запуск нескольких сайтов, перенаправление запросов на внутренние приложения и разбор мест, где новички спотыкаются чаще всего.
Установка Caddy в Debian и Ubuntu из репозитория проекта
В стандартных репозиториях Debian и Ubuntu пакет caddy присутствует, но версия там отстаёт от свежих релизов. Актуальные сборки ставят из официального репозитория проекта, под root команды выглядят так:
apt update
apt install -y debian-keyring debian-archive-keyring curl gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| gpg --batch --armor --dearmor \
-o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| tee /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install -y caddy
После установки система получит службу caddy с автозапуском, конфигурация ляжет в файл /etc/caddy/Caddyfile, а кроме веб-портов служба слушает локальный адрес 127.0.0.1:2019, административный интерфейс, который наружу не торчит. Версия и состояние службы проверяются парой команд:
caddy version
systemctl status caddy
Осенью 2026 актуальна линейка 2.11, свежий релиз 2.11.7 вышел в начале октября. В Fedora и родственных дистрибутивах пакет ставится штатным dnf, а для систем без готовых сборок у проекта есть инструкция по сборке из исходников.
Выпуск и продление сертификатов внутри самого сервера
Механика заслуживает короткого разбора, чтобы не принимать её за волшебство. При старте Caddy читает конфигурацию и находит в ней домены. Для публичных имён он обращается в центры сертификации по протоколу ACME, по умолчанию это публичные центры вроде Let's Encrypt и ZeroSSL. Стандартная проверка владения доменом проходит через HTTP-запрос на порту 80, поэтому он обязан быть открытым наружу. Затем сертификат попадает в хранилище, сервер поднимает HTTPS на 443, а весь HTTP-трафик автоматически получает перенаправление на защищённую версию.
Условия окружения легко проверить заранее: записи A и AAAA домена смотрят на адрес сервера, порты 80 и 443 открыты с внешней стороны, процесс может занять их, каталог данных доступен для записи и сохраняется между перезагрузками, а доменное имя встречается в конфигурации. Когда всё выполнено, первый запуск получает сертификаты за считанные секунды, и руками делать больше ничего не требуется.
Продление работает фоном внутри того же процесса. Обновление стартует задолго до истечения срока, без таймеров, без cron и без хуков перезагрузки. При сбое встроенный клиент не сдаётся: делает паузу и повторяет попытку, меняет способ проверки, переключается на другого эмитента, растягивает паузы между попытками до суток и продолжает историю до тридцати дней, на повторах используя тестовое окружение центра, чтобы не упереться в лимиты. Из чек-листа администратора исчезает целый раздел, и это главная причина пересаживаться на Caddy.
Один нюанс стоит знать заранее: если адрес сайта в конфигурации помечен префиксом протокола без шифрования, автоматический HTTPS для него отключается, что удобно для внутренних сервисов, которым сертификат не нужен:
http://service.example.com {
reverse_proxy 127.0.0.1:8090
}
Структура Caddyfile и проверки перед перезагрузкой
Конфигурация собрана из блоков. Первым может идти глобальный блок в фигурных скобках с общими настройками, за ним блоки сайтов, каждый со своим адресом. Директивы внутри блока читаются сверху вниз, звёздочка после имени директивы означает "применить ко всем запросам", а общие куски подключаются через import, что выручает при десятках однотипных сайтов.
Проверенный порядок работы экономит нервы: сначала проверка синтаксиса, затем форматирование, потом перезагрузка живого сервера без разрыва соединений:
caddy validate --config /etc/caddy/Caddyfile
caddy fmt --overwrite /etc/caddy/Caddyfile
caddy reload --config /etc/caddy/Caddyfile
Если reload не пройдёт, в консоль упадёт причина: занятый порт, опечатка в директиве, недоступный сокет. Журнал службы подсказывает остальное:
journalctl -u caddy -f
Журнал доступа включается директивой log прямо в блоке сайта, формат по умолчанию структурированный JSON, который без усилий уходит в системы анализа:
example.com {
log
root * /var/www/example
file_server
}
Несколько сайтов на одной машине, статика и PHP
Сайт в Caddyfile это блок с адресом. Ниже готовая конфигурация типового набора: основной домен со статикой, зеркало с www, блог на PHP и каталог файлов с листингом:
{
email Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.
}
example.com {
root * /var/www/example
encode zstd gzip
file_server
}
www.example.com {
redir https://example.com{uri} permanent
}
blog.example.com {
root * /var/www/blog
php_fastcgi unix//run/php/php-fpm.sock
file_server
}
files.example.com {
root * /var/www/files
file_server browse
}
Пара пояснений. Глобальная директива email нужна центру сертификации, туда уходят уведомления об ошибках выпуска. Для каждого блока Caddy сам выпустит отдельный сертификат, от администратора требуется только добавить блок и перезагрузить конфигурацию. Директива redir с подстановкой {uri} переносит путь запроса целиком, слова permanent и temporary задают код ответа. Пара php_fastcgi и file_server отдаёт статику блога напрямую, а PHP-файлы заворачивает в пул PHP-FPM через unix-сокет, строка не нужна, если PHP на сервере нет.
Альтернативный расклад, когда оба имени должны отвечать одинаково, делается перечислением адресов в заголовке блока через запятую, и на каждое имя выпускается отдельный сертификат:
example.com, www.example.com {
root * /var/www/example
file_server
}
Новый домен добавляется тремя действиями: DNS-запись, блок конфигурации, перезагрузка. Через несколько секунд после reload сертификат уже получен, и в браузере горит замок. В классической связке тех же действий пять, включая установку Certbot, выпуск сертификата, прописывание путей в конфиге и настройку таймера обновления.
Перенаправление запросов на приложения на локальных портах
Вторая по частоте задача после статики это проброс внешнего домена на приложение, которое слушает локальный порт. В Caddy за это отвечает директива reverse_proxy:
app.example.com {
reverse_proxy 127.0.0.1:8080
}
api.example.com {
reverse_proxy localhost:3000 {
header_up X-Real-IP {remote_host}
}
encode gzip
}
panel.example.com {
reverse_proxy 10.0.0.12:9000 10.0.0.13:9000 {
lb_policy round_robin
health_uri /health
}
}
Что здесь происходит. Заголовки Host, X-Forwarded-For и X-Forwarded-Proto Caddy проставляет сам, X-Real-IP добавлен руками, потому что приложения любят видеть реальный адрес клиента. Websocket-соединения передаются без дополнительных настроек, что важно для панелей управления и чатов. Перечисление нескольких апстримов включает балансировку, lb_policy задаёт стратегию, health_uri заставляет сервер проверять живость бэкендов и отводить трафик от молчащих. Сертификаты для таких доменов выпустятся так же автоматически, как и для статического сайта. Ещё одна мелочь экономит вечер: reload подхватывает изменения без разрыва открытых соединений, поэтому править конфигурацию можно прямо под живым трафиком, пул бэкендов пересоберётся на ходу.
Само приложение при этом слушает только 127.0.0.1 или внутреннюю сеть и наружу не торчит. Даже если в его коде найдётся дыра, наружный периметр прикрыт веб-сервером.
Локальный HTTPS, wildcard домены и выдача по запросу
Для внутренних адресов публичные центры сертификаты не выпускают, но Caddy и тут не оставляет админа с самоподписанными заглушками ручной сборки. Для localhost и IP-адресов сервер создаёт собственный центр сертификации и выпускает через него короткоживущие сертификаты, а при первом запуске предлагает установить корневой сертификат в хранилище доверия, после чего браузер перестаёт ругаться. Корневой сертификат локального центра и выпущенные копии лежат в каталоге данных службы, по умолчанию это /var/lib/caddy; при потере каталога всё выпускается заново. Для внутреннего домена с обычным видом достаточно директивы tls internal:
intranet.example.com {
tls internal
reverse_proxy 127.0.0.1:8081
}
Wildcard-имена вида *.example.com тоже поддерживаются, но с ограничением: звёздочка допускается только в крайней левой метке, составные варианты правилами инфраструктуры доверия не разрешаются. Проверка владения wildcard требует DNS-записи, поэтому нужен плагин провайдера DNS, который собирается утилитой xcaddy, для Cloudflare команда такая:
xcaddy build --with github.com/caddy-dns/cloudflare
После сборки в глобальном блоке указывается опция acme_dns с именем плагина, а токен хранится в переменной окружения. Начиная с версии 2.10 Caddy использует один wildcard-сертификат сразу для всех поддоменов конфигурации и не плодит отдельные для каждого.
Платформам с тысячами пользовательских доменов адресована выдача по запросу: сертификат получается в момент первого TLS-рукопожатия, а имена заранее в конфиге не перечисляются. Режим обязан быть ограничен запросом ask, по которому Caddy спрашивает у внутреннего бэкенда, есть ли право выпускать сертификат для пришедшего имени:
{
on_demand_tls {
ask http://localhost:8082/check
}
}
*.customers.example.com {
tls {
on_demand
}
reverse_proxy 127.0.0.1:8082
}
Первое рукопожатие с новым доменом занимает несколько секунд, остальные идут мгновенно из кэша, продление снова работает фоном. Для экспериментов предусмотрен тестовый режим, в котором глобальной опцией acme_ca подменяется адрес тестового окружения центра, чтобы не расходовать лимиты выпусков на боевых доменах:
{
acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
Типичные проблемы первого запуска и порядок диагностики
Девять из десяти неудач объясняются пятью причинами. Брандмауэр режет порты 80 и 443, из-за чего центр сертификации не может подтвердить домен. Запись DNS ещё не обновилась, и проверка уходит на старый адрес. Порты заняты Apache или Nginx, оставшимся после переезда, и служба просто не стартует. Каталог данных недоступен по правам, о чём журнал пишет прямо. Правила брандмауэра проверяют изнутри через ufw или nftables, а снаружи любым независимым средством проверки портов. Для HTTP/3 требуется ещё и UDP на 443 порту, без него клиенты молча откатятся на TCP, ошибки не будет, но и преимуществ протокола не будет тоже.
Команды первичной проверки:
ufw allow 80,443/tcp
ufw allow 443/udp
ss -tlnp | grep -E ':(80|443)'
dig +short example.com
curl -v https://example.com
journalctl -u caddy --since today
Порядок действий, когда домен не открывается с замком, обычно такой:
1. убедиться, что имя резолвится в адрес сервера и запись обновилась у провайдера DNS;
2. проверить с внешней машины, что порты 80 и 443 доступны, любым внешним средством проверки;
3. посмотреть через ss, не заняты ли порты старым веб-сервером, и остановить лишнюю службу;
4. открыть журнал caddy в journalctl, причина чаще всего описана в последних строках;
5. повторить запрос через curl с флагом -v и убедиться, что TLS-рукопожатие проходит чисто.
Когда настройка третьего и десятого сайта сводится к трём строкам конфигурации и одной команде reload, а продление сертификатов перестаёт быть строкой в графике дежурств, вечер пятницы возвращается к законным делам. Caddy не отменяет понимание сети: DNS, брандмауэр и открытые порты остаются на совести администратора. Зато сервер честно забирает ту часть рутины, которая годами крала выходные, и за одно это заслуживает место на машине.