MHOSTMHOST

Сервис не запускается

Сервис в systemd отказывается запускаться, падает через пару секунд после старта или уходит в бесконечный цикл перезапуска — одна из самых частых проблем на VPS. Причина почти всегда одна из пяти: занят порт, ошибка в конфигурации, не хватает прав, не готова зависимость или сервису не хватило памяти, — и в этой статье разобран общий метод диагностики через systemctl status и journalctl, а затем каждая из пяти причин на конкретных примерах.

Что понадобится

  • VPS MHost на Ubuntu 22.04/24.04, Debian 11/12 или AlmaLinux/Rocky Linux 8/9.
  • Доступ по SSH под root или пользователем с sudo — если ещё не подключались к серверу, начните со статьи «Консоль VNC и первое подключение (SSH/RDP)».
  • Имя сервиса, который не запускается: например nginx, postgresql, myapp. Список всех systemd-сервисов на сервере — systemctl list-units --type=service --all.
Совет: ниже везде используется <service> как условное имя — замените на реальное имя своего сервиса. Команды даны от имени root; под обычным пользователем добавляйте sudo.

Шаг 1. Проверьте статус: systemctl status

bash
systemctl status <service>

Пример вывода для упавшего сервиса:

● myapp.service - My Application
     Loaded: loaded (/etc/systemd/system/myapp.service; enabled; preset: enabled)
     Active: failed (Result: exit-code) since Fri 2026-07-10 09:14:22 UTC; 38s ago
    Process: 4021 ExecStart=/opt/myapp/venv/bin/python /opt/myapp/main.py (code=exited, status=1/FAILURE)
   Main PID: 4021 (code=exited, status=1/FAILURE)

Loaded заодно подтверждает, что systemd вообще знает такой юнит и откуда взял его файл; строка вида Unit myapp.service could not be found означает опечатку в имени, неустановленный пакет либо новый unit-файл, про который systemd ещё не знает (лечится командой systemctl daemon-reload).

Главное — строка Active:

  • `active (running)` — сервис работает; если вы здесь по этой статье, скорее всего, вы видите не это.
  • `activating (auto-restart)` — сервис падает и systemd раз за разом пытается перезапустить его автоматически (настройка Restart= в юните). Загляните в журнал (Шаг 2), не дожидаясь, пока попытки закончатся.
  • `failed` — не смог подняться либо упал и повторно не перезапускается.
  • `inactive (dead)` — просто остановлен, никто и не пытался его запустить: возможно, вы просто забыли его включить — systemctl start <service>.

Строка Process/Main PID с кодом в скобках расшифровывается так:

  • `code=exited, status=N/...` (N ≠ 0) — процесс успешно стартовал и сам завершился с ошибкой — причину ищите в собственном выводе приложения (Шаг 2).
  • `code=exited, status=203/EXEC` — до запуска самого приложения дело не дошло: systemd не смог выполнить файл из ExecStart — неверный путь, файл не помечен исполняемым или недоступен по правам. См. «Неправильные права доступа» и «Не хватает зависимости» ниже.
  • `code=killed, signal=KILL` — процесс убит сигналом SIGKILL. Чаще всего это OOM killer ядра — см. «Не хватило памяти» ниже.
  • `code=killed, signal=SEGV` (или ABRT) — процесс упал сам: похоже на баг в приложении или несовместимую версию библиотеки.
  • `Result: oom-kill` — на более новых версиях systemd так отдельно помечается случай, когда именно cgroup сервиса получила уведомление об уничтожении процесса от OOM killer'а ядра — по сути то же самое, что signal=KILL из-за нехватки памяти, но systemd явно называет причину.
Совет: если не уверены, какой именно сервис сейчас в проблемном состоянии, посмотрите разом все упавшие юниты на сервере: systemctl --failed.

Шаг 2. Прочитайте журнал: journalctl

bash
journalctl -u <service> -e

-u — фильтр по конкретному юниту; -e сразу перематывает вывод в конец, к самым свежим записям.

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

Полезные варианты команды:

  • journalctl -xeu <service> — то же самое плюс флаг -x: там, где это возможно, добавляет пояснение к системному сообщению из каталога systemd. Именно эту команду обычно подсказывает сам systemctl, когда сервис падает.
  • journalctl -u <service> --no-pager -n 50 — последние 50 строк без интерактивного пейджера, удобно, если нужно скопировать вывод целиком.
  • journalctl -u <service> -f — следить за журналом в реальном времени, пригодится сразу после перезапуска (Шаг 4).
  • journalctl -u <service> --since "10 min ago" — только записи за последние 10 минут, если журнал юнита большой.

Шаг 3. Найдите причину среди самых частых

Строка с ошибкой из Шага 2 почти всегда прямо указывает на одну из пяти причин ниже.

Порт уже занят

Если порт, который пытается слушать сервис, уже занят другим процессом, забиндиться на него не получится, и сервис упадёт сразу при старте — в журнале это обычно видно как что-то вроде:

bind() to 0.0.0.0:80 failed (98: Address already in use)

Найдите, кто реально держит порт:

bash
ss -tlnp | grep :80
  • -t — только TCP;
  • -l — только слушающие сокеты;
  • -n — порты и адреса числом, без резолва в имена;
  • -p — какой процесс держит порт (нужен sudo/root, иначе имя процесса и PID скрыты).

Пример вывода:

LISTEN 0      511          0.0.0.0:80         0.0.0.0:*    users:(("apache2",pid=1587,fd=6))

Порт 80 занят процессом apache2 — именно поэтому второй сервис (например, nginx) не смог стартовать. Дальше — выбор:

  • если второй сервис реально не нужен — остановите и отключите его автозапуск: systemctl disable --now apache2;
  • если нужны оба — смените порт у того сервиса, который настраиваете сейчас (для Nginx это директива listen в конфигурации vhost, см. статью «Nginx: установка и первый сайт»), и не забудьте открыть новый порт в файрволе.
Совет: порт бывает занят и «зависшим» экземпляром того же самого сервиса — например, если предыдущий systemctl stop не до конца убил дочерние процессы. В выводе ss в этом случае владельцем порта окажется тот же сервис, только с другим, «старым» PID.

Ошибка в конфигурации

Большинство зрелых демонов умеют проверить свой конфигурационный файл на синтаксис без реального запуска — пользуйтесь этим перед restart/reload, чтобы не уронить рабочий процесс проверкой битого конфига:

bash
nginx -t
apachectl configtest
sshd -t

Если у сервиса нет отдельного флага проверки, самый надёжный способ найти ошибку — всё тот же journalctl (Шаг 2): перед тем как завершиться, приложение почти всегда само печатает, в каком файле и на какой строке оно споткнулось.

Отдельно от конфигурации самого приложения можно проверить и unit-файл systemd — на опечатки в директивах, циклы зависимостей и подобное:

bash
systemd-analyze verify <service>

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

Важно: если проверка конфигурации сообщила об ошибке — не выполняйте restart/reload, пока не исправите её. Сообщение обычно указывает точный файл и номер строки.

Неправильные права доступа

Юнит-файл сервиса может явно задавать, от имени какого пользователя реально работает процесс, — не обязательно root, даже если вы управляете сервисом через sudo. Узнать, от чьего имени запускается сервис:

bash
systemctl show <service> -p User -p Group

Пустое значение (User= без содержимого) значит, что сервис по умолчанию работает от root. Если указан отдельный пользователь — именно у него должны быть права на чтение (а иногда и запись) конфигурации, данных и рабочего каталога сервиса.

Проверьте владельца и права:

bash
ls -l /path/to/config
namei -l /path/to/config

namei -l построчно показывает права и владельца для каждого каталога в цепочке пути — от корня до самого файла, а не только для конечного файла. Это удобно, когда сам файл выглядит доступным (ls -l в порядке), а прочитать его всё равно не получается: значит, доступ закрыт на каком-то из родительских каталогов.

Исправьте владельца и права на нужные (подставьте пользователя и группу, которые вернула команда systemctl show из примера выше, — www-data здесь лишь пример для Ubuntu/Debian):

bash
chown www-data:www-data /path/to/config
chmod 640 /path/to/config
Совет: именно с правами связан код status=203/EXEC из Шага 1 — если у пользователя сервиса нет права на исполнение файла из ExecStart (не хватает бита x) или нет доступа к каталогу с ним, systemd не сможет запустить процесс вообще, и до логов самого приложения дело не дойдёт.
Совет: на дистрибутивах AlmaLinux/Rocky «Permission denied» возможен и при формально верных Unix-правах — из-за SELinux. Посмотрите недавние отказы:
bash
ausearch -m avc -ts recent

Если команда не найдена, установите пакет audit (dnf install audit) и включите systemctl enable --now auditd. Пример восстановления SELinux-контекста через semanage fcontext и restorecon для каталога вне стандартных путей — в статье «Nginx: установка и первый сайт».

Не хватает зависимости

Бывает двух видов.

Первый — сервис стартует раньше, чем готово то, от чего он зависит: например, приложение подключается к базе данных сразу при старте, а сама база на этом же сервере ещё не успела подняться после перезагрузки. Характерный симптом: вручную, спустя минуту после reboot, сервис запускается без проблем, а сразу при загрузке сервера — падает. Чинится через systemd-зависимости — переопределите юнит:

bash
systemctl edit <service>

Команда откроет редактор и сама сохранит правки в отдельный override-файл (/etc/systemd/system/<service>.service.d/override.conf), автоматически перечитав конфигурацию systemd после сохранения — отдельный daemon-reload не нужен. Добавьте:

ini
[Unit]
After=postgresql.service
Requires=postgresql.service

(замените postgresql.service на реальное имя юнита, от которого зависит ваш сервис). After задаёт только порядок запуска, но не гарантирует, что зависимость вообще поднялась; Requires добавляет к этому жёсткую зависимость — если postgresql.service не запустится, systemd не станет пытаться запускать и ваш сервис (в отличие от более мягкого Wants=, который просто игнорирует неудачу зависимости).

Второй вид — не хватает чего-то на уровне самой системы: не установлена библиотека, не создано виртуальное окружение Python, не выполнен npm install. В журнале это обычно видно как явная ошибка самого приложения — что-то вроде ModuleNotFoundError: No module named 'foo' (Python) или Error: Cannot find module 'bar' (Node.js), — либо тот же status=203/EXEC, если файла из ExecStart не существует вовсе (например, неверно указан путь до venv). Проверьте отсутствующие библиотеки:

bash
ldd $(which <binary>)

Строки => not found в выводе показывают, каких .so-библиотек не хватает, — доустановите нужный пакет.

Совет: правильные пути ExecStart, виртуальное окружение и переменные окружения для systemd-юнита Python/Node-приложения разобраны в статьях «Python и FastAPI: продакшн-деплой» и «Django на VPS» — сверьтесь с ними, если юнит-файл писали вручную. Если приложение запускается не напрямую через systemd, а внутри Docker-контейнера, журнал journalctl -u его вообще не покажет — см. docker compose logs в статье «Docker и Docker Compose».

Не хватило памяти (OOM)

Если сервис (сам или вместе с остальными процессами на сервере) исчерпал всю доступную память, ядро Linux принудительно убивает один из процессов, чтобы не допустить полного зависания сервера, — это и есть OOM killer (out-of-memory killer). Убитый процесс — не обязательно самый прожорливый: ядро выбирает по эвристике (oom_score), но чаще всего это действительно тот, кто съел больше всего.

Симптом в systemctl status — падение без внятной причины со стороны самого приложения:

Main PID: 4021 (code=killed, signal=KILL)

или, отдельным статусом:

Active: failed (Result: oom-kill)

Подтвердите, что дело именно в OOM killer'е, а не в ручном kill или падении самого приложения:

bash
dmesg -T | grep -i -E "out of memory|killed process"
journalctl -k -e

-k показывает только сообщения ядра (то же самое, что dmesg, но через журнал); -T у dmesg печатает время в человекочитаемом формате вместо секунд с загрузки. Типичная строка в выводе:

Out of memory: Killed process 4021 (myapp) total-vm:2048000kB, anon-rss:1523000kB, file-rss:0kB

Проверьте общую картину по памяти и swap:

bash
free -h

Что делать:

  • быстро — добавьте swap, если его вообще нет:
bash
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
swapon --show
  • надёжнее — снизьте реальное потребление памяти сервисом: меньше воркеров/потоков, ниже лимиты кеша в его собственной конфигурации (зависит от конкретного приложения);
  • поставьте сервису мягкий потолок через systemd, чтобы утечка памяти укладывала только его cgroup, а не весь сервер:
ini
[Service]
MemoryMax=512M

(добавляется тем же systemctl edit <service>, что и в предыдущей причине);

  • если памяти физически не хватает на тарифе — увеличьте объём RAM в биллинг-панели my.mhost.ee.
Совет: systemd-oomd — отдельный userspace-демон, который может завершать процессы ещё до срабатывания OOM killer'а ядра, реагируя на давление на память (PSI). На Ubuntu он включён по умолчанию только в Desktop-редакции; на Ubuntu Server и на остальных перечисленных дистрибутивах его нет, если не поставить отдельно, — так что на обычном VPS Host вы почти всегда имеете дело именно с классическим OOM killer'ом ядра, описанным выше.

Шаг 4. Исправьте причину и перезапустите

Если правили сам unit-файл вручную (а не через systemctl edit), обязательно выполните daemon-reload перед перезапуском — иначе systemd продолжит использовать старую версию, уже загруженную в память:

bash
systemctl daemon-reload
systemctl restart <service>
systemctl status <service>

Не полагайтесь только на active (running) сразу после команды — задержитесь на несколько секунд и последите за журналом, чтобы поймать сервис, который падает не мгновенно, а спустя пару секунд после старта (например, при исчерпании памяти под нагрузкой):

bash
journalctl -u <service> -f

(Ctrl+C, чтобы выйти из режима слежения)

Важно: если чините именно ssh/sshd — не закрывайте текущую SSH-сессию, пока не откроете и не проверите новое подключение в отдельном окне. Если всё же потеряли доступ — консоль сервера в VMmanager 6 работает независимо от SSH (см. статью «Консоль VNC и первое подключение (SSH/RDP)»); если в итоге совсем не получается зайти по SSH — это уже отдельная тема, разобранная в отдельной статье о восстановлении доступа по SSH.
Готово: systemctl status <service> показывает active (running), а свежий journalctl -u <service> -e — без новых ошибок после перезапуска.

Что дальше

Если чинили именно веб-сервер и после перезапуска сам сервис в порядке, а сайт всё равно не открывается снаружи, — это уже отдельная тема, разобранная в статье «Сайт не открывается», включая проверку файрвола (см. также «Файрвол ufw: порты»). Если journalctl вместо перечисленных выше причин жалуется на нехватку места на диске («No space left on device») — см. статью «Диск заполнен»; если сам сервис в порядке, а сервер в целом тормозит, — статью «Высокая нагрузка на сервер». Если ничего не помогло, а незадолго до этого вы меняли конфигурацию, — проще откатить именно то изменение (или восстановить конфигурацию из бэкапа, если он есть), чем разбираться дальше.

Диагностика, специфичная для конкретного приложения, — в статьях по этому сервису: «Nginx: установка и первый сайт», «Apache: установка и виртуальные хосты», «PostgreSQL: установка и настройка», «MySQL / MariaDB: установка и настройка», «Redis: установка и настройка».