Большинство аварий на VPS у начинающих случаются не из-за хакеров и не из-за DDoS. Это пять-шесть одних и тех же историй: доступ к серверу закрыли сами себе, диск забился логами, базу данных убила нехватка памяти, наружу торчал Redis без пароля, бэкап лежал на том же сервере, что и сайт. Ниже разобрана каждая: как она выглядит изнутри, почему происходит и что сделать, чтобы с тобой её не случилось.
Если сервер только что куплен, сначала пройди первые шаги настройки VPS: там порядок действий, при котором половина ошибок из этой статьи просто не возникает. Если сервер уже работает, начни с проверки ниже.
Проверка за пять минут
Зайди на сервер по SSH и выполни команды по очереди. Они ничего не меняют, только показывают состояние.
df -h df -i free -h swapon --show sudo ss -tlnp sudo ufw status verbose journalctl --disk-usage cat /etc/os-release ls /var/run/reboot-required
На что смотреть в ответах:
df -hиdf -i: если место или inode заняты больше чем на 85%, разбираться стоит сегодня, пока сайт ещё работает.free -hиswapon --show: swap пустой (вторая команда ничего не выводит), а свободной памяти меньше пары сотен мегабайт? При первом всплеске трафика ядро начнёт убивать процессы, и первой обычно попадает база.ss -tlnp: смотри на колонку с адресом.127.0.0.1:3306означает, что база принимает подключения только с самого сервера.0.0.0.0:3306или*:6379означает, что сервис слушает весь интернет.ufw status: «Status: inactive» значит, что файрвол выключен. Если он включён, проверь, что в списке есть порт SSH./etc/os-release: Ubuntu 20.04 и более ранние версии, CentOS 7 и 8 обычных обновлений безопасности уже не получают.- Если файл
reboot-requiredсуществует, обновление ядра установлено, но сервер работает на старом ядре, пока его не перезагрузить.
И один вопрос, на который команды не ответят: где лежит последний бэкап и когда ты в последний раз из него что-нибудь восстанавливал. Если ответ «на этом же сервере» или «ни разу», раздел про бэкапы ниже читай первым.
Как запереть себя снаружи
Самая частая авария первой недели, и почти всегда она устроена одинаково. Человек включает файрвол по инструкции, сервер предупреждает, что команда может оборвать SSH-подключения, человек отвечает «y». Текущая сессия при этом обычно продолжает жить, т.к. UFW пропускает уже установленные соединения, поэтому всё выглядит нормально. Сюрприз приходит на следующий день, когда новое подключение висит до таймаута.
Правильный порядок такой: сначала разрешить SSH, потом включать.
sudo ufw allow OpenSSH sudo ufw enable
Профиль OpenSSH открывает только стандартный порт 22. Если ты перевёл SSH на другой порт, разреши именно его: sudo ufw allow 2222/tcp. Подробно правила и их порядок разобраны в статье про UFW и iptables.
Вторая версия той же истории связана с SSH-ключами. Вход по паролю отключают раньше, чем убедились, что вход по ключу работает. Третья связана со сменой порта: в sshd_config порт поменяли, а в файрволе открыть забыли. В Ubuntu 24.04 есть ещё одна ловушка. SSH там запускается через ssh.socket, и после правки порта простой systemctl restart ssh ничего не меняет, нужны systemctl daemon-reload и перезапуск самого сокета. Как это сделать без риска, описано в статье про SSH-ключи.
Защита от всех трёх сценариев одна и та же. Пока меняешь что-то, связанное с доступом, держи открытой вторую SSH-сессию и проверяй вход в третьем окне, не закрывая первые два. Перед перезапуском SSH проверяй файл настроек командой sudo sshd -t: если она молчит, синтаксис в порядке. Для изменений файрвола пригодится «мёртвая рука», т.е. таймер, который откатит изменение сам, если ты потеряешь доступ:
sudo systemd-run --on-active=5min /usr/sbin/ufw disable
Команда ответит именем таймера вида run-r1a2b3c4.timer. Если за пять минут ты спокойно зашёл в новом окне, отмени его:
sudo systemctl stop run-r1a2b3c4.timer
Если дверь всё-таки захлопнулась, у большинства провайдеров в панели есть консоль (её называют VNC, KVM или просто «Консоль»). Это экран и клавиатура сервера, им файрвол не мешает. Но войти через неё можно только по паролю, а если провайдер выдал сервер с одним ключом, у root пароля может не быть вовсе. Тогда остаётся сброс пароля из панели или режим восстановления. Узнай, что из этого есть у твоего провайдера, заранее, пока доступ есть.
Ещё одно место, где теряют доступ, находится вообще не на сервере. У облачных провайдеров бывает собственный внешний файрвол в панели (security groups, «сетевые правила»). Порт открыт в UFW, сервис слушает, а снаружи тишина, потому что пакет режется раньше, чем доходит до машины.
UFW включён, а порт всё равно открыт
Картина, которую на клиентских серверах видишь чаще, чем хотелось бы. Человек аккуратно настроил UFW, в ufw status открыты только 22, 80 и 443. А процессор загружен на 100%, и в списке процессов висит что-то с невнятным именем, которое никто не запускал. Через пару дней от провайдера приходит жалоба на исходящее сканирование чужих сетей.
Почти всегда за этим стоит Docker. Когда контейнер запускают с -p 6379:6379, Docker сам прописывает правила перенаправления в iptables, и трафик к контейнеру уходит в обход тех цепочек, где живут правила UFW. Это не баг: в документации Docker прямо сказано, что с UFW они несовместимы. Файрвол показывает одно, а снаружи порт открыт.
Дальше накладывается вторая особенность. Официальный образ Redis собран с выключенным защищённым режимом, чтобы к нему было удобно подключаться из соседних контейнеров. Пароля по умолчанию тоже нет. В итоге одна строчка из чьего-то docker-compose выставляет в интернет базу, в которую может писать кто угодно, а боты ищут открытый 6379-й порт круглосуточно. Данные в пустом Redis ботам не нужны, через него на сервер заносят майнер.
Лечится это указанием адреса при публикации порта. Если сервис нужен только приложению на этом же сервере, публикуй его на 127.0.0.1:
services:
redis:
image: redis
ports:
- "127.0.0.1:6379:6379" А если к Redis ходят только другие контейнеры, порт вообще не нужно публиковать: контейнеры в одной сети Docker видят друг друга по имени сервиса. Подробнее об этом в статье о Docker на VPS.
Проверять, что закрыто, нужно снаружи: с самого сервера порт ответит в любом случае. С домашнего компьютера (на Windows подойдёт WSL или Test-NetConnection в PowerShell):
nc -zv 203.0.113.10 6379
Вместо 203.0.113.10 подставь IP своего сервера. «Connection refused» или долгое ожидание означают, что порт закрыт. «succeeded» означает, что он открыт всему миру.
Без всякого Docker та же история случается с MySQL и MariaDB. В Debian и Ubuntu они из коробки слушают только 127.0.0.1, и это разумно. Но захотелось подключиться к базе из DBeaver с домашнего ноутбука, в настройках появилось bind-address = 0.0.0.0, задача решилась, а строчку так и не вернули. Для разовой работы с базой проще SSH-туннель: база остаётся закрытой, а клиент на ноутбуке подключается к 127.0.0.1:3306, как будто она стоит рядом.
ssh -L 3306:127.0.0.1:3306 user@203.0.113.10
Другие варианты, включая постоянный доступ для приложения с другого сервера, собраны в статье о безопасном удалённом подключении к базе данных.
Диск забит, а ты ничего не скачивал
Снаружи это выглядит так: WordPress пишет «Error establishing a database connection», MySQL не стартует, в логах nginx и PHP мелькает No space left on device. Иногда сломано всё сразу, и непонятно, с чего начинать. Начинать стоит с поиска, кто съел место:
sudo du -xh / --max-depth=2 2>/dev/null | sort -h | tail -20
Подозреваемых обычно немного.
Логи контейнеров. Docker по умолчанию пишет вывод каждого контейнера в JSON-файл без ограничения размера. Болтливое приложение за полгода набивает десятки гигабайт, и docker system df показывает это одной строкой. Лимит задаётся в /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
} Подвох в том, что после sudo systemctl restart docker лимит действует только на новые контейнеры. Уже запущенные придётся пересоздать, например через docker compose up -d --force-recreate. Об этом молчат почти все инструкции, и люди неделями удивляются, почему настройка «не работает».
Бинарные логи MySQL. В MySQL 8 они включены по умолчанию и хранятся 30 дней. Для блога это мелочь, а у интернет-магазина с активными заказами и обновлением остатков набегает несколько гигабайт. В MariaDB они по умолчанию выключены.
Бэкапы, которые скрипт складывает рядом с сайтом и никогда не удаляет. Про них ещё будет ниже, т.к. проблема у них не только в месте.
Системный журнал (journald) ограничивает себя сам: по умолчанию он занимает до 10% раздела, но не больше 4 ГБ. На диске в 20 ГБ это всё равно 2 ГБ, так что journalctl --disk-usage посмотреть стоит, хотя главным виновником журнал бывает редко.
Есть два случая, которые сбивают с толку даже тех, кто знает команды выше. Первый: ты удалил огромный лог, а место не освободилось. Файл исчез из каталога, но процесс, который в него писал, держит его открытым, и место вернётся только после перезапуска процесса. Найти такие файлы помогает sudo lsof +L1, а исправить ситуацию обычно хватает systemctl reload нужного сервиса. Второй: df -h показывает 40% занятого места, а файлы не создаются. Тогда смотри df -i: закончились inode, т.е. записи о файлах. Так бывает, когда плагин кэширования или PHP-сессии плодят миллионы крошечных файлов. Каждый весит копейки, но у файловой системы есть лимит на их количество.
Как разгрести диск аккуратно, ничего не сломав, разобрано в статье об очистке места на диске, а про ротацию логов есть статья о работе с логами сервера.
Памяти хватало, пока не пришли посетители
Сайт неделями работает нормально, а потом пару раз в день на несколько секунд падает с ошибкой подключения к базе и сам поднимается обратно. Хостера подозревают в нестабильности, а он ни при чём. Причину видно в журнале ядра:
sudo journalctl -k | grep -i -E "out of memory|killed process"
Если там есть строки вида Out of memory: Killed process 1234 (mariadbd), всё ясно. Память закончилась, и ядро убило процесс, чтобы спасти систему. Выбирает оно тот, что освободит больше всего памяти, а на типичном веб-сервере это база данных. systemd через пару секунд поднимает её снова, поэтому падение и выглядит коротким.
Готовые образы ОС у многих провайдеров идут без swap, так что запаса нет совсем. Swap-файл на 1–2 ГБ даёт серверу пережить всплеск, медленно, но без убитых процессов. Однако swap работает как подушка безопасности, а не как дополнительная память. Если free -h показывает, что он занят постоянно, сервер либо мал для задачи, либо настроен с запасом, которого у него нет. Чаще всего второе. PHP-FPM, т.е. менеджер процессов PHP, по умолчанию может запустить столько обработчиков, сколько не влезет ни в какую память. Каждый процесс WordPress обычно занимает 40–80 МБ, и двадцать таких процессов на сервере с 1 ГБ это гарантированная встреча с OOM killer в первый же наплыв посетителей. Как посчитать лимит под свой сервер, описано в статье об оптимизации PHP-FPM, а как создать swap, в статье о swap-файле на VPS.
На контейнерных тарифах OpenVZ swap-файл обычно создать нельзя, а free там показывает не совсем то, что выделено тебе на самом деле. Если упираешься в память на таком тарифе, начни с того, чтобы разобраться, чем OpenVZ отличается от KVM.
Бэкап, который не спасёт
Бэкапы делают почти все. Восстановить из них сайт получается заметно реже, и причины довольно однообразные.
Копия лежит на том же сервере. От удалённого по ошибке файла она спасёт, а от всего остального нет: умер диск, сервер взломали, провайдер потерял узел, и копия уходит вместе с оригиналом. Взломщики, к слову, архивы рядом с сайтом находят быстро. Плюс эти архивы и есть тот самый «съеденный» диск из раздела выше.
Копия лежит у того же провайдера, в его снапшотах или платной услуге бэкапа. Это уже лучше, но аккаунт по-прежнему один. Заблокировали аккаунт за неоплату или по жалобе, и пропадает сразу всё: и сервер, и его копии. Снапшот к тому же снимается с работающей машины, и база в нём сохраняется в том состоянии, в каком была на этот момент посреди записи. MySQL с InnoDB обычно из такого состояния восстанавливается сам, но «обычно» и «гарантированно» не одно и то же. В чём ещё снапшот отличается от бэкапа, разобрано в статье про snapshot и backup.
В копии нет базы. Скрипт архивирует /var/www, а без базы данных файлы WordPress почти бесполезны: тексты, заказы и пользователи живут в базе.
И самое обидное: копия есть, но пустая. Скрипт с mysqldump полгода исправно создавал файлы по 20 байт, потому что после смены пароля к базе дамп падал с ошибкой. Cron отправлял письмо об этом на локальную почту root, которую на VPS не открывает никто и никогда. Узнают о таком в день, когда бэкап нужен.
Минимум, на котором я бы остановился даже для маленького проекта: автоматический дамп базы и файлов, одна копия за пределами аккаунта провайдера (другой провайдер, S3-совместимое хранилище, домашний NAS) и пробное восстановление раз в квартал на отдельный дешёвый VPS. Только пробное восстановление показывает, что в архиве действительно есть всё нужное. С чего начать, описано в статье об основах резервного копирования. Вынести копии наружу поможет статья об удалённом бэкапе, а дамп базы по расписанию разобран в статье об автобэкапе MySQL.
Обновления: два способа ошибиться
Первый способ: не обновлять ничего, потому что «работает, не трогай». В Ubuntu автоматические обновления безопасности включены по умолчанию, и это хорошо. Но перезагружать сервер сами они не будут, а исправления ядра без перезагрузки не вступают в силу. Отсюда серверы с аптаймом в 400 дней, которыми гордятся владельцы. Гордиться там нечем: это 400 дней на ядре с известными дырами. Как настроить автообновления с перезагрузкой в удобное ночное время, разобрано в статье об автоматических обновлениях безопасности.
Хуже, когда устарела сама система. CentOS 7 закончила поддержку летом 2024 года, у Ubuntu 20.04 стандартная поддержка закончилась в мае 2025-го. Обновления для неё ещё можно получать через Ubuntu Pro, который бесплатен для личного использования на нескольких машинах, но многие об этом не знают и сидят без патчей. Если сервер настраивали «когда-то давно», загляни в /etc/os-release прямо сейчас.
Второй способ ошибиться противоположный: запустить do-release-upgrade на работающем сайте без снапшота. Типичный сценарий выглядит так. После перехода с Ubuntu 22.04 на 24.04 сайт отдаёт 502. Сначала ищут проблему в WordPress, а дело в том, что вместе с системой PHP обновился с 8.1 до 8.3, и nginx стучится в сокет php8.1-fpm.sock, которого больше нет. Это ещё лёгкий случай. Бывает, что старый плагин просто несовместим с новым PHP.
По-моему, для сервера, на котором работают сайты, переход на новую мажорную версию ОС надёжнее делать переездом: поднять рядом чистый VPS с новой системой, перенести сайт, проверить, переключить DNS. Это дольше, чем одна команда, зато старый сервер остаётся работающим запасным вариантом до самого конца. Если всё же обновляешь на месте, сначала снапшот, потом апгрейд. Подробнее об этом в статьях об обновлении ОС и о выборе ОС для VPS.
Правка конфига без проверки
У каждого серьёзного сервиса есть команда, которая проверяет файл настроек, ничего не перезапуская:
sudo nginx -t sudo sshd -t sudo apachectl configtest sudo php-fpm8.3 -t
Порядок простой: копия файла, правка, проверка, и только потом systemctl reload. Причём reload там, где сервис его поддерживает: nginx при reload с ошибкой в настройках продолжит работать на старых, а restart с той же ошибкой оставит тебя без сайта.
Отдельно о копировании команд из интернета. Блоги на WordPress (и не только) любят «улучшать» типографику: двойной дефис превращается в длинное тире, прямые кавычки в ёлочки или лапки. Выглядит красиво, а –dport вместо --dport iptables уже не понимает. В старых версиях статей этой энциклопедии мы на этом тоже попадались, поэтому команды теперь только в блоках кода. Если команда из статьи выдаёт странную ошибку синтаксиса, первым делом перенабери тире и кавычки руками.
И про chmod -R 777. Это самый популярный ответ на «Permission denied» на форумах, и он действительно убирает ошибку. Заодно он разрешает запись в каталог сайта любому процессу на сервере, включая PHP-скрипт, который однажды зальют через дырявую форму загрузки. Права выставляются точечно, и как это сделать, объясняет статья о правах на файлы.
Тариф не под задачу
Часть проблем закладывается ещё при покупке. Самая частая: 1 ГБ памяти под WordPress с WooCommerce, панель управления и почтовый сервер одновременно. Панель с почтой и антивирусом сама по себе съедает заметную часть этого гигабайта, и магазину остаются крохи. Тут даже правильная настройка PHP-FPM не спасёт, памяти просто нет. Сколько ресурсов занимают разные панели, видно из сравнения панелей управления для VPS.
Похожая история с почтой. Многие провайдеры на новых аккаунтах закрывают исходящий 25-й порт, чтобы с их сетей не шёл спам, и письма с сервера просто не уходят. А если уходят, без правильной обратной DNS-записи они падают в спам у получателя. Стоит ли вообще держать почту на VPS, разобрано в статье о почтовом сервере.
Если на сервере должны работать Docker или WireGuard, проверь тип виртуализации до оплаты: на контейнерной OpenVZ им может не хватить доступа к ядру. А если сервер тормозит при скромной нагрузке, загляни в top на значение st (steal). Высокий steal означает, что процессорное время у тебя забирают соседи по физической машине, т.е. провайдер продал ресурсов больше, чем у него есть. Это классический оверселлинг, и настройками внутри VPS он не лечится. Как поймать его на тестовом периоде, описано в статье о тестировании VPS перед покупкой.
Если ты как раз выбираешь, где брать сервер под проект, провайдеры с реальными замерами собраны на главной странице портала, а как прикинуть нужный объём памяти и процессора, подскажет статья о выборе тарифа. И честный вопрос самому себе: если не хочется разбираться ни с чем из этой статьи, может, тебе нужен управляемый сервер, где обновления и бэкапы берёт на себя провайдер.
Сервер удалили за неуплату
Со стороны провайдера это одна из самых грустных историй, и она повторяется регулярно. Карта истекла, автоплатёж не прошёл, сервер приостановили, а через срок от нескольких дней до пары недель (у каждого провайдера свой) удалили вместе с дисками. Все предупреждения ушли на почту, указанную при регистрации пять лет назад, которую владелец давно не открывает. Почти так же заканчиваются проигнорированные абузы: если взломанный сервер рассылает спам, провайдер пишет письмо с требованием разобраться в срок, а после срока блокирует.
Лечится скучно и надёжно: рабочая почта в аккаунте, напоминание в календаре о дате оплаты, а лучше запасная карта. Домен, кстати, истекает ровно так же…
Об аварии узнают от клиентов
Сервер не может сообщить о собственной смерти, поэтому проверять его доступность нужно извне. Бесплатного внешнего мониторинга, который раз в несколько минут открывает сайт и пишет в Telegram, когда тот не отвечает, хватает для начала, а свой можно поднять на отдельном дешёвом VPS, об этом статья про Uptime Kuma. К нему стоит добавить предупреждение о заполнении диска и памяти, чтобы узнавать о проблемах из разделов выше за неделю до аварии. Что и чем отслеживать, разобрано в статье о мониторинге нагрузки.
Источники
- Packet filtering and firewalls – Docker Docs
- JSON File logging driver – Docker Docs
- redis – Docker Official Image (раздел Security)
- Redis security – документация Redis
- Firewalls – Ubuntu Server documentation
- Automatic updates – Ubuntu Server documentation
- Ubuntu release cycle – Canonical
- journald.conf – systemd manual
- systemd-run – systemd manual
- Binary Logging Options and Variables – MySQL Reference Manual
- Command-line parameters – документация nginx

