Выберите продукт

Caddy + PHP-FPM на VDS: авто-HTTPS, HTTP/2/3, сжатие и разбор 502/504

Собираем на VDS связку Caddy + PHP-FPM: ставим пакеты, пишем рабочий Caddyfile для FastCGI, включаем Auto HTTPS и проверяем HTTP/2/3. Затем разбираем 502/504: сокеты, права, systemd-логи, таймауты, slowlog и настройку pm.max_children.
Caddy + PHP-FPM на VDS: авто-HTTPS, HTTP/2/3, сжатие и разбор 502/504

Зачем Caddy для PHP и что важно учесть на VDS

Caddy хорош тем, что закрывает несколько задач сразу: автоматически получает и продлевает сертификаты (Auto HTTPS), включает HTTP/2 «из коробки», умеет HTTP/3 (QUIC) и обычно требует меньше «обвязки», чем классические конфиги.

Для PHP-проектов на VDS типовая схема такая: Caddy принимает HTTP(S), раздаёт статику, а динамику отправляет в PHP-FPM через FastCGI (чаще всего Unix-сокет). Это быстро, безопасно и проще для доступа по правам, чем открывать TCP-порт наружу.

Самые частые проблемы в такой связке — не «как включить HTTPS», а почему внезапно появляются 502 Bad Gateway и 504 Gateway Timeout после деплоя, роста нагрузки или обновления пакетов. Поэтому ниже — не только минимальный конфиг, но и понятный набор проверок, которые реально экономят время в проде.

Базовая установка: Caddy и PHP-FPM

На Debian/Ubuntu PHP-FPM ставится из репозиториев, а Caddy — из пакета (официального или дистрибутивного). Ниже — пример с PHP-FPM; с Caddy логика та же: установить пакет, включить сервис, проверить статус.

sudo apt update
sudo apt install -y php-fpm
sudo systemctl enable --now php-fpm

Важно: на Ubuntu/Debian часто поднимается конкретная версия, например php8.3-fpm или php8.2-fpm, а абстрактный юнит php-fpm может быть алиасом или отсутствовать. Сначала найдите реальное имя сервиса.

systemctl list-units --type=service | grep -E 'php.*fpm'
sudo systemctl status php8.3-fpm

Практика: сначала фиксируем версию PHP-FPM и имя systemd-юнита, потом под неё подбираем путь к сокету и настройки пула. Большинство «необъяснимых» 502 на старте — это неправильный сокет в Caddyfile.

Проверка статусов сервисов Caddy и PHP-FPM через systemd в терминале

Рабочий Caddyfile: статика + PHP через fastcgi

Ниже — базовый «боевой» пример: задаёт корень сайта, включает сжатие, проксирует PHP в FPM через php_fastcgi, раздаёт статику и пишет access-лог в файл.

example.com {
	root * /var/www/example.com/public
	encode gzip

	php_fastcgi unix//run/php/php8.3-fpm.sock

	file_server

	log {
		output file /var/log/caddy/example.com.access.log
		format console
	}
}

Ключевые моменты, которые чаще всего влияют на стабильность:

  • root лучше указывать на публичную директорию (public/web), а не на корень репозитория. Это и безопаснее, и проще для try_files-логики.

  • php_fastcgi unix//run/php/php8.3-fpm.sock обычно предпочтительнее TCP на 127.0.0.1:9000: меньше накладных расходов и проще контролировать доступ к сокету.

  • Не усложняйте роутинг на старте. Сначала поднимите минимальный рабочий конфиг, затем добавляйте правила для API, админки и т.д.

Если нужен явный контроль маршрутизации и обработчиков

Иногда удобнее разделить обработку PHP и статики через матчеры и handle — например, когда хочется отдельно настраивать правила для разных путей или аккуратно расширять конфиг.

example.com {
	root * /var/www/example.com/public
	encode gzip

	@phpFiles path *.php
	handle @phpFiles {
		rewrite * {path}
		php_fastcgi unix//run/php/php8.3-fpm.sock
	}

	handle {
		try_files {path} {path}/ /index.php?{query}
		file_server
	}
}
FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Auto HTTPS в Caddy: как работает и где чаще всего ломается

Auto HTTPS в Caddy означает, что при указании домена в site block сервер сам поднимет TLS и попробует получить сертификат через ACME. В большинстве случаев этого достаточно, но типовые сбои почти всегда про DNS и сеть.

Что чаще всего мешает выпуску сертификата:

  • Домен не указывает на IP вашего сервера (A/AAAA записи не совпадают).

  • Порты 80 и 443 недоступны извне (фаервол на VDS, security group, внешний фильтр).

  • На 80/443 уже слушает другой сервис (Nginx/Apache), и Caddy не может занять порт.

  • Есть AAAA-запись, но IPv6 не настроен (часть клиентов пойдёт по IPv6 и «упрётся в пустоту»).

Быстрая проверка на самом сервере: кто слушает 80/443, и стартует ли Caddy без ошибок.

sudo ss -lntup | grep -E ':(80|443)'
sudo systemctl status caddy

Если сертификат не выпускается — смотрите лог Caddy и конкретную причину отказа.

sudo journalctl -u caddy -n 200 --no-pager

Если вы только поднимаете домен под новый проект, удобнее заранее проверить записи в панели домена. При необходимости домен можно быстро зарегистрировать и привязать через регистрацию доменов, а дальше уже включать Auto HTTPS.

HTTP/2 и HTTP/3: включение и проверка

HTTP/2 в Caddy включается автоматически при наличии TLS. HTTP/3 (QUIC) тоже обычно включён по умолчанию, но у него есть сетевой нюанс: требуется доступность UDP/443.

Что проверить в первую очередь:

  • Открыт ли UDP порт 443 на сервере и на внешнем фаерволе (security group).

  • Нет ли ограничений у провайдера или на маршруте (иногда UDP режется).

sudo ss -lunp | grep ':443'
sudo journalctl -u caddy -n 200 --no-pager

Проверка с клиента (нужна достаточно свежая версия curl, собранная с поддержкой HTTP/3):

curl -I --http2 https://example.com
curl -I --http3 https://example.com

Если --http3 не работает, но HTTPS работает идеально — в 90% случаев проблема в UDP/443. Начинайте с сети, а не с Caddyfile.

Сжатие gzip/brotli в Caddy: что реально включать на проде

В Caddy сжатие включается директивой encode. Самый безопасный вариант, который почти никогда не ломается и везде поддерживается — gzip:

encode gzip

Brotli может зависеть от сборки Caddy и подключённых модулей. Если ваш бинарник поддерживает Brotli, включайте так (и оставляйте gzip как фоллбек):

encode br gzip

Проверить, какие модули есть в конкретном бинарнике:

caddy list-modules | grep -E '^http\.handlers\.encode'

И проверить ответ сервера по Content-Encoding:

curl -I -H 'Accept-Encoding: br' https://example.com
curl -I -H 'Accept-Encoding: gzip' https://example.com

Если хотите глубже разобраться, что и когда стоит сжимать (и почему часть статики лучше не трогать), пригодится отдельный разбор про gzip/brotli: Brotli и gzip: практические настройки и подводные камни.

Reverse proxy headers: что важно передавать приложению

Запросы вроде «caddy reverse proxy headers» обычно появляются, когда приложение начинает «думать», что оно работает по HTTP, теряет реальный IP клиента или строит неправильные абсолютные ссылки. Для php_fastcgi Caddy обычно выставляет нужные параметры сам. Но если вы используете reverse_proxy (например, до Node.js/Go/Java), базовые заголовки стоит держать в голове.

example.com {
	reverse_proxy 127.0.0.1:8080 {
		header_up Host {host}
		header_up X-Forwarded-Proto {scheme}
		header_up X-Forwarded-For {remote}
	}
}

Для PHP-FPM чаще проблема не в заголовках, а в неправильной связке root и маршрутизации. Если приложение отдаёт 404 на существующий PHP-файл — упростите конфиг до минимального php_fastcgi и проверьте, что корень сайта указывает на правильную public-директорию.

Мониторинг протоколов HTTP/2 и HTTP/3 и диагностика ошибок 502 и 504

502 Bad Gateway с PHP-FPM: быстрый разбор причин

502 в связке Caddy→PHP-FPM почти всегда означает одно из трёх: Caddy не может подключиться к сокету/порту, сокет недоступен по правам, либо PHP-FPM падает/не успевает принимать соединения.

1) Проверяем статус PHP-FPM и логи systemd

sudo systemctl status php8.3-fpm
sudo journalctl -u php8.3-fpm -n 200 --no-pager

Если сервис перезапускается по кругу — сначала чините FPM (конфиг пула, синтаксис, расширения). Caddy в этом случае лишь «показывает» проблему.

2) Проверяем сокет: путь и права

ls -la /run/php/
stat /run/php/php8.3-fpm.sock

Сравните путь с тем, что указан в Caddyfile. Типовая ошибка после обновлений — версия PHP изменилась, а в Caddyfile остался старый сокет (например, php8.2-fpm.sock вместо php8.3-fpm.sock).

Если сокет есть, но по правам не пускает, проверьте настройки пула: listen.owner, listen.group, listen.mode. Пример:

; /etc/php/8.3/fpm/pool.d/www.conf
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

Дальше выясните, под каким пользователем работает Caddy, и входит ли он в нужную группу. Идея простая: либо общий сервисный пользователь для веб-стека, либо корректные группы и режим сокета (без «0666»).

3) Проверяем, не упёрся ли PHP-FPM в лимиты пула

На нагрузке PHP-FPM может упираться в количество воркеров и начать копить очередь. Это приводит к «цепочке» проблем: рост времени ответа, а затем 502/504 на фронте. Ключевой параметр здесь — pm.max_children.

; /etc/php/8.3/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 4
pm.max_spare_servers = 8
pm.max_requests = 500

Ориентиры по смыслу:

  • pm.max_children ограничен RAM: каждый процесс PHP потребляет память. Слишком большое значение легко приводит к OOM и перезапускам.

  • pm.max_requests полезен, если есть утечки памяти: воркер перезапускается после N запросов и не «раздувается» бесконечно.

Быстро посмотреть, сколько процессов и как они едят ресурсы:

ps -o pid,ppid,cmd,%mem,%cpu --sort=-%mem -C php-fpm8.3 | head
FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

504 Gateway Timeout: где искать «тормоза»

504 означает, что фронт (Caddy) дождался не ответа, а таймаута. Для PHP это обычно один из сценариев: скрипт реально выполняется слишком долго, запрос стоит в очереди (не хватает воркеров), либо всё упирается в внешние зависимости (БД, API, DNS, диск).

1) Включаем slowlog в PHP-FPM и находим медленные места

Самый практичный способ быстро понять, какой код «висит» — включить slowlog на уровне пула:

; /etc/php/8.3/fpm/pool.d/www.conf
request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm/www-slow.log

Перезапускаем FPM и смотрим лог:

sudo systemctl restart php8.3-fpm
sudo tail -n 200 /var/log/php8.3-fpm/www-slow.log

Если там постоянно одни и те же места — лечить надо приложение и базу (индексы, N+1, кэш), а не веб-сервер.

2) Проверяем лимиты времени выполнения и внешние таймауты

Даже если Caddy готов ждать, PHP может ограничивать выполнение через max_execution_time. А «реальный» таймаут нередко сидит в MySQL/PostgreSQL, клиенте внешнего API или в DNS-резолвере. На проде лучше опираться на наблюдаемость: логировать время запроса на уровне приложения и параллельно смотреть системные метрики (CPU, iowait, память).

Мониторинг: status-страница PHP-FPM и очередь

Чтобы понять, «хватает ли» воркеров, удобно включить status endpoint пула: он показывает активные/idle процессы, очередь, и достигался ли pm.max_children. Это даёт более прикладную картину, чем просто systemctl status.

Включаем в пуле:

; /etc/php/8.3/fpm/pool.d/www.conf
pm.status_path = /php-fpm-status
ping.path = /php-fpm-ping
ping.response = pong

Пути статуса нельзя оставлять открытыми в интернет. Самый простой безопасный вариант — разрешить доступ только с localhost и проверять через SSH-туннель или прямо на сервере.

Проверка локально:

curl -sS http://127.0.0.1/php-fpm-ping
curl -sS http://127.0.0.1/php-fpm-status

Если видите рост listen queue и частые max children reached — это прямой сигнал: текущая конфигурация пула не справляется. Дальше выбор обычно такой: оптимизация (кэш/БД), увеличение ресурсов сервера, либо вынос тяжёлых задач в фоновые очереди.

Практический чеклист: что проверить при 502/504 за 5 минут

  1. Caddy жив и слушает порты? sudo systemctl status caddy, sudo ss -lntup | grep -E ':(80|443)'.

  2. PHP-FPM жив? sudo systemctl status php8.3-fpm, sudo journalctl -u php8.3-fpm -n 200 --no-pager.

  3. Сокет совпадает? путь в Caddyfile соответствует файлу в /run/php/.

  4. Права на сокет? listen.owner/listen.group/listen.mode, пользователь Caddy, группы.

  5. Пул не упёрся? смотрим признаки очереди и pm.max_children, включаем status/slowlog.

  6. Для HTTP/3: UDP/443 открыт, иначе HTTP/3 не взлетит при полностью рабочем HTTPS.

Вывод: стабильный Caddy + PHP-FPM — это сокеты, лимиты и наблюдаемость

Caddy действительно упрощает жизнь: Auto HTTPS, HTTP/2 и понятный Caddyfile позволяют быстро поднять фронт. Но в проде качество связки определяется тем, насколько аккуратно настроены PHP-FPM-пулы и насколько быстро вы диагностируете узкие места.

Держите под рукой проверку сокета, логи systemd, включённый slowlog и статус пула — и 502/504 перестанут быть «магией». А если проект растёт, проще заранее закладываться на масштабирование по ресурсам: подобрать тариф VDS под реальную нагрузку и память под нужный размер PHP-пула.

Поделиться статьей

Вам будет интересно

AppArmor vs SELinux в Debian/Ubuntu: что выбрать и как не сломать прод при hardening OpenAI Статья написана AI (GPT 5)

AppArmor vs SELinux в Debian/Ubuntu: что выбрать и как не сломать прод при hardening

Сравниваем AppArmor и SELinux в Debian/Ubuntu глазами админа: в чём разница моделей, что проще внедрить на проде, как включать мяг ...
systemd-resolved: NXDOMAIN, negative caching, TTL и DNSSEC (SERVFAIL) — диагностика и лечение OpenAI Статья написана AI (GPT 5)

systemd-resolved: NXDOMAIN, negative caching, TTL и DNSSEC (SERVFAIL) — диагностика и лечение

Частая проблема на Linux/VDS: внезапные NXDOMAIN в systemd-resolved, «залипание» из-за negative caching и stale cache, влияние SOA ...
FRR Linux: BGP multihoming, communities и защита от route leak — практический шаблон OpenAI Статья написана AI (GPT 5)

FRR Linux: BGP multihoming, communities и защита от route leak — практический шаблон

Собираем практичную конфигурацию FRR для eBGP multihoming с двумя аплинками: строгий экспорт только своих префиксов, импорт defaul ...