Ошибка Too many levels of symbolic links в Debian/Ubuntu встречается чаще, чем кажется. Обычно она всплывает уже после релиза, переноса проекта, правки структуры каталогов или подключения новой схемы деплоя с симлинками вроде current -> releases/.... Выглядит это загадочно, но причина почти всегда приземлённая: цикл ссылок, неверная относительная цель в ln -s или расхождение между тем, что ожидает Nginx, и тем, что реально лежит на диске.
Сообщение может проявляться по-разному. Где-то в логах появится realpath() failed, где-то Nginx отдаст 404 или 500, а где-то shell прямо скажет, что не может пройти по пути из-за слишком большого числа уровней символьных ссылок. Смысл один: система пытается вычислить конечный путь, ходит по цепочке ссылок и упирается в цикл.
Для администратора важно не просто удалить подозрительный symlink, а понять, где именно ломается цепочка. Одна и та же ошибка может быть вызвана неправильным root в Nginx, зацикленным current, неудачной ссылкой внутри public или обычной ошибкой запуска ln -s из другой директории.
Хорошая новость в том, что диагностика здесь довольно прямолинейна. Если последовательно проверить путь, каждую ссылку в цепочке и то, как этот путь видят Nginx и PHP-FPM, проблема обычно находится за несколько минут.
Главное правило: проверяйте не только конечный symlink, но и все каталоги по дороге до него. Цикл часто сидит не в файле сайта, а в одном из родительских элементов пути.
Что означает ошибка на уровне Linux
Символьная ссылка — это специальная запись файловой системы, указывающая на другой путь. Когда приложение пытается открыть файл, ядро последовательно разрешает такие ссылки, пока не дойдёт до реального объекта. Если ссылка указывает сама на себя или образует кольцо через несколько звеньев, путь не может быть вычислен, и система возвращает ошибку уровня ELOOP.
Простейший пример — a -> b и b -> a. Но на практике цикл чаще бывает неочевидным: current ведёт в релиз, внутри релиза public/storage ссылается на shared, а shared по ошибке возвращает вас обратно в current. На глаз всё выглядит аккуратно, а при вычислении абсолютного пути получается петля.
Отдельный источник проблем — относительные симлинки. Команда ln -s target linkname сохраняет цель буквально. Если команда выполнялась не из той директории, которую вы держали в голове, можно получить не просто битую ссылку, а именно цикл.
Где проблема проявляется чаще всего
На веб-серверах ошибка обычно всплывает в трёх местах: Nginx не может открыть файл по root или try_files, PHP-FPM получает скрипт по пути, который не удаётся нормализовать, либо само приложение падает на realpath() при загрузке конфигов, шаблонов, кэша или автозагрузчика.
В логах Nginx это часто выглядит как open() failed (40: Too many levels of symbolic links) или realpath() failed (40: Too many levels of symbolic links). В PHP ошибка может быть замаскирована под сообщение фреймворка о недоступном index.php, каталоге кэша или директории загрузок.
Чаще всего это случается после:
- переезда проекта в новый каталог;
- настройки atomic deploy через
currentиreleases; - изменения структуры
public,webилиhtdocs; - ручного исправления ссылок через
ln -sfбез проверки результата; - смешивания абсолютных и относительных symlink в одном дереве;
- миграции сайта между серверами.
Если вы как раз переносите проект без простоя, полезно заранее проверить схему каталогов и релизных ссылок по чек-листу из материала про миграцию сайта без даунтайма.
Для проектов с собственными релизными схемами и нестандартной файловой структурой такие задачи удобнее разбирать на VDS, где у вас полный контроль над Nginx, PHP-FPM и deploy-логикой.

С чего начать диагностику
Первое правило: не исправляйте вслепую. Сначала найдите точный путь, на котором возникает ошибка. Если это Nginx, смотрите error.log. Если проблема заметна только в shell, попробуйте пройти по пути с помощью ls, readlink и realpath. Если падает PHP-приложение, ищите путь к скрипту, каталогу кэша, include-файлу или загрузкам.
Базовый набор команд для проверки:
pwd
ls -l
ls -l /var/www
ls -l /var/www/site
namei -l /var/www/site/public/index.php
readlink /var/www/site
readlink -f /var/www/site
realpath /var/www/site
Команда namei -l особенно полезна: она раскладывает путь на компоненты и показывает, какой элемент является каталогом, а какой — ссылкой. Это самый быстрый способ увидеть, где именно начинается проблема.
Если readlink -f и realpath не могут вычислить путь, это сильный признак именно зацикленной структуры. Если shell путь видит, а Nginx — нет, тогда уже смотрите права доступа, контекст запуска и соответствие конфигурации реальному дереву каталогов.
Как быстро увидеть цикл
Предположим, у вас есть такая схема:
/var/www/app/current -> releases/2026-04-06
/var/www/app/releases/2026-04-06/public/storage -> ../../../shared/storage
/var/www/app/shared -> current/shared
С первого взгляда всё выглядит правдоподобно. Но если развернуть цепочку, окажется, что shared возвращает вас в current, который ведёт обратно в релиз. Для веб-сервера это уже петля.
Полезно проверить не только один файл, но и всё дерево ссылок:
find /var/www/app -xtype l -ls
find -L /var/www/app -maxdepth 5 -type l -ls
Первой командой удобно искать подозрительные symlink, второй — быстро находить места, где дерево начинает вести себя неправильно. На больших каталогах find -L лучше запускать осторожно, чтобы не утонуть в шумном выводе.
Типовые причины symlink loop
Ссылка указывает сама на себя
Редкий, но очень наглядный случай:
ln -s current current
Такое обычно происходит в скриптах, где переменная уже содержит имя ссылки, а команда выполняется без дополнительной проверки.
Две ссылки указывают друг на друга
ln -s ../b /var/www/a
ln -s ../a /var/www/b
Снаружи это может выглядеть как удобная навигация между каталогами, но по факту любой доступ к одному из путей превращается в бесконечный обход.
Ошибка в относительной цели при деплое
Это самый частый сценарий. Вы создаёте current на новый релиз, а внутри релиза делаете ссылки на shared, storage, uploads или .env. Если ошибиться на один уровень ../, ссылка может уйти не в общий каталог, а обратно в current или соседний релиз.
Nginx смотрит не туда, куда вы ожидаете
Допустим, реальный документ-рут должен быть /var/www/app/current/public, а в конфиге указан /var/www/app/public, который сам является symlink на ../current/public. Если структура каталога уже имеет ошибку, цикл проявится через веб-сервер, хотя корень проблемы лежит в файловой системе.
Разбор для Nginx
Если ошибка проявляется через Nginx, сначала найдите точный виртуальный хост и финальное значение root:
nginx -t
nginx -T | grep -n "root "
Затем проверьте путь вручную:
namei -l /var/www/app/current/public
readlink -f /var/www/app/current/public
stat /var/www/app/current/public
Если используется try_files, помните, что Nginx будет проверять несколько вариантов пути. Поэтому цикл может проявляться только на части URI, а главная страница при этом будет открываться.
Отдельно обращайте внимание на директиву disable_symlinks. Она не создаёт цикл, но может запутать диагностику. Если в логе явно есть Too many levels of symbolic links, сначала исправляйте структуру ссылок, а уже потом разбирайтесь с ограничениями доступа.
На небольших проектах без сложной релизной схемы такие проблемы часто проще избежать на обычном виртуальном хостинге, где структура каталогов обычно предсказуемее и меньше ручной возни с symlink.
Разбор для PHP-FPM и приложений
С PHP-FPM ситуация чуть сложнее, потому что ошибка может возникать не только при открытии входного скрипта, но и уже внутри приложения. PHP, Composer, CMS и фреймворки постоянно используют нормализацию путей, поэтому realpath failed может относиться к кэшу, логам, автозагрузке, шаблонам или временным файлам.
Начните с проверки входного скрипта от имени пользователя веб-сервера:
sudo -u www-data readlink -f /var/www/app/current/public/index.php
sudo -u www-data php -r 'echo realpath("/var/www/app/current/public/index.php"), PHP_EOL;'
Если shell от root путь видит, а www-data — нет, это может быть вопрос прав доступа. Но если есть настоящий цикл, ошибка обычно воспроизводится у всех пользователей одинаково.
Если приложение пишет realpath(): Too many levels of symbolic links, в первую очередь проверьте каталоги storage, bootstrap/cache, var, cache, logs и uploads. Именно их чаще всего выносят в общие директории и подключают ссылками.
Если вы используете панели управления сервером, полезно отдельно проверить, не меняют ли они ожидаемую структуру веб-корня и PHP-пула. В этом может помочь сравнение подходов из статьи про панели управления для VDS.

Как исправить проблему безопасно
Когда цикл найден, не удаляйте всё подряд. Сначала сохраните текущее состояние: вывод ls -l и namei -l для проблемного дерева. На продакшене это банально экономит время, если после правки нужно быстро откатиться.
Рабочая последовательность такая:
- Определите конкретное звено, создающее цикл.
- Удалите только ошибочную ссылку.
- Создайте её заново с проверенной целью.
- Сразу проверьте результат через
readlink -fиnamei -l. - Только потом перезагружайте сервисы и проверяйте сайт.
Пример безопасного исправления:
ls -l /var/www/app/current
namei -l /var/www/app/current/public
rm /var/www/app/current
ln -s /var/www/app/releases/2026-04-06 /var/www/app/current
readlink -f /var/www/app/current/public
namei -l /var/www/app/current/public
Если используете относительные symlink, создавайте их только из заранее известной директории. Для критичных ссылок на продакшене часто безопаснее абсолютные пути. Большая часть странных циклов появляется именно из-за неверного текущего каталога в deploy-скриптах.
Самая опасная команда в такой ситуации —
ln -sfn, запущенная без проверки цели. Она удобна для автоматизации, но при ошибке мгновенно закрепляет неправильную структуру.
Пошаговый чек-лист
Если нужен быстрый алгоритм без лишней теории, действуйте так:
- Найдите точный путь из сообщения об ошибке или лога.
- Проверьте путь командами
namei -l,readlink -f,realpath. - Посмотрите каталоги
current,public,shared,storageиuploads. - Сравните фактический путь с
root,aliasи параметрами приложения. - Удалите только ошибочный symlink и создайте его заново.
- Проверьте путь от имени пользователя веб-сервера.
- После исправления перечитайте логи Nginx и PHP-FPM.
Как избежать ошибки в будущем
Лучший способ профилактики — держать структуру ссылок максимально простой. Один current на релиз, несколько ссылок из релиза на стабильные каталоги вроде shared, и никаких обратных ссылок из shared назад в current.
Второе правило — автоматическая валидация после деплоя. Даже несколько команд с readlink -f, realpath и проверкой document root позволяют поймать ошибку до того, как её увидит пользователь.
Третье правило — не смешивать без причины абсолютные и относительные symlink. Для одиночного сервера абсолютные ссылки чаще проще в сопровождении. Для переносимых сборок относительные допустимы, но требуют более аккуратного тестирования.
И наконец, документируйте дерево каталогов. Когда в проект приходит новый администратор, именно понятная схема ссылок помогает решить инцидент за десять минут, а не ночью по живому серверу.
Итог
Too many levels of symbolic links в Debian/Ubuntu — это почти всегда не каприз Nginx и не странность PHP-FPM, а обычная ошибка в структуре путей. Чтобы исправить её быстро, нужно не гадать, а пройти цепочку ссылок до конца и увидеть, где она замыкается.
Практически всегда работает один и тот же подход: берёте путь из лога, проверяете его через namei -l, readlink -f и realpath, находите неправильную ссылку, пересоздаёте её и повторно валидируете путь от имени веб-пользователя.
Чем сложнее схема деплоя, тем важнее простота symlink-структуры. Пока ссылки образуют понятное дерево, Linux работает предсказуемо. Как только дерево превращается в лабиринт, система честно сообщает об этом первой.


