Привет, друзья! Сегодня у нас будет большой практический разбор, посвященный обновлению серверного веб-стека. Из года в год, настраивая новые серверы, многие системные администраторы по привычке ставят связку из Apache и утилиты Certbot для управления SSL-сертификатами. Мы копируем старые проверенные конфигурационные файлы, настраиваем cron для автоматического восстановления ключей Let’s Encrypt и тратим время на рутинное обслуживание системы.
Но зачем плодить лишний оверхед и усложнять архитектуру, если всю эту работу можно доверить автоматике? Сегодня мы поговорим про Caddy — современный, быстрый и стабильный веб-сервер, написанный на Go. Его главная фишка — ультимативная простота и полностью автоматическое управление безопасностью.
В этой статье мы пошагово разберем, как развернуть Caddy на актуальном Debian 13 (Trixie), как настроить реверс-прокси для внутренних приложений, как полностью заменить им старый Apache для обычных сайтов на WordPress с PHP 8.4 и как без боли перевести на этот стек облачное хранилище Nextcloud. Поехали разбираться!
Часть 1. Почему Caddy — это удобно
Традиционные веб-серверы проектировались в эпоху, когда шифрование трафика было редким исключением, а настройки серверов производились один раз и вручную. В современных реалиях HTTPS стал обязательным стандартом.
Caddy полностью меняет подход к администрированию инфраструктуры:
- Автоматический HTTPS по умолчанию. Больше не нужно ставить Certbot, настраивать cron и следить за обновлением ключей. Caddy сам определяет доменное имя из конфигурационного файла, отправляет запросы в Let’s Encrypt или ZeroSSL, проходит проверку, выпускает сертификаты и сам обновляет их в фоновом режиме.
- Лаконичный конфигурационный файл (Caddyfile). Вместо сотен строк XML-подобного кода Apache, конфигурация Caddy занимает всего несколько строк. Ее легко читать, легко изменять, а шанс совершить случайную ошибку сведен к минимуму.
- HTTP/3 из коробки. Современный быстрый протокол работает без необходимости собирать пакеты из исходников — сервер сам поднимает нужные UDP-сокеты.
Часть 2. Развертывание Caddy штатными средствами Debian 13
Начиная со стабильных версий Debian, веб-сервер Caddy официально включен в базовые репозитории дистрибутива. Вам не нужно скачивать сторонние GPG-ключи или прописывать внешние URL-адреса, рискуя нарушить чистоту системы при глобальном обновлении.
Установка на Debian 13 выполняется стандартным пакетным менеджером apt в одну команду:
sudo apt update && sudo apt install -y caddy
Пакетный менеджер сам создаст системного пользователя caddy, выделит необходимые директории и зарегистрирует службу в systemd. Проверим статус запущенного демона, чтобы убедиться, что система выделила ресурсы под процесс:
systemctl status caddy
Если в терминале горит зеленая строка active (running), значит, базовая часть готова и можно переходить к настройке маршрутов.
Часть 3. Сценарий 1: Настройка реверс-прокси для внутренних приложений
Довольно часто в практике администрирования возникает ситуация, когда на сервере работают изолированные сервисы или панели управления, которые слушают только внутреннюю петлю (localhost) на нестандартных портах — например, :3000 или :5000. Наша задача — безопасно вывести их во внешний мир, повесить на красивый поддомен и защитить SSL-шифрованием.
Открываем главный конфигурационный файл Caddy:
sudo nano /etc/caddy/Caddyfile
Очищаем его содержимое от дефолтных приветственных блоков и пишем простую логику для поддомена:
panel.phoenix901.ru {
reverse_proxy 127.0.0.1:3000 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
В Apache для этого пришлось бы подключать модули проксирования, городить блоки виртуальных хостов по 80 и 443 портам отдельно и вручную прописывать пути к SSL-ключам. В Caddy достаточно указать домен и адрес внутреннего порта. Блок header_up необходим для того, чтобы внутреннее приложение правильно видело реальный IP-адрес внешнего клиента, а не считало, что все запросы идут локально от самого сервера.
Обновим конфигурацию сервера без разрыва текущих сетевых соединений пользователей:
sudo systemctl reload caddy
Часть 4. Сценарий 2: Полная замена Apache для сайтов на WordPress (PHP 8.4)
Если вы вдохновились простотой конфигурации реверс-прокси, то логичный следующий шаг — полностью отказаться от громоздкого Apache, переведя все свои сайты на Caddy. Он отлично справляется как с отдачей статических файлов (HTML, картинок, стилей), так и с обработкой динамических PHP-скриптов.
Допустим, на сервере крутится простой статический сайт и полноценный blog на WordPress с использованием актуальной версии PHP 8.4.
Важный нюанс: файлы конфигурации Apache .htaccess новым сервером игнорируются физически. Caddy не умеет их читать, поэтому всю логику защиты и ЧПУ мы переносим прямо в Caddyfile.
1. Первым делом полностью останавливаем старый Apache и убираем его из автозагрузки, чтобы освободить сетевые порты 80 и 443:
sudo systemctl stop apache2
sudo systemctl disable apache2
2. Открываем наш /etc/caddy/Caddyfile and добавляем конфигурацию для наших сайтов:
# Простой статический сайт
static.phoenix901.ru {
root * /var/www/static_site
file_server
encode gzip
}
# Блог на WordPress с PHP 8.4
phoenix901.ru {
root * /var/www/html/phoenix_blog
file_server
encode gzip
# Безопасность: блокируем доступ к служебным файлам WordPress
@wp-internal {
path /wp-config.php /xmlrpc.php /wp-content/uploads/*.php
path */.*
}
respond @wp-internal "Access Denied" 403
# Перенаправляем обработку PHP на системный сокет PHP 8.4-FPM
php_fastcgi unix//run/php/php8.4-fpm.sock
}
Директива file_server указывает Caddy самостоятельно отдавать файлы из указанного корня root. Конструкция encode gzip сжимает данные на лету, ускоряя загрузку страниц.
Директива php_fastcgi автоматически берет на себя все правила перезаписи URL (Rewrite Rules), которые раньше жили в .htaccess. Она сама перенаправляет запросы к несуществующим адресам на index.php, обеспечивая корректную работу постоянных ссылок (permalink) в WordPress.
Для защиты системы мы добавили блок @wp-internal. Он намертво закрывает внешку от чтения критически важного файла конфигурации базы данных wp-config.php, отключает уязвимый протокол xmlrpc.php (который часто брутфорсят тролли) и запрещает выполнение PHP-скриптов, если их попытаются залить в папку медиафайлов uploads. Точки со звездочкой (*/.*) закрывают от сканирования любые скрытые файлы вроде системных логов или старых бэкапов.
Часть 5. Сценарий 3: Перенос Nextcloud под управление Caddy
Если помимо обычных сайтов у вас на сервере развернуто собственное облачное хранилище Nextcloud, его тоже можно легко избавить от опеки Apache. Nextcloud очень чувствителен к настройкам веб-сервера, заголовкам безопасности и путям веб-окружения (WebDAV).
В архитектуре Caddyfile критически важно соблюдать **строгую иерархию директив**: служебные редиректы .well-known должны быть объявлены в самом верху блока конфигурации. Если написать их ниже вызова php_fastcgi, то FastCGI-тракт перехватит трафик раньше, и правила перенаправления просто проигнорируются.
Добавим блок конфигурации для Nextcloud в наш единый Caddyfile:
cloud.phoenix901.ru {
root * /var/www/nextcloud
file_server
encode gzip
# 1. Срочные редиректы .well-known — выполняются строго до FastCGI
redir /.well-known/carddav /remote.php/dav/ 301
redir /.well-known/caldav /remote.php/dav/ 301
redir /.well-known/webfinger /index.php/.well-known/webfinger 307
redir /.well-known/nodeinfo /index.php/.well-known/nodeinfo 307
# Перенаправление Client Push на выделенный порт
reverse_proxy /push/* 127.0.0.1:7867
# 2. Защита ядра Nextcloud (Исключаем .well-known из бана скрытых файлов)
@nc-blocked {
path /data/* /config/* /db_structure /README /3rdparty/* /lib/* /templates/* /occ /issue*
path /.ncdata
path */.*
not path /.well-known/*
}
respond @nc-blocked "Access Denied" 403
# 3. Обработка PHP-скриптов через сокет PHP 8.4 с флагом фронт-контроллера
php_fastcgi unix//run/php/php8.4-fpm.sock {
env front_controller_active true
}
# Настройки заголовков безопасности, которые требует Nextcloud
header {
Strict-Transport-Security "max-age=15552000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
X-Permitted-Cross-Domain-Policies "none"
X-Robots-Tag "none"
X-XSS-Protection "1; mode=block"
}
}
В этом блоке мы учли все ключевые требования Nextcloud:
- Прописали необходимые HTTP-заголовки безопасности в секции
header, чтобы система не ругалась в административной панели. - Настроили редиректы
redirдля путей.well-known. Обратите внимание: дляwebfingerиnodeinfoиспользуется код ответа307 (Temporary Redirect). Если оставить там дефолтный301, внутренний curl-тестер Nextcloud выдаст ошибку конфигурации. - С помощью комбинированного правила
@nc-blockedзакрыли прямой доступ из веба к служебным каталогам (папкам/data/и/config/). При этом конструкцияnot path /.well-known/*делает нативное исключение для системной папки, позволяя внешним клиентам синхронизировать контакты и календари.
Часть 6. Права доступа и запуск системы
При тотальном переезде с Apache на Caddy часто возникает классический конфликт прав доступа. Исторически в Debian обработчик PHP-FPM работает от имени пользователя www-data. Caddy же запускается под собственным пользователем caddy.
Лучшая административная практика при полном демонтаже старого веб-сервера — **полностью перевести весь веб-стек на изолированного системного юзера caddy:caddy**. Это исключит путаницу в правах и закроет лишние уязвимости.
1. Передаем права на все директории сайтов и облака пользователю и группе caddy:
sudo chown -R caddy:caddy /var/www/static_site
sudo chown -R caddy:caddy /var/www/html/phoenix_blog
sudo chown -R caddy:caddy /var/www/nextcloud
2. Перенастраиваем сам интерпретатор PHP-FPM, чтобы его рабочие воркеры стартовали от имени нужного нам пользователя. Открываем конфигурационный файл пула (например, /etc/php/8.4/fpm/pool.d/www.conf):
sudo nano /etc/php/8.4/fpm/pool.d/www.conf
Находим параметры процесса и unix-сокета, приводя их к единому стеку caddy:
user = caddy
group = caddy
listen.owner = caddy
listen.group = caddy
listen.mode = 0660
Сохраняем изменения и перезапускаем службу PHP-FPM, чтобы пересоздать файл сокета с новыми правами доступа:
sudo systemctl restart php8.4-fpm
3. Перед окончательным запуском обязательно проверяем синтаксис нашего единого конфигурационного файла на наличие опечаток или пропущенных скобок:
caddy validate --config /etc/caddy/Caddyfile
Если утилита сообщает Valid configuration, автоматически выравниваем отступы форматировщиком и мягко перезапускаем службу Caddy. В этот момент он подхватит все прописанные конфигурации, мгновенно сгенерирует под них SSL-сертификаты и выведет проекты в сеть:
caddy fmt --overwrite /etc/caddy/Caddyfile
sudo systemctl restart caddy
Часть 7. Грабли, на которые я наступил (Реальный боевой опыт)
Теория в мануалах всегда выглядит красиво, но когда переносишь реальную живую инфраструктуру с кучей кастомных скриптов, ядро новой системы обязательно проверит тебя на прочность. Вот три реальные проблемы, с которыми я столкнулся при миграции, и их физическое решение.
1. Капкан приоритетов и падение ЧПУ в Nextcloud
В конфигурации Caddyfile директива php_fastcgi имеет наивысший внутренний приоритет обработки. Если вы просто пропишете редиректы служебных путей .well-known ниже по тексту, Caddy перехватит входящие запросы раньше и сразу швырнет их в сокет FastCGI. В итоге мобильные приложения синхронизации отвалятся, а Nextcloud начнет сыпать ошибками.
Решение: Правила redir для WebDAV обязаны стоять на самом верху блока конфигурации хоста, до вызова PHP-обработчика. Более того, коды редиректов для путей webfinger и nodeinfo должны отдавать статус 307 (Temporary Redirect) вместо дефолтного 301, иначе внутренний curl-тестер облака не сможет верифицировать контур.
2. Синтаксис регулярных выражений Go и скрытые файлы
Пытаясь защитить сервер, логично прописать глобальный запрет на чтение скрытых папок через матчер путей path */.* (чтобы никто не вытащил логи, файлы .env или репозитории .git). Но в этот момент Nextcloud полностью деградирует.
Почему? Потому что путь /.well-known/ физически начинается с точки. Универсальный матчер посчитает его запрещенным скрытым файлом и выдаст жесткий 403 Access Denied. Пытаться решить это через CEL-выражения и конструкции Lookahead/Lookbehind (типа (?!well-known)) бесполезно — Caddy написано на Go, а его встроенный движок регулярных выражений RE2 принципиально не поддерживает Perl-совместимый синтаксис PCRE.
Решение: Использовать простую и быструю нативную комбинацию матчеров Caddy в блоке блокировки, которая работает как логическое И (AND) на уровне ядра веб-сервера:
path */.*
not path /.well-known/*
3. Отвал объектного кэша Redis при сносе Apache
Самый неожиданный подвох ждал со стороны кэширования. Если ваш WordPress или Nextcloud общается с локальной базой Redis через Unix-сокет (например, /var/run/redis/redis-server.sock с правами 770), то после удаления пакетов Apache вся связка упадет с ошибкой Error establishing a Redis connection.
Физика процесса проста: когда интерпретатор PHP-FPM переводится на изолированного пользователя, воркеры PHP теряют права на чтение «трубы» сокета Redis, которая исторически принадлежала группе старого веб-сервера. Чтобы вернуть объектный кэш в строй и сохранить минимальную задержку (латентность) дисковых операций, необходимо принудительно добавить пользователя нового веб-сервера в системную группу Redis:
sudo usermod -aG redis caddy
sudo systemctl restart redis-server php8.4-fpm
Заключение
Как видите, перейти на Caddy и полностью убрать Apache из системы можно без боли и долгих мучений с конфигурациями. Мы объединили управление обычными сайтами, реверс-прокси и облачным хранилищем Nextcloud в одном лаконичном файле, избавились от Certbot и полностью автоматизировали работу с HTTPS. Пробуйте, настраивайте и делайте свои серверы проще и надежнее. Стабильного аптайма вашим проектам! Не прощаемся!

Если будете повторять этот переезд, обязательно учитывайте следующие две вещи, иначе словите скрытые аномалии и отвал сервисов.
Во-первых, вырезайте к чертям on_demand из дефолтного блока-заглушки для левых доменов и IP. Если оставить там автоматический выпуск сертификатов по запросу, вы откроете ящик Пандоры. Буквально через пару часов после запуска сервер попал под массированный скан подсетей (Dictionary Attack / Host Header Injection). Боты начали слать пачки запросов с левыми заголовками SNI (всякие мусорные домены, краулеры и левые прокси). Caddy честно пытался под каждый этот левый запрос выписать валидный SSL, забивая процессор генерацией ключей и моментально словив от Let’s Encrypt бан по лимитам (HTTP 429 RateLimited — too many new registrations from this IP). Физика лечения проста: левые запросы по HTTP швыряем на основу, а по HTTPS сервер должен просто жестко сбрасывать соединение на этапе TLS Handshake (Drop TCP-сессии) без всякой TLS-самодеятельности. В дефолтном блоке оставляем только :80 { redir https://phoenix901.ru{uri} permanent }, и боты моментально идут нахер.
Во-вторых, когда мы перевели весь веб-стек и воркеры PHP-FPM на изолированного пользователя caddy:caddy, в воздухе повис встроенный планировщик WordPress. Если у вас настроен автопостинг в Telegram (например, через плагин wp-telegram), после сноса Apache он омертвеет. Встроенный механизм WP-Cron дергается виртуально, когда на сайт заходят люди, но на чистом Caddy с его агрессивным кэшированием внутренние триггеры просто перестают срабатывать, и очередь постов замораживается. Плюс при удалении Apache утилитой apt purge старый системный юзер www-data полностью депривилегируется, блокируя старые хвосты. Решение чисто админское — затыкаем встроенный крон в wp-config.php строкой define(‘DISABLE_WP_CRON’, true); и переводим его на системный демон Debian в sudo crontab -u caddy -e строкой * * * * * /usr/bin/php8.4 -f /var/www/html/phoenix_blog/wp-cron.php >/dev/null 2>&1. После этого асинхронный контур закрывается окончательно: Caddy рубит ботов в зародыше, а системный крон стабильно проталкивает посты в Telegram каждую минуту.
Эксперимент свернут. Из-за параноидальной внутренней логики плагина защиты AIOS (All-In-One Security), который при смене веб-сервера намертво заблокировал отправку комментариев (ошибка 403), контур Caddy пришлось полностью демонтировать.
Чтобы не вырезать боевые плагины брандмауэра и не тратить время на пересчет их баз данных под новый сетевой контекст, я оперативно вернул проверенную временем связку Apache2 (mpm_event) + PHP-FPM.
Продакшен переведен в исходное состояние, права на файлы и сокет Redis возвращены пользователю www-data. Настройки .htaccess снова в строю, формы работают штатно. Стабильность и аптайм — в приоритете.