Как защитить админ-панель сайта на VPS: пошаговая настройка
Стоит запустить новый сайт на VPS — и уже через несколько часов в логах веб-сервера появляются запросы к /wp-login.php, /administrator и /phpmyadmin. Лично к вам это отношения не имеет: автоматические сканеры обходят целые диапазоны адресов и проверяют типовые пути входа. Нашли форму авторизации — начинают подбирать пароли.
Админ-панель открывает доступ к контенту, настройкам, плагинам и нередко к выполнению кода на сервере. Поэтому защита админ-панели входит в обязательный минимум для любого проекта, будь то блог, интернет-магазин или корпоративный портал. На виртуальном сервере всё держится на вас: готовой панели хостинга, которая закрыла бы лишнее, нет, зато есть полный контроль над сетью, веб-сервером и приложением.
В статье разберём, откуда приходят атаки, как собрать несколько независимых уровней защиты и в каком порядке их настраивать. Примеры написаны для Ubuntu, Nginx и WordPress, но принципы работают и для других CMS, и для самописных админок. Звучит громоздко? На практике базовая настройка занимает около часа.
Что такое админ-панель и почему её атакуют
Админ-панель — это веб-интерфейс, через который управляют сайтом: публикуют материалы, ставят плагины, меняют настройки и права пользователей. В WordPress это /wp-admin/, в Joomla — /administrator, в 1С-Битрикс — /bitrix/admin, в Django и Laravel — собственные разделы вроде /admin. Сюда же относятся инструменты управления базами данных, например phpMyAdmin и Adminer. Общая черта у всех одна: страница открыта из интернета, а за единственным паролем стоит полный контроль над проектом.
Что получает человек, который попал внутрь? Выбор большой: публикация спама и фишинговых страниц, внедрение вредоносного JavaScript, установка плагинов с бэкдорами, выгрузка клиентской базы, запуск майнера на вашем сервере. Сайт попадает в чёрные списки браузеров и поисковиков, а восстанавливать репутацию приходится месяцами. Именно поэтому админку атакуют чаще остальных частей сайта.
Чтобы выбирать меры осознанно, полезно понимать, с какими сценариями вы имеете дело. Основные из них собраны в таблице.
| Тип атаки | Как выглядит в логах | Чем закрывается |
|---|---|---|
| Брутфорс (перебор паролей) | Сотни POST-запросов к форме входа с одного или нескольких адресов | Лимит запросов, fail2ban, 2FA |
| Credential stuffing | Вход с парами логин-пароль из чужих утечек | Уникальные пароли, 2FA, белый список адресов |
| Уязвимости плагинов и тем | Запросы к известным путям плагинов, попытки загрузки файлов | Обновления, минимум плагинов, WAF |
| Перехват трафика | Логин и пароль уходят по HTTP открытым текстом | HTTPS, HSTS |
| Перебор имён пользователей | Запросы вида /?author=1 и /wp-json/wp/v2/users |
Отключение выдачи списка пользователей, нестандартный логин |
| Угон сессии | Вход по украденной cookie с другого адреса | Обновления, флаги Secure и HttpOnly, короткое время жизни сессии |
Как работает многоуровневая защита
Идея простая: ни одна мера не закрывает все сценарии, поэтому защиту строят слоями. Если злоумышленник обошёл один уровень, он упирается в следующий. Проще говоря, вы увеличиваете цену атаки, пока она не перестаёт окупаться. Большинство ботов сдаётся на первом же слое, а до остальных доходят единицы.
| Уровень | Что делает | Инструменты |
|---|---|---|
| Сеть | Оставляет открытыми только нужные порты, прячет панель от посторонних | UFW, VPN, SSH-ключи |
| Веб-сервер | Фильтрует запросы до того, как они дойдут до PHP | Nginx: allow/deny, basic auth, limit_req, HTTPS |
| Приложение | Проверяет, кто именно входит и что ему разрешено | 2FA, роли, настройки CMS |
| Наблюдение | Замечает перебор, банит источники, помогает восстановиться | fail2ban, логи, резервные копии |
Настраивать удобнее снаружи внутрь: сначала сеть, затем веб-сервер, потом приложение. Перед любыми правками стоит сделать копию сервера. На VPS Serverspace для этого подойдёт снимок: если новая конфигурация Nginx заблокирует вас самих, откатиться можно быстро. Подробнее о конфигурациях серверов — на странице VPS-серверов Serverspace.
Пошаговая настройка: как защитить админ-панель на VPS
Шаг 1. Обновите систему и включите автоматические обновления безопасности
Большая часть успешных взломов происходит через известные уязвимости, для которых давно вышли патчи. Начните с обновления пакетов:
sudo apt updatesudo apt upgrade -yЗатем включите автоматическую установку обновлений безопасности, чтобы не зависеть от собственной памяти:
sudo apt install unattended-upgrades -ysudo dpkg-reconfigure --priority=low unattended-upgradesОб обновлениях самой CMS и плагинов тоже не стоит забывать. Если установлен WP-CLI, всё обновляется одной командой:
wp plugin update --allШаг 2. Настройте брандмауэр и защитите SSH
Снаружи должны быть видны только порты 22 (SSH), 80 и 443. Всё остальное, включая порт базы данных 3306, закрывается. В Ubuntu для этого удобно использовать UFW. Важно соблюдать порядок: сначала разрешаем SSH и только потом включаем фильтр, иначе можно потерять доступ к серверу.
sudo ufw allow OpenSSHsudo ufw allow 'Nginx Full'sudo ufw enableТеперь SSH. Вход по ключам надёжнее паролей: в файле /etc/ssh/sshd_config задайте PasswordAuthentication no и PermitRootLogin no, затем перезапустите службу:
sudo systemctl restart sshПрежде чем закрывать текущую сессию, откройте вторую и убедитесь, что вход по ключу работает. Лишние пять минут сэкономят час возни с консолью восстановления.
Шаг 3. Включите HTTPS
Без шифрования логин и пароль от админки передаются открытым текстом, и любой промежуточный узел может их прочитать. Бесплатные сертификаты Let's Encrypt устанавливаются за пару минут:
sudo apt install certbot python3-certbot-nginx -ysudo certbot --nginx -d example.kz -d www.example.kzCertbot сам добавит редирект с HTTP на HTTPS и настроит автоматическое продление. Проверить, что продление работает, можно так:
sudo certbot renew --dry-runДля WordPress дополнительно добавьте в wp-config.php строку define('FORCE_SSL_ADMIN', true);. Она заставит передавать данные входа только по защищённому каналу.
Шаг 4. Разрешите вход только с доверенных адресов
Самая сильная мера из всего списка. Если админка отвечает лишь нескольким адресам, остальной интернет её просто не видит: боты получают ответ 403 и уходят искать другую цель. Доступ по IP настраивается в Nginx через директивы allow и deny внутри нужного блока location.
| Директива | Что делает | Пример |
|---|---|---|
allow |
Разрешает адрес или подсеть | allow 203.0.113.10; |
deny all |
Запрещает всем остальным; ставится после всех allow | deny all; |
location ~ |
Блок по регулярному выражению для страницы входа и PHP-файлов админки | location ~ ^/(wp-login\.php|wp-admin/(?!admin-ajax\.php).*\.php)$ |
Здесь есть тонкость, на которой спотыкаются многие. Запросы к .php-файлам обрабатывает отдельный блок с регулярным выражением, поэтому правила allow и deny в обычном блоке location /wp-admin/ до них просто не дойдут. Надёжнее описать защищаемые пути регулярным выражением из таблицы, поставить этот блок выше основного обработчика PHP и повторить в нём те же include и fastcgi_pass, что стоят в основном блоке. Файл admin-ajax.php исключён намеренно: к нему обращаются и обычные посетители, например формы и корзина.
После правки проверьте конфигурацию и перезагрузите Nginx:
sudo nginx -tsudo systemctl reload nginxУбедиться в результате можно с любого другого адреса, например с телефона через мобильный интернет, или командой с постороннего сервера. Ожидаемый ответ — 403:
curl -I https://example.kz/wp-login.phpШаг 5. Добавьте второй пароль на уровне веб-сервера
HTTP Basic Auth спрашивает логин и пароль ещё до того, как запрос попадёт в PHP. Даже если в форме входа или плагине найдут уязвимость, добраться до неё без этого пароля не получится. Создадим файл с учётной записью:
sudo apt install apache2-utils -ysudo htpasswd -c /etc/nginx/.htpasswd webadminЗатем в тот же блок location добавьте директивы auth_basic "Restricted"; и auth_basic_user_file /etc/nginx/.htpasswd;. По умолчанию действует режим satisfy all: пользователь должен пройти и проверку адреса, и ввод пароля. Если нужно разрешить вход либо с доверенного адреса, либо по паролю, добавьте satisfy any;. Пароль для Basic Auth должен отличаться от пароля в самой CMS — иначе смысл второго уровня теряется.
Шаг 6. Ограничьте частоту запросов к странице входа
Перебор паролей — это сотни запросов в минуту, тогда как живой человек вводит пароль раз в несколько секунд. Модуль limit_req отсекает слишком частые обращения до того, как они нагрузят PHP и базу данных. В блоке http (обычно в /etc/nginx/nginx.conf) объявите зону: limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;. Затем в блоке со страницей входа примените её: limit_req zone=login burst=3 nodelay; и limit_req_status 429;.
Пять запросов в минуту с одного адреса достаточно для обычной работы и мало для бота. Заодно закройте xmlrpc.php: через него можно перебирать пароли пачками, а нужен он лишь некоторым мобильным приложениям и Jetpack. Если вы ими не пользуетесь, добавьте блок location = /xmlrpc.php { deny all; }.
Шаг 7. Подключите fail2ban
Fail2ban читает журналы, находит повторяющиеся неудачные попытки входа и временно блокирует источник на уровне файрвола. Установка занимает минуту:
sudo apt install fail2ban -ysudo systemctl enable --now fail2banДля базовой авторизации Nginx в пакете уже есть готовый фильтр nginx-http-auth. Для страницы входа WordPress понадобится собственный. Создайте файл /etc/fail2ban/filter.d/wp-login.conf с секцией [Definition] и строкой failregex = ^<HOST> .* "POST /wp-login\.php[^"]*" 200. Расчёт здесь такой: при неверном пароле WordPress возвращает код 200, а при успешном входе — редирект 302, поэтому удачные входы под правило не попадают. Затем опишите «клетку» в файле /etc/fail2ban/jail.local:
| Параметр | Значение | Зачем нужен |
|---|---|---|
[wp-login] |
имя секции | Название «клетки», по нему вызывают статус и разбан |
enabled |
true | Включает правило |
filter |
wp-login | Подключает созданный фильтр |
logpath |
/var/log/nginx/access.log | Журнал, который разбирает fail2ban |
maxretry |
5 | Сколько неудач допустимо до бана |
findtime |
10m | Окно, в котором считаются попытки |
bantime |
1h | Длительность блокировки |
ignoreip |
ваш адрес | Защищает от бана самого администратора |
Остаётся перезапустить службу и проверить, что правило работает:
sudo systemctl restart fail2bansudo fail2ban-client status wp-loginЕсли вы случайно забанили своего сотрудника, снять блокировку можно одной командой:
sudo fail2ban-client set wp-login unbanip 203.0.113.50Шаг 8. Подключите второй фактор входа
Что будет, если пароль всё-таки подобрали или он утёк из другого сервиса? Здесь выручает двухфакторная аутентификация: без одноразового кода из приложения войти не получится, даже зная пароль. Для WordPress подойдут плагины Two Factor, WP 2FA и Wordfence Login Security. Они работают с приложениями-генераторами кодов вроде Google Authenticator, Aegis или 2FAS, а Two Factor поддерживает ещё и аппаратные ключи FIDO2.
Включайте второй фактор для всех, у кого есть права администратора и редактора, и сразу сохраните резервные коды в менеджере паролей. Заодно наведите порядок с учётными записями. Не используйте логин admin, выдавайте роли по минимуму, а бывших сотрудников удаляйте сразу. Список администраторов проверяется командой:
wp user list --role=administrator[/code>
Шаг 9. Уберите лишнее и спрячьте типовые пути
Эти меры не заменяют предыдущие, но снижают шум в логах и убирают очевидные лазейки:
- отключите редактор файлов в админке: добавьте в
wp-config.php строку define('DISALLOW_FILE_EDIT', true);, и через панель больше нельзя будет изменить код темы или плагина;
- перенесите адрес входа с
/wp-login.php на нестандартный путь плагином вроде WPS Hide Login, большинство ботов тогда не найдёт форму;
- уберите phpMyAdmin из публичного доступа и подключайтесь к нему через SSH-туннель, например командой
ssh -L 8080:127.0.0.1:80 user@203.0.113.5;
- ограничьте права на конфигурацию командой
sudo chmod 640 /var/www/example.kz/wp-config.php, чтобы файл с данными базы не читали посторонние процессы.
Шаг 10. Настройте журналы и резервные копии
Защиту нужно проверять. Раз в неделю полезно смотреть, кто чаще всего стучится на страницу входа:
sudo grep "POST /wp-login.php" /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head[/code>
Если в верхних строках сотни запросов с одного адреса, значит, перебор идёт, а лимиты и fail2ban делают свою работу. Дополнительно поставьте плагин журнала действий, чтобы получать уведомления о входах и смене ролей. И главное: резервная копия, которую ни разу не пробовали восстановить, копией не считается. Раз в квартал разворачивайте её на тестовом сервере. Хранить копии стоит отдельно от основного VPS, а перед крупными изменениями удобно делать снимок диска.
Плюсы и минусы каждой меры
Универсального рецепта нет: у каждой защиты своя цена в виде неудобств для команды и времени на поддержку. Сравнение помогает выбрать набор под свой проект.
Мера
Плюсы
Минусы
Белый список адресов
Прячет панель от всего интернета, не нагружает сервер
Неудобен при динамическом адресе, есть риск самоблокировки
Basic Auth
Срабатывает до PHP, закрывает даже уязвимости формы входа
Ещё один пароль для команды, может мешать плагинам и API
Лимит запросов
Гасит перебор на раннем этапе
Требует аккуратной настройки, чтобы не задеть живых пользователей
fail2ban
Автоматически банит источники атак
Ложные срабатывания за общим NAT, зависит от формата логов
2FA
Защищает от украденных и подобранных паролей
Нужно обучить команду и хранить резервные коды
Смена адреса входа
Убирает основной шум из логов
Целенаправленную атаку не остановит
VPN
Панель полностью недоступна снаружи, работает с любого устройства
Нужно поддерживать VPN-сервер и выдавать ключи
Ограничения и риски
Даже полный набор мер не делает сайт неуязвимым. Самый неприятный случай — уязвимость нулевого дня в плагине, позволяющая обойти авторизацию. Ни пароль, ни второй фактор тут не помогут, потому что атака идёт мимо формы входа. Против такого сценария работают только своевременные обновления, минимум установленных плагинов и WAF, который режет подозрительные запросы по сигнатурам.
Второй риск касается вас самих. Ошибка в списке адресов — и войти не сможет никто, включая администратора. Поэтому перед каждым изменением запускайте
nginx -t, держите открытой вторую SSH-сессию и заранее делайте снимок сервера.
Третья ловушка возникает, когда сайт стоит за CDN или обратным прокси. В этом случае Nginx видит адрес прокси, а не посетителя, и правила по адресам перестают работать. Хуже того, fail2ban может забанить сам прокси, и сайт станет недоступен для всех. Решение — настроить модуль real_ip: директивы set_real_ip_from с диапазонами прокси и real_ip_header X-Forwarded-For.
Остальные ограничения проще перечислить коротко. Офис с общим адресом даёт ложные срабатывания fail2ban, и тут выручает ignoreip. Жёсткие правила могут ломать REST API, wp-cron и мобильные приложения, поэтому после каждого изменения проверяйте формы, корзину и поиск. Смена адреса входа создаёт лишь ощущение безопасности, если за ней нет остальных слоёв.
Практические сценарии
Сценарий 1. Корпоративный сайт, команда сидит в офисе
У компании один статический адрес, и редакторов человек пять. Идеальный случай: в белый список попадает адрес офиса, всем остальным отдаётся 403. Сверху добавляют Basic Auth и второй фактор. Получается, что доступ по IP отсекает 99 % ботов, а оставшиеся упираются в два пароля и одноразовый код. Работать с такой настройкой можно годами без обслуживания.
Сценарий 2. Редактор работает из дома и в поездках
Динамические адреса ломают белые списки. Выход — поднять VPN-шлюз с постоянным адресом, например WireGuard на отдельном небольшом VPS:
sudo apt install wireguard -y
Адрес шлюза вносится в список разрешённых, а сотрудники подключаются к VPN перед входом в админку. Для всех остальных панель остаётся закрытой, а сами редакторы получают привычный интерфейс без лишних паролей.
Сценарий 3. Интернет-магазин на WooCommerce с десятком менеджеров
Менеджеры заходят из разных мест, и белый список здесь нереален. Опираются на другие слои: обязательная двухфакторная аутентификация для всех ролей выше покупателя, лимит запросов, fail2ban, роль Shop Manager вместо администратора там, где полных прав не нужно, и журнал действий. Параллельно подключают WAF — он закрывает атаки на платёжные плагины. Магазин получает защиту без неудобств для команды.
Сценарий 4. Самописная админка на Django или Laravel
Здесь удобнее вынести панель на отдельный поддомен, например admin.example.kz, с собственным серверным блоком Nginx, отдельным сертификатом и своими правилами. Основной домен вообще не знает о существовании админки, а значит, сканеры не найдут её по типовым путям. На поддомен накладывают белый список адресов, Basic Auth и ограничение частоты запросов.
Сценарий 5. Сайт ломают прямо сейчас
В логах сотни запросов к форме входа, сайт тормозит. Экстренная защита админ-панели строится по шагам:
- определите самые активные адреса командой из шага 10 и заблокируйте их, например
sudo ufw deny from 203.0.113.50; - включите лимит запросов и fail2ban, если их ещё нет;
- смените пароли всех администраторов и проверьте список ролей на незнакомые учётные записи;
- обновите ключи и соли в
wp-config.php, чтобы завершить все активные сессии; - проверьте недавно изменённые файлы командой
sudo find /var/www/example.kz -type f -mtime -2.
Типичные ошибки
| Ошибка | К чему приводит | Как исправить |
|---|---|---|
| Логин admin и простой пароль | Взлом перебором за несколько часов | Нестандартный логин, менеджер паролей, 2FA |
Правила allow/deny только в блоке location /wp-admin/ |
Блок PHP перехватывает запрос, и панель остаётся открытой для всех | Регулярное выражение выше обработчика PHP, проверка через curl с постороннего адреса |
Закрыт admin-ajax.php |
Перестают работать формы, поиск и корзина | Исключить файл из ограничения |
| Игнорирование CDN или прокси | Fail2ban банит адрес прокси, сайт недоступен | Настроить real_ip |
| UFW включён до разрешения SSH | Потеря доступа к серверу | Сначала allow OpenSSH, при проблемах помогает консоль в панели управления |
| Белый список (доступ по IP) без запасного канала | Адрес сменился, и администратор открывает панель всем «временно» | VPN с постоянным адресом |
| Резервные копии ни разу не восстанавливали | После взлома восстанавливаться не из чего | Тестовое восстановление раз в квартал |
Итоги
Надёжная защита админ-панели на VPS строится из нескольких простых слоёв. Сначала закрываем сеть и включаем HTTPS. Затем ограничиваем вход по адресам и добавляем Basic Auth на уровне Nginx. Поверх ставим лимиты и fail2ban, а в приложении включаем второй фактор и убираем лишние права. Каждая мера сама по себе небольшая, но вместе они превращают лёгкую добычу в слишком дорогую цель.
Если сомневаетесь, с чего начать, возьмите минимальный набор: HTTPS, 2FA, лимит запросов и fail2ban. Он занимает меньше часа и закрывает основную массу автоматических атак. Остальные уровни добавляйте по мере роста проекта. Для тестов и отработки конфигураций удобно развернуть отдельный сервер, например VPS в Serverspace, а материалы по администрированию и безопасности собраны в блоге Serverspace.
FAQ
Достаточно ли просто сменить адрес входа в админку?
Этого мало. Новый адрес убирает основной шум от ботов, но не останавливает тех, кто целенаправленно ищет вашу панель, а адрес часто раскрывается через ссылки, карту сайта или заголовки. Используйте смену пути как дополнение к лимитам, белому списку и второму фактору.
Можно ли обойтись одной двухфакторной аутентификацией?
Для небольшого блога это уже серьёзный шаг, однако полагаться только на неё не стоит. Второй фактор не спасает от уязвимостей в плагинах и не снимает нагрузку от перебора. Лимит запросов и fail2ban остановят сотни ненужных обращений ещё до проверки кода.
Как понять, что админку уже пытаются взломать?
Посмотрите журнал доступа Nginx: частые POST-запросы к /wp-login.php с одних и тех же адресов говорят о переборе. Помогает и плагин журнала действий, который сообщает о неудачных входах. Команда из шага 10 покажет самых активных отправителей за пару секунд.
Что делать, если у меня динамический IP-адрес?
Белый список в таком случае неудобен: адрес меняется, и доступ пропадает. Поднимите VPN с постоянным адресом и разрешите вход только с него. Вариант попроще — вообще не ограничивать панель по адресам, но усилить остальные уровни: лимиты, fail2ban и второй фактор.
Нужен ли WAF, если уже установлен fail2ban?
Это инструменты для разных задач. Fail2ban реагирует на повторяющиеся неудачные попытки, а WAF анализирует содержимое запросов и блокирует эксплуатацию уязвимостей, SQL-инъекции и XSS. Для небольшого сайта хватит плагина с функциями фильтрации, для магазина с оплатой лучше добавить полноценный WAF.
Как часто нужно менять пароли администраторов?
Регулярная смена по календарю приносит меньше пользы, чем длинный уникальный пароль в менеджере паролей. Меняйте его при подозрении на утечку, после ухода сотрудника и после любого инцидента. Вместе со вторым фактором этого достаточно для большинства проектов.