Ошибка Too many levels of symbolic links почти всегда означает одно: в файловой системе получилась петля символических ссылок, и ядро возвращает ELOOP при попытке «развернуть» путь. В веб‑стеке это обычно проявляется как open() failed в Nginx и как ошибки include/require/autoload в PHP‑приложении через PHP‑FPM (иногда не сразу — из‑за кэша realpath).
Ниже — практичная инструкция для продакшена: как быстро доказать, что это именно symlink loop, найти место зацикливания и исправить так, чтобы проблема не возвращалась при следующем деплое с переключением current.
Как выглядит ELOOP: симптомы в логах Nginx и PHP
Типовые признаки, по которым проще всего начать расследование:
- В
/var/log/nginx/error.log:open() "/path/to/file" failed (40: Too many levels of symbolic links). - В PHP‑логах/STDERR пула: ошибки открытия файлов, падение автозагрузчика, проблемы с шаблонами и конфигами.
- Время появления совпало с релизом, где
currentпереключали на новый каталог (releases/shared схема).
Важно: «40» в сообщении Nginx на Linux — это тот же
ELOOP. И Nginx, и PHP видят только факт: конечный файл недостижим, потому что цепочка симлинков зациклилась или стала «слишком длинной».
Откуда берётся петля: частые причины при деплое через current/releases
В реальных инцидентах чаще всего встречаются такие сценарии:
- Петля current ↔ releases: например, релизный каталог (или его часть) ссылается обратно на
currentили на родителя, аcurrentпри этом указывает на релиз. - Относительные пути в
ln -sиз «не того» каталога: скрипт создаёт ссылку, но target получается не тем, что вы ожидали. - Конфликт shared ↔ release: сделали
current/storage→shared/storage, а позже (или другим скриптом) появилосьshared/storage→current/storage. - Перенос релизов rsync/архивацией: симлинки «переехали» иначе, чем вы думали (особенно при разных ключах сохранения/разыменования ссылок).
- Инструменты приложения: CMS‑плагины, генераторы статики, сборщики ассетов, которые создают симлинки во время работы и «перекрещивают» дерево.
Если проект крутится на нескольких инстансах или вы хотите быстро масштабироваться, удобнее держать релизную схему на VDS: проще контролировать файловую структуру, права и перезапуски сервисов в одном месте.
Перед тем как править, зафиксируйте текущую картину: куда указывает current, что лежит в shared, и какие симлинки появились «внутри» релиза. Это ускорит разбор, если ошибка повторится.

Диагностика: как быстро доказать symlink loop и найти точку зацикливания
Начинайте от конкретного пути, который фигурирует в ошибке. Не «сканируйте весь сервер», пока не проверили один проблемный файл/каталог из error.log.
Шаг 1. Посмотреть, куда реально указывают ссылки: ls -la
Проверяем ключевые узлы схемы деплоя:
ls -la /var/www/site
ls -la /var/www/site/current
ls -la /var/www/site/releases
ls -la /var/www/site/shared
Ищите «подозрительные» направления: ссылки из релиза или shared назад на current или на верхние директории проекта.
Шаг 2. Получить канонический путь: readlink -f
readlink -f разворачивает путь до конца. При петле часто вернёт ошибку или не сможет получить результат.
readlink -f /var/www/site/current
readlink -f /var/www/site/current/public/index.php
readlink -f /var/www/site/current/vendor/autoload.php
Если команда «спотыкается», у вас почти наверняка именно ELOOP из-за симлинков (а не, скажем, права или отсутствующий файл).
Шаг 3. Найти симлинки, которые «утягивают» дерево: find -L
Ключ: -L заставляет find следовать по симлинкам. На зацикливании он начинает натыкаться на проблему и это видно по поведению и сообщениям.
find -L /var/www/site/current -maxdepth 5 -type l -ls
find -L /var/www/site -maxdepth 7 -type l -ls
Смотрите, какие ссылки ведут «не туда»: назад в current, в родителя или в неожиданные места вне структуры releases/shared.
Шаг 4. Когда readlink не помогает: namei -l по компонентам
Если цепочка сложная и нужно увидеть «на каком шаге» путь начинает ездить по кругу:
namei -l /var/www/site/current/public/index.php
Это удобно ещё и тем, что сразу видны права на каждом компоненте (бывает полезно, когда параллельно есть проблема с доступом).
PHP-FPM и realpath_cache: почему симптомы могут «плавать»
После переключения current PHP может вести себя непредсказуемо, если воркеры держат старые развернутые пути в realpath cache. В итоге возможна ситуация: симлинки уже починили, Nginx уже «видит» корректный путь, а часть PHP‑воркеров продолжает пытаться включать файлы по старым, уже невалидным цепочкам.
Быстро посмотреть настройки кэша:
php -i | grep -E 'realpath_cache_size|realpath_cache_ttl'
Для расследования иногда полезно временно уменьшить realpath_cache_ttl, но главный практический шаг после исправления симлинков — корректно перезагрузить PHP‑FPM (reload или restart), чтобы воркеры сбросили состояние.
Где смотреть ошибки PHP-FPM, если всё под systemd
Не ограничивайтесь только файловыми логами пула: journald часто быстрее показывает картину по времени деплоя.
systemctl status php-fpm
journalctl -u php-fpm --since "1 hour ago"
journalctl -u php8.3-fpm --since "1 hour ago"
Имя юнита зависит от дистрибутива/версии. Ищите по ключевым словам ELOOP, Too many levels, realpath.
Если вы разворачиваете проекты на общем сервере и хотите уменьшить риск таких инцидентов за счёт предсказуемого окружения, посмотрите тарифы на виртуальный хостинг (для типовых PHP‑сайтов) или переходите на VDS (если нужна полная свобода с системными настройками и деплоем).
Как исправить петлю без даунтайма: два рабочих сценария
Точная тактика зависит от того, где именно образовалась петля. Важно: не «чистите всё подряд» — ваша задача вернуть current в корректное состояние и убрать встречные ссылки, создающие круг.
Сценарий A: зациклился current
Если проблема в current (например, он указывает не туда или на относительный target, который сам приводит обратно), самый безопасный путь — пересоздать current как ссылку на правильный релиз.
Проверка перед правкой:
cd /var/www/site
ls -la current
ls -la releases
Пересоздание симлинка:
cd /var/www/site
rm -f current
ln -s releases/2026-01-22_120000 current
ls -la current
readlink -f /var/www/site/current
Дальше — применяем reload:
systemctl reload php-fpm
systemctl reload nginx
Если видите, что часть воркеров продолжает «жить прошлым» (типично при realpath‑кэше и долгоживущих процессах), лучше сделать restart PHP‑FPM в окно минимальной нагрузки:
systemctl restart php-fpm
Сценарий B: петля между shared и релизом
Это самый коварный вариант в схеме releases/shared/current. Базовый принцип простой:
- shared не должен ссылаться на current или конкретный релиз.
- Ссылки должны идти «из релиза в shared», а не наоборот.
Проверяем, нет ли ссылок из shared на current:
find /var/www/site/shared -maxdepth 5 -type l -ls
Если видите что-то вроде shared/storage -> /var/www/site/current/storage, это почти гарантированная причина петли (особенно если в релизе сделана обратная ссылка current/storage → shared/storage). Исправление: удаляем или пересоздаём проблемный симлинк в shared так, чтобы он указывал на реальный каталог внутри shared, а не на current.
Когда виноват не деплой: связка root/alias/try_files в Nginx
Бывает, что петля действительно есть, но вы ищете её «не там», потому что путь сформирован логикой Nginx. Два частых источника путаницы:
rootв одном контексте и неожиданныйtry_files, который проверяет альтернативные кандидаты (петля может быть во втором или третьем варианте).aliasвlocation(он подставляет путь иначе, чемroot), и ошибка проявляется только на части URI.
Снимите эффективный конфиг и восстановите соответствие URI → filesystem:
nginx -T | sed -n '1,220p'
Дальше находите нужный server_name, смотрите root или alias, затем try_files. Если вас интересует ещё и «ветвление» логики в Nginx, может пригодиться материал про nginx map для кеширования форматов WebP/AVIF: там наглядно видно, как альтернативные пути в конфиге приводят к неожиданным обращениям к файловой системе.
Как предотвратить ELOOP в будущем: безопасный паттерн deploy symlink
В продакшене лучше один раз «зашить» защиту в процесс деплоя, чем потом ловить ELOOP ночью.
1) Делайте ссылки абсолютными или жёстко контролируйте рабочую директорию
Большинство случайных петель появляются из-за относительных путей, созданных из неверного каталога. Если используете относительные ссылки — фиксируйте cd перед ln -s и проверяйте результат readlink.
2) Добавьте preflight-проверку в CI/CD перед переключением current
Ещё до перевода трафика на новый релиз проверяйте, что ключевые файлы корректно развернутся:
readlink -f /var/www/site/releases/2026-01-22_120000/public/index.php
readlink -f /var/www/site/releases/2026-01-22_120000/vendor/autoload.php
И что в shared нет ссылок на current:
find /var/www/site/shared -maxdepth 5 -type l -print
3) После переключения current перезагружайте сервисы осознанно
Для Nginx обычно достаточно reload. Для PHP‑FPM reload часто нормальный вариант, но при странностях из‑за realpath и долгоживущих воркеров быстрее и честнее сделать restart в контролируемое окно.
4) Держите под рукой «ночной» набор команд
tail -n 200 /var/log/nginx/error.log
journalctl -u nginx --since "30 min ago"
journalctl -u php-fpm --since "30 min ago"
Если вы дополнительно используете кеши (Redis/Memcached) и логика приложения «разветвляется», полезно иметь понятную картину, где именно приложение берёт пути и артефакты. В этом контексте может быть полезна заметка про кеширование в PHP через Redis и Memcached — она помогает быстрее исключать «не файловые» причины похожих симптомов.

Шпаргалка: что делать, когда «горит»
- Возьмите точный путь из
open() failedи проверьте егоreadlink -f. - Проверьте
currentи его target:ls -la. - Просканируйте симлинки по дереву:
find -Lс ограничением глубины. - Если петля shared ↔ release — уберите ссылки из shared в current (shared должен быть «реальным»).
- Сделайте reload или restart PHP‑FPM (учитывая
realpath_cache_ttl) и reload Nginx. - Добавьте preflight‑проверки в деплой, чтобы ELOOP больше не повторялся.
Если у вас атомарные релизы и несколько сервисов (Nginx, PHP‑FPM, воркеры), воспринимайте симлинки как контракт: один неверный указатель превращается в ELOOP, который выглядит как «упал веб». Но с диагностикой через readlink -f, find -L, namei и journald проблема обычно находится за 5–15 минут и потом устраняется на уровне процесса деплоя.


