Если сайт не стартует после reboot, не спешите снова перезагружать VPS и запускать все процессы вручную. Повторный reboot стирает часть временных признаков и не исправляет отсутствующий автозапуск, неверный порядок зависимостей, недоступный диск, переменную окружения или конфликт порта. Сначала нужно определить, какой слой не поднялся в текущей загрузке: сеть, веб-сервер, приложение, база, контейнер, очередь или файловое хранилище.
Ручной запуск через SSH часто проходит, потому что к этому моменту сеть и диски готовы, пользователь имеет другой PATH, а команда выполняется из правильного каталога. При загрузке systemd или Docker запускает сервис раньше, в чистом окружении и под отдельной учетной записью. Поэтому «после systemctl start работает» — важный диагностический признак, а не готовое исправление.
Сначала восстановите доступность без потери доказательств
Зафиксируйте время загрузки и состояние сервисов до массовых рестартов. Если сайт критичен, можно запустить один подтвержденный компонент штатной командой, но сначала сохраните его status и журнал текущей загрузки. Не меняйте одновременно unit, права файлов, firewall и конфигурацию Nginx: после такого набора невозможно понять первопричину.
- запишите время последней загрузки и boot ID;
- сохраните список failed unit и состояние стека сайта;
- зафиксируйте, какие порты слушаются и каким процессом;
- проверьте место, inode, память и монтирование нужных файловых систем;
- сохраните журналы веб-сервера, приложения, базы и контейнеров;
- не удаляйте PID-файлы, сокеты, volume и логи до понимания их состояния;
- не публикуйте EnvironmentFile, пароли базы, токены и приватные ключи.
Команды выше читают состояние. Выполняйте их с учетной записью, которая имеет право видеть нужные процессы и журналы. Не копируйте весь 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 и базу.
Подставляйте свой безопасный 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 текущей загрузки.
Приложение должно переживать временную недоступность внешней сети: повторять соединение ограниченно, показывать понятный 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 не подтверждает, что автозапуск работает. Финальный тест обязательно включает новую контролируемую перезагрузку в согласованное окно и проверку всех зависимых функций.
- Зафиксируйте boot ID, failed unit, порты, mount, ресурсы и первую ошибку.
- Определите последний работающий слой: сеть, proxy, runtime, приложение, база или хранилище.
- Проверьте status и journal нужного unit только в текущей загрузке.
- Сравните active и enabled, содержимое unit и его зависимости.
- Проверьте абсолютные пути, пользователя, WorkingDirectory и EnvironmentFile.
- Убедитесь в готовности network, mount, базы и secret service.
- Для Docker проверьте daemon, restart policy, volume, network и health.
- Исправьте одну причину и выполните daemon-reload, если менялся unit.
- Запустите сервис штатно и проверьте локальный health и публичный HTTP.
- Проверьте фоновые worker, cron/systemd timer, очереди и отправку почты.
- Выполните контролируемый reboot и наблюдайте critical chain.
- После загрузки проверьте 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 и приватные ключи присылать не нужно.