Если сайт не стартует после reboot, не спешите снова перезагружать VPS и запускать все процессы вручную. Повторный reboot стирает часть временных признаков и не исправляет отсутствующий автозапуск, неверный порядок зависимостей, недоступный диск, переменную окружения или конфликт порта. Сначала нужно определить, какой слой не поднялся в текущей загрузке: сеть, веб-сервер, приложение, база, контейнер, очередь или файловое хранилище.

Ручной запуск через SSH часто проходит, потому что к этому моменту сеть и диски готовы, пользователь имеет другой PATH, а команда выполняется из правильного каталога. При загрузке systemd или Docker запускает сервис раньше, в чистом окружении и под отдельной учетной записью. Поэтому «после systemctl start работает» — важный диагностический признак, а не готовое исправление.

Сначала восстановите доступность без потери доказательств

Зафиксируйте время загрузки и состояние сервисов до массовых рестартов. Если сайт критичен, можно запустить один подтвержденный компонент штатной командой, но сначала сохраните его status и журнал текущей загрузки. Не меняйте одновременно unit, права файлов, firewall и конфигурацию Nginx: после такого набора невозможно понять первопричину.

  • запишите время последней загрузки и boot ID;
  • сохраните список failed unit и состояние стека сайта;
  • зафиксируйте, какие порты слушаются и каким процессом;
  • проверьте место, inode, память и монтирование нужных файловых систем;
  • сохраните журналы веб-сервера, приложения, базы и контейнеров;
  • не удаляйте PID-файлы, сокеты, volume и логи до понимания их состояния;
  • не публикуйте EnvironmentFile, пароли базы, токены и приватные ключи.
uptime -s systemctl --failed systemctl list-units --type=service --state=failed journalctl -b -p warning..alert --no-pager ss -lntp df -h df -i free -h

Команды выше читают состояние. Выполняйте их с учетной записью, которая имеет право видеть нужные процессы и журналы. Не копируйте весь journalctl в публичный чат: в нем могут оказаться адреса, имена пользователей, параметры запуска и секреты. Для передачи достаточно обезличенного фрагмента вокруг первой ошибки.

Определите, на каком уровне сайт недоступен

Ошибка браузера не говорит, какой сервис не запустился. Сначала проверьте цепочку снаружи и изнутри сервера. DNS обычно не меняется от reboot, но публичный IP мог измениться у некоторых хостингов. Соединение refused означает одно, тайм-аут — другое, а 502 показывает, что reverse proxy работает, но не получает ответ upstream.

  • домен не разрешается или ведет на старый IP — проверяйте DNS и адрес VPS;
  • TCP 80/443 дает timeout — проверяйте сеть, firewall, security group и маршрут;
  • connection refused — на адресе никто не слушает порт или слушает другой интерфейс;
  • Nginx возвращает 502 — приложение, PHP-FPM или сокет upstream не готов;
  • 503 — сервис сознательно недоступен, упал health check или включен maintenance;
  • 500 — веб-слой поднялся, ошибка находится в приложении или конфигурации;
  • статические файлы открываются, динамика нет — проверяйте runtime и базу.
# Проверка изнутри сервера без обхода аутентификации приложения curl -I --max-time 5 http://127.0.0.1/ curl -I --max-time 5 -H 'Host: example.test' http://127.0.0.1/ # Проверка конфигурации перед reload nginx -t apachectl configtest

Подставляйте свой безопасный Host и не отправляйте запрос к административному действию. Локальный ответ помогает отделить внешний firewall от приложения. Если сайт работает на нестандартном порту, проверяйте конкретный upstream и его health endpoint, который не изменяет данные.

Посмотрите ошибки именно текущей загрузки

Главный источник — журнал с ключом -b, то есть текущий boot. Общий журнал смешивает старые сбои и успешные запуски. Начинайте с первой ошибки сервиса, от которого зависят остальные. Сообщения Nginx о недоступном upstream могут быть следствием того, что база или mount не успели подняться.

journalctl -b -u nginx --no-pager journalctl -b -u php-fpm --no-pager journalctl -b -u mariadb --no-pager journalctl -b -u postgresql --no-pager journalctl -b -u my-app.service --no-pager systemctl status my-app.service --no-pager -l systemctl show my-app.service -p ActiveState -p SubState -p Result -p ExecMainStatus
  • exit-code показывает, что процесс запустился и завершился с ошибкой;
  • timeout означает, что unit не достиг ожидаемого состояния вовремя;
  • dependency означает сбой обязательного unit;
  • start-limit-hit появляется после слишком частых неуспешных рестартов;
  • status=203/EXEC обычно указывает на путь, права или отсутствующий исполняемый файл;
  • permission denied требует проверки пользователя процесса и конкретного объекта;
  • address already in use означает конфликт порта или сокета.

Не лечите start-limit-hit только командой reset-failed. Она разрешит еще одну попытку, но процесс снова упадет, если причина осталась. Сначала исправьте первую содержательную ошибку, затем сбросьте состояние и выполните контролируемый старт.

Проверьте разницу между active и enabled

Active означает, что сервис работает сейчас. Enabled означает, что создана связь с target для запуска при загрузке. Сервис может быть active после ручного start, но disabled и потому не появиться после следующего reboot. Возможен и обратный случай: unit enabled, но падает во время загрузки из-за конфигурации или зависимости.

systemctl is-active my-app.service systemctl is-enabled my-app.service systemctl cat my-app.service systemctl list-dependencies multi-user.target systemctl list-dependencies my-app.service

Включайте автозапуск только для нужного unit и после проверки его содержимого. Не используйте enable --now как замену диагностике: команда одновременно меняет постоянное состояние и запускает сервис, что смешивает две проверки. Сначала добейтесь надежного ручного запуска, затем включите unit и отдельно протестируйте загрузку.

Проверьте unit systemd и абсолютные пути

При входе по SSH shell загружает профиль, задает PATH и открывается в домашнем каталоге. Systemd этого не делает. Относительный путь к бинарнику, .env или файлу конфигурации может работать вручную и падать при boot. В unit явно задают User, Group, WorkingDirectory, EnvironmentFile и абсолютный ExecStart.

[Unit] Description=Web application Wants=network-online.target After=network-online.target [Service] Type=simple User=app Group=app WorkingDirectory=/srv/app/current EnvironmentFile=/etc/my-app/app.env ExecStart=/usr/bin/runtime /srv/app/current/server Restart=on-failure RestartSec=5s [Install] WantedBy=multi-user.target

Это схема, а не готовый unit для копирования. Тип сервиса, runtime, пользователь, зависимости и способ остановки зависят от приложения. После редактирования используйте systemd-analyze verify для файла, daemon-reload и тестовый start. Не храните секреты прямо в unit, который читается через systemctl cat. EnvironmentFile должен иметь ограниченные права.

  • проверьте существование ExecStart и право исполнения;
  • убедитесь, что User и Group существуют до запуска unit;
  • проверьте доступ пользователя к WorkingDirectory, логам и сокетам;
  • используйте абсолютные пути к runtime и приложению;
  • не полагайтесь на alias, shell-функции и интерактивный профиль;
  • не используйте root, если приложению не нужны системные привилегии;
  • задайте корректный Type для поведения процесса.

Исправьте порядок запуска сети и зависимостей

After задает порядок, но не обязательно притягивает unit в транзакцию загрузки. Requires или Wants задают зависимость, однако готовность процесса не всегда равна готовности сервиса. Например, база может иметь active, но еще выполнять recovery; сетевой target может быть достигнут до появления нужного маршрута или DNS. Простая задержка sleep маскирует гонку и ломается при более долгой загрузке.

  • используйте network-online.target только когда приложению действительно нужна готовая сеть;
  • убедитесь, что сервис ожидания сети включен для используемого network manager;
  • описывайте порядок с локальной базой и mount через реальные unit;
  • для внешнего API реализуйте retry с backoff внутри приложения;
  • для базы используйте readiness-проверку, а не фиксированный sleep;
  • не делайте веб-приложение навсегда зависимым от необязательного внешнего сервиса;
  • проверьте critical chain текущей загрузки.
systemd-analyze critical-chain my-app.service systemd-analyze blame systemctl show my-app.service -p After -p Wants -p Requires

Приложение должно переживать временную недоступность внешней сети: повторять соединение ограниченно, показывать понятный degraded status и восстанавливаться без ручного restart. Systemd отвечает за процесс, но бизнес-зависимости лучше проверять самим приложением.

Проверьте диски, mount и временные каталоги

После reboot дополнительный диск, сетевое хранилище или зашифрованный раздел может не смонтироваться. Путь при этом существует как пустой каталог на корневом разделе, и приложение запускается с отсутствующими uploads, конфигурацией или базой. В худшем случае оно записывает новые данные не туда, а после восстановления mount эти файлы становятся невидимыми.

findmnt findmnt /srv/app-data systemctl status srv-app-data.mount --no-pager journalctl -b -u srv-app-data.mount --no-pager lsblk -f mountpoint /srv/app-data
  • проверьте UUID в fstab, а не только имя устройства;
  • задайте зависимость RequiresMountsFor для критичного пути;
  • проверьте доступность сетевого хранилища после поднятия сети;
  • убедитесь, что /run и /tmp создаются до сокета или PID-файла;
  • используйте RuntimeDirectory для каталога в /run, если это подходит unit;
  • не выполняйте chmod -R 777 для устранения ошибки доступа;
  • до повторного mount проверьте, не появились ли файлы в скрываемом каталоге.

Если mount отсутствует, остановите пишущее приложение перед восстановлением пути. Иначе часть данных окажется на корневом разделе, часть — на целевом хранилище. Переносите такие файлы только после сравнения времени, владельцев и целостности.

Проверьте переменные окружения, секреты и права

При ручном запуске приложение может получать DATABASE_URL и ключи из .bashrc, локального .env или менеджера секретов, доступного интерактивному пользователю. Systemd запускает другой контекст. После reboot временный агент секретов или расшифрованный файл может быть недоступен. В журнале это выглядит как пустая строка подключения, отказ аутентификации или отсутствие конфигурации.

  • проверьте наличие EnvironmentFile без вывода его содержимого;
  • сравните имена переменных и окружение deployment;
  • убедитесь, что сервис секретов запускается раньше приложения;
  • проверьте владельца и режим файлов конфигурации;
  • не передавайте секреты через аргументы командной строки;
  • не выводите полное окружение в журнал;
  • реализуйте явную ошибку при отсутствии обязательной переменной.

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

Проверьте порт, сокет и старые PID-файлы

Веб-сервер может подняться раньше приложения, а приложение — не открыть порт из-за конфликта. Другой unit, старый процесс или два manager запускают один и тот же сервис. Для Unix socket возможны неверный каталог, владелец или порядок создания. PID-файл в постоянном каталоге иногда указывает на уже несуществующий процесс и мешает старту.

ss -lntp ss -lxnp systemctl status my-app.service --no-pager -l ps -ef | grep '[m]y-app' stat /run/my-app /run/my-app/app.sock
  • запускайте один экземпляр через один supervisor;
  • не стартуйте тот же процесс одновременно из cron, rc.local и systemd;
  • создавайте runtime-каталог штатно и с нужным владельцем;
  • согласуйте путь сокета в приложении и Nginx;
  • проверяйте bind на 127.0.0.1, 0.0.0.0 или IPv6 в зависимости от схемы;
  • удаляйте stale PID только после проверки, что процесс действительно отсутствует;
  • не открывайте внутренний порт наружу ради временного обхода proxy.

Если сайт работает в Docker или Compose

Контейнер, запущенный командой docker run, не обязательно вернется после reboot. Важны restart policy, автозапуск Docker, существование network и volume, доступ к env-файлу и корректный Compose project. Контейнер может быть running, но приложение внутри — неготово или постоянно перезапускается.

systemctl status docker --no-pager docker ps -a docker inspect --format '{{.Name}} {{.HostConfig.RestartPolicy.Name}} {{.State.Status}} {{.State.ExitCode}}' CONTAINER docker logs --since 30m CONTAINER docker compose ps docker compose config --quiet
  • проверьте restart policy каждого обязательного контейнера;
  • не используйте always для маскировки постоянного падения;
  • добавьте healthcheck, который проверяет готовность, а не наличие процесса;
  • используйте named volume или проверенный абсолютный bind mount;
  • убедитесь, что env-файл доступен из контекста запуска Compose;
  • проверьте порядок и readiness базы, а не только depends_on;
  • зафиксируйте имя проекта, чтобы после boot не создать второй набор контейнеров.

Если Compose запускается через systemd, unit должен указывать абсолютный WorkingDirectory и конкретный compose-файл. Команда down при остановке удаляет network и контейнеры, что не всегда требуется; сценарий остановки выбирают осознанно. Обновление image и миграции нельзя запускать автоматически при каждом reboot без контроля версии.

Проверьте базу, кэш и очередь

Приложение может слушать порт, но отдавать 500 или 503, пока не подключится к базе. После некорректного выключения PostgreSQL, MySQL/MariaDB или Redis может выполнять recovery, а очередь — возвращать незавершенные задания. Перезапуск веб-приложения не ускоряет восстановление базы и способен создать дополнительную нагрузку.

  • проверьте status и журнал базы в текущем boot;
  • убедитесь, что раздел данных смонтирован и имеет правильного владельца;
  • проверьте завершение recovery и доступность локального сокета или порта;
  • не удаляйте lock-файлы базы без процедуры конкретной СУБД;
  • не выполняйте repair или reset данных как первый шаг;
  • проверьте миграции отдельно от обычного запуска приложения;
  • восстановите queue worker только после готовности его зависимостей.

Readiness приложения должна отличать временное ожидание базы от необратимой ошибки конфигурации. В первом случае процесс может повторять соединение с backoff, во втором — завершиться с понятным кодом, чтобы systemd не создавал бесконечный шум.

Проверьте нехватку памяти и аварийное завершение

Во время boot одновременно запускаются база, кэш, контейнеры, антивирус, резервное копирование и приложение. Пиковая память выше обычной, поэтому OOM killer может завершить один процесс. Через несколько минут ручной start работает, потому что пик уже прошел. Этот сценарий подтверждают kernel journal и статус unit, а не догадка по текущему free.

journalctl -b -k | grep -Ei 'out of memory|oom|killed process' systemctl show my-app.service -p MemoryCurrent -p MemoryPeak -p OOMPolicy free -h swapon --show

Исправление зависит от причины: ограничить параллельный старт, настроить ресурсы контейнеров, убрать утечку, добавить разумный swap или увеличить RAM. Нельзя просто выключать OOM-защиту. После изменения проведите холодный тест и проверьте пиковое потребление всех сервисов.

Пошаговый порядок безопасного исправления

Исправляйте первую доказанную причину и каждый раз проверяйте штатный старт. Ручной запуск под root не подтверждает, что автозапуск работает. Финальный тест обязательно включает новую контролируемую перезагрузку в согласованное окно и проверку всех зависимых функций.

  1. Зафиксируйте boot ID, failed unit, порты, mount, ресурсы и первую ошибку.
  2. Определите последний работающий слой: сеть, proxy, runtime, приложение, база или хранилище.
  3. Проверьте status и journal нужного unit только в текущей загрузке.
  4. Сравните active и enabled, содержимое unit и его зависимости.
  5. Проверьте абсолютные пути, пользователя, WorkingDirectory и EnvironmentFile.
  6. Убедитесь в готовности network, mount, базы и secret service.
  7. Для Docker проверьте daemon, restart policy, volume, network и health.
  8. Исправьте одну причину и выполните daemon-reload, если менялся unit.
  9. Запустите сервис штатно и проверьте локальный health и публичный HTTP.
  10. Проверьте фоновые worker, cron/systemd timer, очереди и отправку почты.
  11. Выполните контролируемый reboot и наблюдайте critical chain.
  12. После загрузки проверьте HTTP 200, логи, порты, ресурсы и автозапуск.

Перед reboot убедитесь, что есть доступ к консоли хостинга или другому аварийному каналу. Изменение firewall, сети, SSH и дисков может лишить удаленного доступа. Резервная копия конфигурации должна находиться вне изменяемого сервера и иметь понятный способ восстановления.

Как проверить результат после исправления

  • обязательные unit имеют enabled и active без failed состояния;
  • сервис стартует под заданным пользователем без интерактивного профиля;
  • диски и сетевые хранилища готовы до записи приложения;
  • Nginx или Apache проходит проверку конфигурации;
  • upstream слушает ожидаемый адрес, порт или сокет;
  • база завершает recovery, и приложение достигает ready;
  • Docker-контейнеры не находятся в restart loop;
  • локальный и внешний health возвращают ожидаемый результат;
  • динамическая страница, загрузка файла и фоновая задача работают;
  • journal текущего boot не содержит повторяющейся ошибки;
  • второй контролируемый reboot дает такой же результат без ручного входа.

Проверяйте не только главную страницу. Кэшированный HTML может открываться при неработающей базе или очереди. Нужен безопасный smoke-тест динамической операции, которая не меняет критичные данные, а также проверка расписаний, worker и сертификатного обновления.

Типичные ошибки при восстановлении

  • много раз перезагружать сервер вместо чтения журнала текущего boot;
  • запускать приложение вручную под root и считать проблему решенной;
  • путать active с enabled;
  • добавлять sleep вместо проверки реальной готовности зависимости;
  • использовать относительные пути и переменные из .bashrc;
  • делать chmod -R 777 для каталогов приложения;
  • удалять socket, PID или lock-файл без проверки процесса;
  • очищать Docker volume или данные базы ради быстрого старта;
  • читать журнал предыдущей загрузки как текущую ошибку;
  • включать бесконечный Restart=always для постоянно падающего процесса;
  • делать reboot без консоли хостинга и резервной копии конфигурации;
  • проверять только статическую главную страницу.

Опасно складывать автозапуск в rc.local, cron @reboot и systemd одновременно. Два механизма создают гонку и конфликт порта. У каждого процесса должен быть один владелец жизненного цикла, а его конфигурация — храниться и проверяться как код.

Как предотвратить повторение

Автозапуск нужно проверять регулярно, а не только после аварии. В инфраструктуре должны быть явные unit, health checks, ограниченные рестарты, мониторинг и runbook. Обновление runtime, путей deployment или пользователя обязано сопровождаться тестом загрузки на staging или новом узле.

  • храните unit и Compose-файлы в системе контроля версий без секретов;
  • используйте абсолютные пути и отдельного системного пользователя;
  • задавайте зависимости только там, где они действительно обязательны;
  • проверяйте readiness базы и внешних сервисов с backoff;
  • используйте адресный health endpoint без изменения данных;
  • ограничивайте restart loop и алертируйте по failed unit;
  • контролируйте место, inode, RAM, mount и срок сертификата;
  • проверяйте резервные копии восстановлением;
  • проводите контролируемый reboot после существенных изменений;
  • держите аварийный доступ через консоль провайдера;
  • документируйте порядок восстановления и ответственных.

Полезный мониторинг проверяет не только TCP 443, но и готовность приложения, подключение к базе и возраст успешной фоновой задачи. Тогда сервер может быть online, но неготовый сайт будет замечен до жалоб пользователей.

Когда нужна помощь с автозапуском сайта

Если сайт не стартует после reboot, я могу восстановить цепочку текущей загрузки, найти первый упавший unit, проверить systemd, Nginx или Apache, runtime, Docker, mount, базу, переменные окружения и порядок зависимостей. Затем исправлю подтвержденную причину, настрою ограниченный автозапуск и проведу контролируемую проверку после перезагрузки без опасных прав и бесконечных restart loop. Для первичной оценки достаточно обезличенного вывода systemctl --failed, статуса нужного unit, первой ошибки journalctl -b и описания стека — пароли, EnvironmentFile и приватные ключи присылать не нужно.