Короткий ответ
Админка https://site.example/wp-admin/ и wp-login.php — самый шумный URL в логах. База: HTTPS, сильные пароли, 2FA на administrator, обновления ядра/плагинов, запрет PHP в uploads, rate limit. Усиление: доступ к /wp-admin/ и wp-login.php только с VPN/офиса 10.0.20.0/24. Смена URL логина («hide login») — слабый слой, ломает сервисы, не заменяет 2FA. Origin /var/www/site.example. Если после allow-list словите 403 с дома — это ожидаемо, откройте VPN, не allow all.
Brute: отдельная статья. Инцидент: плагин.
Смена URL логина («hide login») — слабый слой. Основа: HTTPS, 2FA администраторов, allow-list или VPN, обновления, rate limit на wp-login.php. Проверяйте с IP вне офиса 10.0.20.0/24, иначе не увидите 403. WP-CLI (wp user list, core verify-checksums) оставьте запасным входом, если закроете UI. Не открывайте :81 панелей и phpMyAdmin в интернет «на время настройки». phpMyAdmin — отдельный vhost только с VPN, не на том же server_name site.example.
Симптомы и как отличить
Вы настраиваете защиту, не обязательно после взлома. Признаки «голой» админки: wp-login.php 200 из интернета, xmlrpc pingback, нет плагина 2FA, plugin list с outdated.
403 на админке после ваших правок — смотрите 403, не отключайте правило навсегда.
Возможные причины
- Админка в интернете без 2FA.
- Слабый пароль admin / пользователь
admin. - xmlrpc.php для перебора.
- Установка плагинов из UI на проде.
- Нет обновлений.
- HTTP на login (cookie без Secure).
- «Hide login» + забытый URL в закладках как единственная мера.
Диагностика
curl -sI --resolve site.example:443:10.0.20.40 https://site.example/wp-login.php | head
curl -sI --resolve site.example:443:10.0.20.40 https://site.example/xmlrpc.php | head
sudo -u www-data wp --path=/var/www/site.example core check-update
sudo -u www-data wp --path=/var/www/site.example plugin status
sudo -u www-data wp --path=/var/www/site.example user list --fields=user_login,rolesВ nginx ищите уже существующие location = /wp-login.php.
Решение
1. HTTPS и cookie
Сайт только HTTPS, 301. В wp-config.php:
define('FORCE_SSL_ADMIN', true);2. Rate limit на login (nginx)
limit_req_zone $binary_remote_addr zone=wplogin:10m rate=1r/s;
location = /wp-login.php {
limit_req zone=wplogin burst=5 nodelay;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}Не забудьте include как у остальных PHP. Подробнее пороги — статья brute force.
3. Allow-list IP (если есть VPN/офис)
location = /wp-login.php {
allow 10.0.20.0/24;
deny all;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
location /wp-admin/ {
allow 10.0.20.0/24;
deny all;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
}Исключение для AJAX, если нужно с витрины:
location = /wp-admin/admin-ajax.php {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}nginx -t && reload. Проверьте с IP вне офиса: 403. С офиса: форма логина. Заголовок allow за CDN: нужен real IP, иначе все клиенты — IP CDN и правило бессмысленно или закроет всех.
Apache:
<Location /wp-login.php>
Require ip 10.0.20.0/24
</Location>4. 2FA
Плагин 2FA из wordpress.org с TOTP, политика: administrator обязателен. Сохраните backup-коды вне webroot. Не SMS как единственный фактор, если есть TOTP.
5. Обновления и файлы
sudo -u www-data wp --path=/var/www/site.example plugin update --all
sudo -u www-data wp --path=/var/www/site.example core updateНа проде с git:
define('DISALLOW_FILE_MODS', true);
define('DISALLOW_FILE_EDIT', true);Обновления тогда через пайплайн, не UI.
6. xmlrpc
Если Jetpack/мобильное приложение не нужны:
location = /xmlrpc.php { deny all; }7. PHP в uploads — deny (см. инцидент-статьи)
8. Пользователь admin
Не используйте логин admin. Создайте именного administrator, старого понизьте/удалите после проверки.
Сценарий: REST и application passwords
/wp-json/wp/v2/users иногда светит логины. Закройте enumeration плагином/nginx для неавторизованных, если нет нужды в публичном REST. Application Passwords для интеграций — отдельные, с отзывом при увольнении, не пароль администратора в wp-config константе.
DISALLOW_FILE_MODS ломает обновление из UI — это цель на проде с git. На staging оставьте UI. Документируйте, кто катает обновления WP-CLI:
sudo -u www-data wp --path=/var/www/site.example core update
sudo -u www-data wp --path=/var/www/site.example plugin update --allПосле обновления — core verify-checksums. Скрытый логин (login-slug) не отменяет 2FA: сканеры всё равно бьют wp-login.php и получают 404, нагрузка остаётся. Держите rate limit на этот URI даже если «спрятали».
Проверьте, что allow 10.0.20.0/24 не забыть для IPv6 офиса (allow 2001:db8:... не выдумывайте — возьмите фактический префикс). Иначе коллеги на v6 получат 403 при живом v4.
Как проверить, что проблема устранена
- Из интернета (LTE):
wp-login.php403 или 429 при флуде, не бесконечный 200. - Из
10.0.20.0/24: логин + 2FA проходит. xmlrpc.php403 если решили закрыть.wp core check-update— нет критичных дыр ядра.- Витрина и
admin-ajax(если нужен) работают.
curl -sI --resolve site.example:443:10.0.20.40 https://site.example/wp-login.php | headЕсли не помогло
- WooCommerce и REST
/wp-json/— не админка, отдельная аутентификация. Не режьте REST без понимания приложения. - Мобильные сотрудники без VPN: либо VPN, либо 2FA+rate limit без IP-allow.
- Плагин hide-login + ваши location на старый URL — 404, поправьте location.
Профилактика
- Квартальный
plugin listoutdated. - Отдельная учётка для контента без administrator.
- Заголовки.
- Бэкап до обновлений.
- Раз в квартал:
wp user list --role=administrator, 2FA включена, xmlrpc закрыт или обоснован,verify-checksumsядра. IP-allow пересматривайте, когда меняется VPN. Проверка защиты — с LTE, не только с10.0.20.0/24.
FAQ
Достаточно ли капчи на login?
Лучше rate limit + 2FA. Капча мешает и обходится ботами выборочно.
wp-admin basic auth поверх WP
Допустимый слой (auth_basic), пароль не тот же, что WP. Усложняет REST. Документируйте.
Нужен ли WAF?
Полезен как слой, не вместо обновлений.
App passwords для REST
Отзывайте при увольнении, не путайте с паролем входа.
Можно ли открыть админку только на отдельном hostname adm.site.example?
Да, отдельный vhost + DNS только внутри VPN. Основной сайт без wp-login location. Следите, чтобы cookie домена не разъехались (COOKIE_DOMAIN).