MAATRIX / Блог / Никто не знает, где лежат исходники сайта: ищем по живому серверу

Никто не знает, где лежат исходники сайта: ищем по живому серверу

MAATRIX

Сайт site.ru открывается, отвечает на запросы, никаких ошибок — а на вопрос «где физически на диске лежат его файлы» никто в команде ответить не может. Подрядчик, который его разворачивал, недоступен, конфиг веб-сервера то ли есть, то ли давно перезаписан, а искать наугад по /var/www и /home можно час и не факт что найти то самое место, а не старую копию или чужой сайт с похожим названием. Задача решаемая и не требует магии: сервер сам знает, откуда отдаёт контент, нужно только правильно у него спросить — через конфиг, через процесс, который реально слушает порт, и как последний резерв — сверив содержимое ответа с файлами на диске.

Чем эта задача отличается от поиска всех сайтов на сервере

Важно сразу развести два похожих, но разных вопроса. Если вы не знаете, сколько вообще сайтов и доменов обслуживает сервер — это отдельная и более широкая задача, разобранная в статье «Сколько сайтов на этой машине: ищем проекты, о которых вам не сказали». Там методика идёт от машины к списку сайтов: перебираются все конфиги, все каталоги, все DNS-записи, и в результате получается перечень доменов, о которых, возможно, никто не помнил.

Здесь ситуация обратная и уже, а значит и решается точнее. Сайт один, он конкретен, он работает и доступен по известному URL. Неизвестна только одна вещь — физический путь к его исходникам на диске этого сервера. Вы не перебираете все сайты подряд, а идёте от одного известного домена вглубь: сначала спрашиваете веб-сервер напрямую, и только если он не даёт прямого ответа — спускаетесь на уровень процесса и файловой системы. Это быстрее и надёжнее, чем ловить нужный каталог интуицией среди десятка похожих папок.

Шаг 1: спросите конфиг веб-сервера — обычно ответ уже написан

Первое, что стоит проверить — не гадать по каталогам, а посмотреть, что сам веб-сервер считает корнем сайта. Для nginx самый надёжный способ — не читать файлы конфигов руками, а попросить сам процесс показать итоговую, уже собранную конфигурацию:

nginx -T 2>/dev/null | grep -B5 -A15 "server_name site.ru"

Флаг -T выводит полный эффективный конфиг со всеми include, поэтому находка гарантированно актуальна — в отличие от grep по файлам в sites-available, где может лежать неактивный или устаревший вариант. В выводе ищите директиву root — это и есть корень сайта на диске. Если рядом встречается alias в конкретном location — учтите, что alias подменяет путь только для этого блока location и может указывать в совершенно другое место, чем общий root сервера, это частый источник путаницы.

Для Apache аналог — встроенная диагностика конфигурации:

apachectl -S
apache2ctl -t -D DUMP_VHOSTS
grep -A20 "ServerName site.ru" /etc/apache2/sites-enabled/*.conf

Ищите директиву DocumentRoot внутри нужного VirtualHost. Если на сервере Caddy — там проще всего, путь к файлам обычно значится как директива root * /path/to/site прямо в блоке домена в Caddyfile, либо смотрите активную конфигурацию через API:

curl -s localhost:2019/config/ | python3 -m json.tool | grep -A3 '"root"'

Три нюанса, о которые здесь спотыкаются чаще всего. Во-первых, root может быть объявлен не в server-блоке нужного домена, а наследоваться из http {} уровня выше — если в самом server-блоке директивы нет, ищите её в общем контексте. Во-вторых, найденный путь иногда оказывается симлинком на что-то ещё — обязательно разворачивайте его до реального пути:

readlink -f /var/www/site.ru/public

Это особенно важно на серверах с деплоем через Capistrano-подобные схемы, где current — симлинк на releases/20260827120000, и реальные файлы физически лежат в каталоге релиза, а не там, где выглядит на первый взгляд. В-третьих, если сервер за панелью управления (aaPanel, FastPanel, ISPmanager) — панель может генерировать конфиг динамически, и быстрее посмотреть путь прямо в интерфейсе панели, чем искать вручную сгенерированный файл на диске.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Шаг 2: конфига нет или он не помог — ищем процесс, который слушает порт

Если веб-сервер не даёт прямого ответа — сайт может обслуживаться не классической связкой nginx+статика, а отдельным приложением на своём порту (Node.js, Python-приложение через Gunicorn/uWSGI, Go-бинарник), к которому nginx просто проксирует трафик. В этом случае нужно найти сам процесс, который реально держит соединение.

Сначала узнайте, какой процесс слушает порт, на который приходит трафик сайта:

ss -tlnp | grep -E ':80|:443'
sudo lsof -i :443 -sTCP:LISTEN

Вывод даст PID процесса. Если это nginx или Apache — вы уже на шаге 1, просто идите через конфиг. Но если это что-то вроде node, gunicorn, php-fpm или неизвестный бинарник — вы нашли реального исполнителя, и дальше нужно выяснить, откуда он читает файлы.

Частый сценарий — цепочка проксирования: внешний nginx принимает запрос на 443, но внутри location стоит proxy_pass http://127.0.0.1:3000; — сайт фактически отдаёт бэкенд-приложение на внутреннем порту. Найдите это в конфиге:

nginx -T 2>/dev/null | grep -A3 "location /" | grep proxy_pass

И повторите поиск процесса уже для найденного порта:

ss -tlnp | grep :3000
ps -p <PID> -o pid,ppid,cmd

Команда ps с флагом cmd часто сразу показывает путь — многие процессы запускаются с абсолютным путём к точке входа в аргументах, например node /var/www/app/server.js или /usr/bin/php-fpm --fpm-config /etc/php/8.3/fpm/pool.d/site.conf. Во втором случае путь к сайту придётся смотреть уже не в самом процессе, а в найденном pool-конфиге PHP-FPM — там есть переменная PHPRC или окружение с DOCUMENT_ROOT, если оно передаётся из nginx через fastcgi_param.

Шаг 3: PID есть — смотрим /proc и lsof на рабочий каталог и открытые файлы

Когда PID процесса известен, но аргументы командной строки не дали абсолютного пути (процесс запущен через systemd-юнит с относительным путём, через supervisor или обёрнут в скрипт-лаунчер), следующий источник правды — сам процесс в файловой системе /proc.

Рабочий каталог процесса на момент запуска:

sudo readlink -f /proc/<PID>/cwd

Это часто и есть искомый корень — особенно для приложений, которые запускают из своей же директории (типичное поведение для Node.js, Python и большинства фреймворков, стартующих через cd /path/to/app && npm start в systemd-юните с директивой WorkingDirectory).

Если cwd показывает что-то общее вроде / или /home/deploy — это ещё не провал, идите дальше и смотрите список реально открытых файлов процесса:

sudo lsof -p <PID> | head -50

В выводе lsof обращайте внимание на несколько типов записей: строку cwd (рабочий каталог), txt (исполняемый файл и загруженные библиотеки — для интерпретируемых языков здесь может мелькнуть путь к самому скрипту), и обычные открытые файлы (REG в колонке TYPE) — открытые логи, файлы шаблонов, загруженные пользователями медиа, сессионные файлы. Даже если исполняемый файл лежит в системном месте вроде /usr/bin/node, открытые им регулярные файлы почти всегда укажут на реальный каталог сайта:

sudo lsof -p <PID> | grep REG | grep -v "\.so"

Для PHP-FPM отдельная особенность: у пула обычно несколько worker-процессов с одинаковым родителем, и cwd мастер-процесса может быть /, а вот у активного в момент запроса worker'а среди открытых файлов будет виден конкретный .php-файл, который он в этот момент выполняет — если поймать процесс в момент обработки запроса, это прямое попадание:

for pid in $(pgrep -f php-fpm); do echo "PID $pid:"; sudo lsof -p $pid 2>/dev/null | grep '\.php'; done

Особый случай: сайт в Docker — путь есть, но не на хосте

Если процесс, слушающий порт, оказался docker-proxy или containerd-shim — сайт крутится в контейнере, и искать его файлы через lsof на хосте бесполезно: контейнер живёт в своём файловом пространстве имён. Здесь нужен другой путь.

Сначала найдите контейнер по проброшенному порту:

docker ps --format "table {{.Names}}\t{{.Ports}}" | grep -E "80->|443->"

Дальше смотрите точки монтирования — именно они определяют, где на хосте физически лежат файлы, доступные внутри контейнера:

docker inspect <container> --format '{{range .Mounts}}{{.Type}}: {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'

Если тип монтирования bind — в колонке Source будет прямой путь на хосте, и это ответ на вопрос. Если тип volume — источник будет выглядеть как имя тома, а не путь; реальные файлы в этом случае физически лежат под /var/lib/docker/volumes/<имя_тома>/_data/, и трогать их напрямую в обход Docker стоит с осторожностью — некоторые драйверы хранения не гарантируют, что это простой каталог на диске хоста.

Отдельная ловушка — если контейнер вообще не использует монтирование, а собирает файлы сайта прямо в образ на этапе docker build (частый паттерн для статических сборок React/Vue/Next.js). В этом случае на хосте отдельного каталога с исходниками попросту нет — есть только образ, и «исходники» в смысле «файлы, которые нужно редактировать руками на сервере» физически не существуют; редактировать нужно исходный репозиторий и пересобирать образ, а не искать файл на диске. Проверить это можно, заглянув внутрь работающего контейнера:

docker exec <container> ls -la /app
docker exec <container> cat /app/Dockerfile 2>/dev/null

Если посмотреть на процессы контейнера и открытые ими файлы всё же нужно — используйте docker top вместе с nsenter, или проще: зайдите внутрь через docker exec и там повторите тот же lsof/ps, что и на шаге 3, но уже в контексте контейнера, а не хоста.

Последний резерв: сверяем содержимое ответа сервера с файлами на диске

Бывают случаи, когда конфиг не помог, процесс — какой-то самописный бинарник без внятного cwd, а нужного контейнера нет. Тогда остаётся прямой, но надёжный метод: взять что-то уникальное из ответа сервера и найти это же самое на диске.

Заберите HTML-ответ сайта и выделите строку, которая маловероятно встречается где-то ещё — не общую фразу, а что-то специфичное: комментарий разработчика, уникальный class-атрибут, кусок текста из футера, который точно не шаблонный:

curl -s https://site.ru/ | grep -o 'уникальный текст со страницы'

Дальше ищите этот текст по всем вероятным местам файловой системы:

sudo grep -rl "уникальный текст со страницы" /var/www /home /srv /opt 2>/dev/null

Для сайтов на статических сборщиках (Hugo, Gatsby, Next.js export) в HTML почти всегда есть имена сгенерированных ассетов с хешем в названии — app.4f8a21bc.js, styles.9c7e3d.css. Такое имя файла практически уникально, и искать его быстрее, чем текст:

curl -s https://site.ru/ | grep -oE '[a-zA-Z0-9_/-]+\.[a-f0-9]{6,10}\.(js|css)' | head -5
find / -xdev -name "app.4f8a21bc.js" 2>/dev/null

Здесь стоит сделать честную оговорку: метод работает не всегда и не идеально. Если сайт отдаётся через CDN или обратный прокси с кэшированием на другом сервере — вы можете искать содержимое, которое физически лежит совсем не на той машине, куда у вас есть доступ по SSH; сначала убедитесь, что подключены именно к origin-серверу, а не к edge-узлу CDN. Если несколько сайтов на сервере используют одну и ту же тему или общий шаблон (частая ситуация у агентств, штампующих лендинги с одной вёрсткой) — совпадение текста может дать несколько кандидатов сразу, и тогда придётся сужать поиск более специфичной строкой или переходить к проверке из следующего раздела.

Как убедиться, что нашли именно тот каталог, а не похожий

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

echo "probe-$(date +%s)" | sudo tee /var/www/site.ru/probe-check-8271.txt
curl -s https://site.ru/probe-check-8271.txt

Если содержимое совпало — вы нашли рабочий корень, и после проверки файл стоит сразу удалить. Если сайт закрыт от прямого доступа к произвольным файлам (например, весь трафик идёт через index-роутер SPA-приложения, и статические файлы вне сборки не отдаются) — тот же принцип можно применить наоборот: временно изменить существующий, заведомо безопасный файл (например, добавить HTML-комментарий в футер) и проверить, появилось ли изменение на живом сайте после обновления страницы.

Дополнительно сверьте даты изменения файлов с тем, что вы знаете о последнем деплое — если в команде помнят, что последнее обновление сайта было условно в июле 2026 года, а stat показывает изменения на порядок старше или новее, это повод усомниться, что каталог действительно тот:

find /var/www/site.ru -type f -newermt "2026-07-01" -printf '%T@ %p\n' | sort -n | tail -10

И последняя проверка — владелец и права файлов должны соответствовать пользователю, от которого предположительно идёт деплой (www-data, deploy, кастомный сервисный аккаунт). Каталог с правами root и датами пятилетней давности, в котором сайт формально «работает» — обычно означает, что вы нашли не активную копию, а забытую, а трафик реально отдаётся откуда-то ещё через символическую ссылку или альтернативный location.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Сайт работает через Cloudflare или другой CDN — как узнать, куда идти по SSH?

Сначала найдите реальный origin-IP: посмотрите DNS-историю сайта до подключения CDN (если она у вас сохранилась), проверьте конфиги на самом предполагаемом origin-сервере, либо обратитесь к тому, кто настраивал прокси. Подключаться нужно именно к origin-серверу — на edge-узлах CDN исходников сайта физически нет, там только кэш.

Нашёл каталог, но у пользователя, от которого работает процесс, нет прав на запись в него — это точно не то место?

Не обязательно. Часть деплоев сознательно делает боевые файлы доступными только на чтение для веб-сервера, а запись идёт от отдельного deploy-пользователя через CI/CD. Проверяйте не права на запись, а совпадение содержимого — через тестовый файл или сверку хеша ассетов, как описано выше.

Сайт на общем (shared) хостинге без root-доступа — работают ли эти методы?

Частично. nginx -T и lsof там обычно недоступны, но панель хостинга (cPanel, ISPmanager, Plesk) сама показывает путь к корню домена в разделе управления доменами — это первое, куда стоит смотреть в такой ситуации, отдельная диагностика через процессы не нужна.

Что делать, если по всем трём шагам находится два разных каталога с одинаковым содержимым?

Такое бывает при незавершённом переезде — старая и новая копии существуют одновременно, а трафик реально идёт через одну из них. Решает проверка с тестовым файлом из последнего раздела: измените файл только в одном из кандидатов и проверьте, отразилось ли это на живом сайте.

Стоит ли после находки сразу фиксировать результат где-то, кроме памяти того, кто искал?

Да, иначе через полгода искать придётся заново. Если на сервере уже ведётся паспорт сервера — впишите найденный путь туда одной строкой; отдельного документа под один сайт заводить не нужно.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →