Node.js-приложение в cPanel возвращает ошибку 503: проверяем Passenger и startup-файл
Ошибка 503 Service Unavailable у Node.js-приложения в cPanel не обязательно означает, что недоступен весь сервер. Домен может открываться, статические файлы отдаваться нормально, а запрос к URI приложения заканчиваться 503. В такой ситуации перезапускать всё подряд, удалять node_modules или менять права на весь каталог — плохой первый шаг.
Диагностику удобнее вести по цепочке запроса: сначала проверить сам URL и регистрацию приложения в Application Manager, затем Passenger и startup-файл, после этого запустить тот же entry point вручную. Если Node.js падает и без Passenger, сначала исправляют приложение. Если тот же файл руками стартует нормально, а через домен остаётся 503, тогда уже сравнивают runtime, переменные окружения, рабочую директорию, права и настройки Passenger.
Главная цель — получить не просто HTTP 503, а конкретный признак: Cannot find module, неправильный Application Path, отсутствующую переменную, другой Node.js runtime или ошибку доступа. После этого проблема перестаёт быть абстрактной.

Почему Node.js-приложение в cPanel возвращает HTTP 503
Если 503 появляется только на URI Node.js-приложения, первым делом нужно отделить проблему приложения от проблемы всего virtual host. Passenger может не получить рабочий Node.js-процесс: startup-файл не найден, приложение завершается во время инициализации либо запускается не с тем окружением. Но такой вывод можно делать только после проверки остальных URL.
Сравните три запроса: обычную страницу домена, простой статический файл и URI приложения. Например:
curl -I https://example.com/
curl -I https://example.com/test.txt
curl -I https://example.com/app/
Если первые два запроса проходят, а только /app/ возвращает 503, DNS и общая доступность веб-сервера уходят далеко вниз списка подозреваемых. Если же 503 возвращает весь домен, искать ошибку исключительно в app.js рано.
У curl -I есть один нюанс: он отправляет запрос методом HEAD. Большинство приложений обрабатывают его ожидаемо, но для окончательной проверки лучше сделать и обычный GET:
curl -v https://example.com/app/
Если HEAD и GET ведут себя по-разному, это уже отдельная зацепка. Например, 503 может быть не общим startup failure, а особенностью маршрутизации или обработки конкретного метода.
| Что происходит | Куда смотреть дальше | Что уже можно исключить |
|---|---|---|
| 503 только на URI Node.js | Application Manager, Passenger, startup-файл | Общий отказ домена менее вероятен |
| Статика работает, Node.js нет | Логи приложения и ручной запуск | Document Root и базовая HTTP-доступность работают |
| 503 возвращает весь домен | Virtual host и веб-сервер | Нельзя сводить проблему только к Passenger |
| HEAD и GET дают разный результат | Маршрутизация и обработка HTTP-методов | Обычный startup failure уже не единственный сценарий |
503 — пока только внешний симптом. Сначала определите, какой именно URL ломается. Потом переходите к Passenger.
Где искать настоящую причину 503 в логах Passenger
Браузер обычно показывает только 503 Service Unavailable, а полезная ошибка остаётся на стороне приложения. Если Node.js стартует и сразу завершается, в логе можно увидеть уже конкретную причину: Cannot find module, SyntaxError, EACCES, ошибку импорта, отсутствующую переменную или сбой подключения к внешнему сервису во время инициализации.
Для приложения в Application Manager сначала перейдите в его каталог и проверьте директорию журналов:
cd ~/path-to-app
ls -lah logs/
Если файлы есть, посмотрите свежий хвост:
tail -n 100 logs/*
Старый лог легко уводит диагностику в сторону. Ошибка могла относиться к предыдущей версии проекта, поэтому удобнее поймать событие непосредственно во время нового запроса.
Как связать конкретный 503 с конкретной строкой лога
В первой SSH-сессии оставьте:
cd ~/path-to-app
tail -f logs/*
Во второй выполните:
curl -v https://example.com/app/
Если после запроса в журнале появляется:
Error: Cannot find module 'express'
Passenger уже не главный подозреваемый — Node.js дошёл до загрузки приложения и упал на зависимости. При:
Error: DATABASE_URL is not set
следующая проверка — environment. А EACCES или Permission denied ведут к владельцу, правам и пути к файлу.
Лог должен менять направление диагностики. Не просто подтверждать, что «что-то сломалось».
Что делать, если каталог logs пуст или его вообще нет
Пустой лог — тоже результат. Не переходите сразу к npm install. Сначала убедитесь, что вы действительно смотрите каталог того приложения, которое получает запрос.
Повторно сравните Application Path с текущей директорией:
pwd
ls -lah
Затем отправьте новый запрос к точному URI, зарегистрированному в Application Manager. Если приложение живёт на /api/, а проверяется /, отсутствие новой записи в его логе вполне закономерно.
Если каталога logs нет, проверьте сам Application Path и права на директорию. На shared-хостинге пользователь также может не иметь доступа к глобальному error log Apache или Passenger. В таком случае после проверки пути, URI и ручного запуска уже имеет смысл передать администратору точное время запроса и попросить посмотреть server-level журнал. Это гораздо полезнее сообщения «у меня 503».
| Что видно в логе | Следующая проверка |
|---|---|
Cannot find module |
npm dependencies и путь импорта |
SyntaxError |
Код и версия Node.js |
EACCES |
Права, владелец, доступ к каталогам |
| Missing environment variable | Environment Passenger |
| Лог не меняется вообще | Application Path, URI и server-level logs |
Правильно ли cPanel зарегистрировал Node.js-приложение
Можно редактировать правильный код и часами не видеть изменений, если Application Manager продолжает смотреть в старую директорию. После deployment это выглядит особенно обманчиво: новая версия уже загружена в ~/app-new, а Passenger всё ещё стартует ~/app.
В Application Manager сверяют как минимум:
- зарегистрировано ли приложение;
- правильный ли указан Deployment Domain;
- куда ведёт Application Path;
- совпадает ли Base Application URL с фактическим URI;
- не осталась ли старая регистрация после миграции или переименования каталога.
На сервере проверьте ту же директорию:
cd ~/path-to-app
pwd
ls -lah
ls -ld ~/path-to-app
Если используются символические ссылки, полезно посмотреть, куда реально ведёт путь:
readlink -f ~/path-to-app
Например, Application Path указывает на ~/current, а current всё ещё ссылается на старый release. Пользователь открывает новый app.js в соседнем каталоге, меняет код, делает restart — а Passenger запускает прежнюю версию. Снаружи кажется, что Passenger игнорирует изменения, хотя проблема просто в пути.
| Что сверить | В cPanel | Через SSH |
|---|---|---|
| Каталог приложения | Application Path | pwd |
| Фактическая цель symlink | Application Path | readlink -f |
| Startup-файл | Конфигурация приложения | ls -lah |
| Домен | Deployment Domain | URL в curl |
| URI | Base Application URL | Фактический путь запроса |
После сборки проекта ещё легко перепутать Application Path с dist, build или public. Наличие собранных файлов в таком каталоге не доказывает, что именно оттуда должен запускаться Node.js. Сначала найдите entry point и посмотрите, где относительно него лежит package.json.
Если пути начинают путаться, выпишите рядом URL, Application Path, результат pwd, реальную цель symlink и startup-файл. Несовпадение обычно видно сразу.
Как проверить startup-файл Node.js и почему Passenger ищет app.js
Когда каталог приложения подтверждён, нужно точно установить JavaScript entry point. В стандартном сценарии cPanel для Node.js используется app.js. Если реальный сервер проекта запускается из server.js, index.js, main.js или dist/server.js, Passenger и приложение должны быть настроены на один и тот же файл.
Не путайте startup-файл Passenger с полем main и командой npm start. Они могут указывать на разные файлы.
Посмотрите содержимое проекта:
ls -lah
Найдите распространённые варианты entry point:
find . -maxdepth 2 -type f \( -name "app.js" -o -name "server.js" -o -name "index.js" \)
И откройте package.json:
cat package.json
Чем startup-файл Passenger отличается от npm start
Допустим, package.json содержит:
{
"scripts": {
"start": "node server.js"
}
}
npm start действительно выполнит node server.js. Но это не означает, что Passenger автоматически выберет server.js. В результате разработчик проверяет проект через npm, видит рабочий сервер, а URL приложения всё равно возвращает 503, потому что Passenger ищет другой startup-файл.
Поле main в package.json тоже не следует воспринимать как автоматическую настройку Passenger. Проверять нужно фактическую конфигурацию запуска.
Заодно посмотрите на регистр имени:
app.js
App.js
APP.js
Для Linux это разные файлы. После разработки или упаковки проекта на Windows такая мелочь иногда проявляется только после загрузки на хостинг.
Что делать, если приложение реально запускается через server.js
Один вариант — использовать app.js как entry point проекта. Другой — явно настроить Passenger на нужный файл через PassengerStartupFile.
Конфигурация может содержать:
PassengerStartupFile index.js
PassengerAppType node
PassengerAppRoot /home/user/path-to-app
Но это уже серверный уровень настройки. Пользователь shared-хостинга обычно не может самостоятельно менять конфигурацию virtual host, перестраивать Apache и перезапускать веб-сервер. Если cPanel не предоставляет нужную настройку в интерфейсе, лучше передать администратору конкретный запрос: какой startup-файл требуется использовать и где расположен Application Path.
| Параметр | npm start |
Passenger |
|---|---|---|
| Что запускает | Команду из scripts.start |
Настроенный startup-файл |
| Entry point | Может быть любым | app.js либо явно заданный файл |
| Окружение | Shell/npm environment | Environment процесса Passenger |
| Где видно ошибку | Терминал | Журнал приложения и HTTP 503 |
На этом этапе нужен однозначный ответ на один вопрос: какой файл Passenger должен запустить. Не предполагаемый — фактический.

Запускается ли startup-файл вручную без Passenger
Ручной запуск entry point быстро делит диагностику на две ветки. Если Node.js сам не способен запустить приложение, Passenger пока ни при чём. Если startup проходит, а через домен остаётся 503, уже есть смысл сравнивать условия выполнения.
Перейдите именно в Application Path:
cd ~/path-to-app
И запустите фактический файл:
node app.js
Если проект использует другой entry point:
node server.js
Что означает, если node app.js не возвращает приглашение командной строки
Такой результат не обязательно означает зависание. Если приложение поднимает HTTP-сервер и процесс остаётся активным без exception, это может быть нормальным успешным запуском. Терминал занят потому, что Node.js ждёт запросы.
Откройте вторую SSH-сессию. Если приложение при ручном запуске слушает известный тестовый порт, его можно проверить локально:
curl -v http://127.0.0.1:3000/
Порт 3000 здесь только пример. Использовать нужно тот, который действительно применяет проект при таком способе запуска. После теста остановите ручной процесс через Ctrl+C, чтобы он не остался работать параллельно.
Если приложение не должно самостоятельно слушать фиксированный порт в Passenger-сценарии, не переделывайте его только ради этой проверки. Здесь важнее увидеть, проходит ли инициализация без exception.
Если node app.js сразу падает
CLI обычно показывает ошибку лучше браузера. Например:
Error: Cannot find module 'express'
В этом случае проверяют зависимости. При:
SyntaxError: Unexpected token ...
нужно смотреть код и Node.js runtime. Если вывод заканчивается:
Error: DATABASE_URL is not set
приложение само сообщает, чего ему не хватает. Настройка PassengerStartupFile такую ошибку не исправит.
| Результат ручного запуска | Куда идти дальше |
|---|---|
SyntaxError |
Код и версия Node.js |
Cannot find module |
npm dependencies |
EACCES |
Права и пути |
| Missing environment variable | Переменные окружения |
| Процесс остаётся активным без ошибки | Passenger, runtime и environment |
Как сравнить Node.js в SSH и внутри Passenger
which node и node -v показывают runtime вашей текущей shell-сессии:
which node
node -v
npm -v
Но сами по себе эти команды не доказывают, что Passenger запускает тот же бинарник. Для короткой диагностики можно временно вывести информацию непосредственно из процесса приложения:
console.log('Node version:', process.version);
console.log('Node executable:', process.execPath);
После restart сделайте один запрос к приложению и посмотрите журнал. Теперь вы видите версию и путь именно из Node.js-процесса, поднятого Passenger. Сравните их с:
node -v
which node
Если значения различаются, причина поведения проекта уже становится намного конкретнее. После проверки диагностический вывод лучше удалить.
На некоторых cPanel-серверах EasyApache Node.js доступен через путь вида:
/opt/cpanel/ea-nodejs20/bin/node app.js
Версия в пути здесь только пример. Не подставляйте её по статье: сначала выясните, какой runtime использует ваше приложение.
Если startup-файл падает вручную, Passenger пока не трогаем. Сначала Node.js должен пройти собственную инициализацию без ошибки.
Не падает ли приложение из-за npm-зависимостей или версии Node.js
После переноса проекта файлы могут выглядеть полностью на месте, но node_modules отсутствует, установлена другая версия пакета или native addon был собран в другом окружении. Passenger пытается запустить startup-файл, Node.js завершается, а пользователь получает всё тот же 503.
Проверьте версии:
node -v
npm -v
Затем состояние верхнего уровня dependencies:
npm ls --depth=0
Проблема может выглядеть, например, так:
npm ERR! code ELSPROBLEMS
npm ERR! missing: express@..., required by my-app@...
Здесь уже нет смысла менять Passenger. Сначала нужно привести зависимости проекта в рабочее состояние.
Как отличить проблему пакетов от неподходящей версии Node.js
| Симптом | Вероятная область | Что проверить |
|---|---|---|
Cannot find module |
Dependencies | Есть ли пакет и корректен ли путь импорта |
ERR_MODULE_NOT_FOUND |
ESM / imports / dependencies | Путь, расширение файла, наличие пакета |
EBADENGINE или engine mismatch |
Node.js runtime | Секцию engines |
| SyntaxError только на сервере | Версия Node.js | Сравнить runtime локально и в Passenger |
Ошибка *.node или native addon |
Сборка модуля | ОС, архитектуру и версию Node.js |
Если в package.json есть:
{
"engines": {
"node": ">=20"
}
}
а Passenger использует более старый runtime, установка пакетов не исправит несовместимость. Сначала сравните реальную версию внутри процесса через process.version.
Не начинайте диагностику с безусловного удаления зависимостей:
rm -rf node_modules
npm install
Такая команда иногда помогает, а иногда меняет дерево пакетов и добавляет новую проблему поверх старой. Если проект использует lock-файл, придерживайтесь предусмотренного для него процесса deployment. Например, npm ci полезен для воспроизводимой установки по package-lock.json, но только когда lock-файл актуален и такой способ соответствует проекту.
Критерий здесь простой: тот же startup-файл должен стабильно проходить запуск в нужном Node.js runtime. Пока этого нет, возвращаться к Passenger рано.
Получает ли Passenger нужные переменные окружения
Сценарий «из SSH работает, через Passenger 503» часто появляется из-за разных переменных окружения. Наличие DATABASE_URL или API_KEY в вашей shell-сессии ещё не доказывает, что эту переменную видит Node.js-процесс Passenger.
Shell можно проверить так:
printenv NODE_ENV
env
Но полный env не стоит копировать в тикет или публичный чат: среди переменных могут оказаться токены и пароли.
Как проверить environment именно внутри Passenger
Чтобы не гадать, временно добавьте безопасную диагностику в startup-код:
console.log(
'DATABASE_URL present:',
Boolean(process.env.DATABASE_URL)
);
console.log(
'API_KEY present:',
Boolean(process.env.API_KEY)
);
console.log(
'NODE_ENV:',
process.env.NODE_ENV || 'not set'
);
Значения секретов здесь не выводятся. Лог сообщает только, существует переменная или нет.
После изменения перезапустите приложение:
mkdir -p tmp
touch tmp/restart.txt
Затем сделайте запрос и сразу откройте свежий лог:
curl -v https://example.com/app/
tail -n 100 logs/*
Если SSH показывает переменную, а Passenger-процесс пишет:
DATABASE_URL present: false
разница окружения уже подтверждена. Это намного сильнее предположения «наверное, Passenger не читает .bashrc».
Почему .bashrc не гарантирует переменные для Passenger
.bashrc относится к shell-сессии. Passenger запускает приложение другим способом, поэтому переменная, экспортированная при входе по SSH, может не попасть в Node.js-процесс веб-приложения.
Если Application Manager предоставляет управление Environment Variables, задавайте параметры там в рамках доступной конфигурации хостинга. Если такой возможности нет, способ передачи переменных зависит от серверной настройки — здесь уже может понадобиться администратор.
Как проверить .env и не показать секреты
После миграции легко забыть скрытый .env: обычный список исходников выглядит полным, а приложение падает только на сервере. Проверить сам файл:
ls -la
Показать только названия переменных без значений:
grep -E '^[A-Za-z_][A-Za-z0-9_]*=' .env | cut -d= -f1
Если .env есть, но приложение его не загружает, проверьте также рабочую директорию. Ручной запуск из каталога проекта и Passenger могут по-разному проявить ошибку относительного пути.
Не пишите секреты в лог даже временно. Для проверки environment достаточно Boolean(process.env.NAME) или безопасного служебного значения.
После диагностики временные console.log нужно удалить и ещё раз перезапустить приложение.
Не мешают ли запуску права доступа, пути и рабочая директория
Файл может лежать на диске и при этом оставаться недоступным процессу. Или startup-файл читается нормально, но приложение падает позже, когда пытается открыть ./config.json, сертификат, шаблон или другой ресурс по относительному пути.
Проверьте каталог и сам entry point:
pwd
ls -lah
stat app.js
Для полного пути полезен namei, если команда доступна:
namei -l /home/user/path-to-app/app.js
Она показывает каждый каталог по пути. Процессу мало иметь право чтения самого app.js — ему нужен доступ и к родительским директориям.
Не пытайтесь «проверить права» командой:
chmod -R 777 ~/path-to-app
Так можно временно скрыть исходную проблему и одновременно открыть лишний доступ. Смотрите владельца, группу и конкретные permission bits.
Почему process.cwd() и __dirname дают разные результаты
Если код использует:
fs.readFileSync('./config.json')
путь зависит от текущей рабочей директории процесса. Для диагностики временно выведите:
console.log('cwd:', process.cwd());
console.log('__dirname:', __dirname);
После Passenger restart и нового запроса журнал покажет реальные значения именно веб-процесса.
Для файла, который всегда лежит рядом с модулем, безопаснее строить путь явно:
const path = require('path');
const configPath = path.join(__dirname, 'config.json');
После миграции проверьте и абсолютные пути. В проекте может остаться:
/home/olduser/app/config.json
На старом сервере он существовал, на новом — нет. При этом весь остальной проект может выглядеть совершенно нормально.
Такие ошибки особенно хорошо выявляются ручным запуском и свежим логом: вместо общего 503 появляется конкретный ENOENT или EACCES.
Как правильно перезапустить Passenger после исправления приложения
После изменения startup-файла, dependencies или environment старый процесс Passenger не всегда нужно считать автоматически заменённым. Для применения изменений в каталоге приложения используется tmp/restart.txt.
cd ~/path-to-app
mkdir -p tmp
touch tmp/restart.txt
Критичен именно Application Path. Если приложение находится в ~/myapp, нужен:
~/myapp/tmp/restart.txt
а не:
~/tmp/restart.txt
Как понять, что restart сработал, но новый процесс снова упал
После touch сразу сделайте новый запрос и смотрите журнал:
curl -v https://example.com/app/
tail -n 100 logs/*
Если появляется новая startup-ошибка с актуальным временем и вашим свежим диагностическим выводом, Passenger действительно попытался поднять новую версию. Проблема уже не в том, что restart «не сработал»: новый процесс просто снова завершился.
Например, после добавления:
console.log('APP BUILD: test-2026');
свежая строка в журнале подтверждает, что Passenger дошёл до изменённого кода. Такой маркер можно использовать кратковременно, если есть сомнение, какая версия запускается. Затем его нужно удалить.
Если после restart не меняется ни поведение, ни лог, ни временный диагностический маркер, вернитесь к Application Path. Часто выясняется, что restart.txt создавался не в той директории или запрос вообще попадает в другую регистрацию приложения.
После исправления последовательность короткая: restart.txt → новый HTTP-запрос → свежий лог. Restart не исправляет приложение, он только заставляет Passenger запустить его ещё раз.
Как за 10 минут локализовать причину Node.js 503 в cPanel
Когда сайт уже отдаёт 503, цель первых минут — не перебрать все настройки cPanel, а определить конкретную ветку проблемы. Удобно идти сверху вниз и не перескакивать через этапы.
-
Проверьте область ошибки.
Если ломается только URI приложения, переходите к Passenger.curl -I https://example.com/ curl -I https://example.com/test.txt curl -v https://example.com/app/ - Сверьте регистрацию в Application Manager. Проверьте Deployment Domain, Application Path и Base Application URL.
-
Подтвердите реальный каталог.
Если путь отличается от того, что вы редактируете, дальнейшая диагностика этого каталога бессмысленна.cd ~/path-to-app pwd ls -lah readlink -f ~/path-to-app -
Найдите фактический startup-файл.
Сравните
app.js,package.json,npm startи реальный entry point. -
Поймайте свежую ошибку.
В другой SSH-сессии повторите HTTP-запрос.tail -f logs/* - Если лог пуст, не переходите сразу к npm. Снова проверьте Application Path, URI и доступ к журналам. Пустой лог может означать, что запрос вообще не дошёл до нужного приложения.
-
Запустите entry point вручную.
Ошибка в CLI означает, что сначала нужно чинить само приложение.node app.js - Если процесс остаётся активным без exception, не принимайте это за зависание. Node.js может успешно работать и ждать запросы. При известном порте проверьте его из второй SSH-сессии.
-
Если вручную работает, сравните Passenger runtime и environment.
Временно выведите:
Сделайте restart и посмотрите свежий лог.console.log('Node version:', process.version); console.log('Node executable:', process.execPath); console.log( 'DATABASE_URL present:', Boolean(process.env.DATABASE_URL) ); -
После исправления перезапустите Passenger.
Повторный запрос должен либо заработать, либо дать новую конкретную ошибку.mkdir -p tmp touch tmp/restart.txt
Чек-лист перед обращением к администратору
- Проверено, какие именно URL возвращают 503.
- Проверены обычный GET и HTTP status.
- Deployment Domain и Application Path сверены с реальным каталогом.
- Проверена цель symlink, если он используется.
- Определён фактический startup-файл.
- Проверен регистр
app.js. npm startне перепутан с Passenger startup.- Получен свежий журнал непосредственно при тестовом запросе.
- Проверено, что делать, если журнал пуст.
- Startup-файл запущен вручную.
- Понятно, завершился Node.js с ошибкой или остался нормально работать.
- Проверены npm dependencies.
- Сравнены Node.js версии shell и Passenger.
- Проверен
process.execPathвнутри Passenger-процесса. - Проверены обязательные environment variables внутри приложения.
- Проверены
process.cwd()и пути к файлам. - Проверены права и владелец без
chmod -R 777. tmp/restart.txtсоздан именно внутри Application Path.- После restart сделан новый запрос и прочитан новый лог.
Если после всех проверок startup-файл стабильно стартует вручную, Passenger использует ожидаемый Node.js runtime, переменные окружения на месте, Application Path правильный и файлы доступны, но URL всё ещё возвращает 503, дальнейшая диагностика может требовать доступа к серверной конфигурации Passenger и Apache. На shared-хостинге этот уровень обычно недоступен пользователю.
К этому моменту в обращение к администратору уже можно передать точный URI, Application Path, startup-файл, результат ручного запуска, process.version, process.execPath, время тестового запроса и свежие строки журнала. Вместо «Passenger отдаёт 503» получается конкретная техническая картина — и искать причину дальше намного проще.


