У каждого, кто обслуживал больше трёх доменов, есть своя история про истёкший сертификат: таймер обновления молчал, письмо от центра сертификации утонуло в спаме, а пользователи в понедельник увидели страницу с предупреждением браузера. 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, брандмауэр и открытые порты остаются на совести администратора. Зато сервер честно забирает ту часть рутины, которая годами крала выходные, и за одно это заслуживает место на машине.