.htaccess для WordPress

WordPress активно использует .htaccess: стандартный блок # BEGIN WordPress обеспечивает «красивые URL» (ЧПУ), а дополнительные правила закрывают wp-config.php, ограничивают wp-login.php и xmlrpc.php, запрещают выполнение PHP в папке загрузок, ставят кэш и заголовки безопасности. Ниже — полный разбор: готовые блоки, объяснение «почему» и типичные грабли. Если что-то уже пошло не так — гид «Почему .htaccess не работает»; собрать блок из чекбоксов — генератор; проверить файл — линтер.

Все WP-рецепты здесь — WP-специфика. Общие темы (IP-фильтрация, Basic-auth, заголовки безопасности, блокировка ботов) подробно разобраны в гидах «Безопасность сайта через .htaccess» и «Ускорение сайта через .htaccess»; ошибки и коды — в гиде «Почему .htaccess не работает»; синтаксис правил — в линтере.

1. Стандартный .htaccess WordPress (ЧПУ)

WordPress автоматически записывает в .htaccess блок между маркерами # BEGIN WordPress и # END WordPress при каждом сохранении настроек постоянных ссылок (Permalinks). Этот блок — фронт-контроллер: все запросы к несуществующим файлам и каталогам перенаправляются в index.php, который уже сам строит ответ по маршруту.

Важно: не редактируйте содержимое внутри # BEGIN WordPress … # END WordPress вручную — WP перезапишет при следующем сохранении настроек. Свои правила добавляйте выше или ниже этого блока.

.htaccessкопировать
# BEGIN WordPress
# Правила ниже генерируются WordPress автоматически.
# Не редактируйте между маркерами BEGIN и END вручную.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
  • Если сайт в подкаталоге (например, /blog/), значение RewriteBase должно совпадать с ним: RewriteBase /blog/.
  • Блок требует включённого mod_rewrite. Если ЧПУ дают 404 — проверьте, загружен ли модуль; подробно — /errors/ → раздел 4.
  • Свои правила (Redirect, RewriteRule, Header) ставьте выше блока # BEGIN WordPress.

2. Принудительный HTTPS для WordPress

Редирект с http:// на https:// ставится в .htaccess выше блока # BEGIN WordPress. После добавления обязательно обновите адрес сайта в настройках WordPress (Настройки → Общие → Адрес WordPress / Адрес сайта) и добавьте define('FORCE_SSL_ADMIN', true); в wp-config.php для защиты входа в админку.

.htaccessкопировать
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>

Если сайт работает за CDN или обратным прокси (Cloudflare, nginx-реверс), Apache получает трафик уже расшифрованным и %{HTTPS} всегда off — редирект уйдёт в петлю. Используйте заголовок X-Forwarded-Proto:

.htaccessкопировать
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>
  • «Бесконечный редирект» после включения HTTPS — типичная проблема за CDN; подробно — /errors/ → раздел 5.
  • Не забудьте обновить адрес сайта в настройках WP и в wp-config.php — иначе WP сам будет редиректить обратно на http://.

3. Склейка зеркал (www ↔ без www)

WordPress лучше всего управляет каноническим адресом сайта через Настройки → Общие (Адрес WordPress и Адрес сайта). Если вам всё же нужно сделать склейку на уровне .htaccess — ставьте правила выше блока # BEGIN WordPress.

.htaccessкопировать
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
</IfModule>
.htaccessкопировать
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>
  • Не задавайте склейку одновременно в .htaccess и в настройках CMS / панели — получите бесконечный редирект.
  • После смены канонического адреса обновите настройки WordPress и карту сайта.

4. Защита служебных файлов WP

По умолчанию Apache отдаёт любой файл в корне сайта — включая wp-config.php с паролем базы данных, файлы .git, бэкапы и логи. Закройте их явно в .htaccess.

.htaccessкопировать
<Files wp-config.php>
    Require all denied
</Files>
.htaccessкопировать
# .htaccess и .htpasswd — всегда
<FilesMatch "^\.ht">
    Require all denied
</FilesMatch>

# Лишние файлы WP в корне
<FilesMatch "^(wp-config-sample\.php|readme\.html|license\.txt)$">
    Require all denied
</FilesMatch>

# Репозитории — на всякий случай
RedirectMatch 404 /\.(git|svn|hg)(/|$)
  • Блоки выше используют синтаксис Apache 2.4 (Require all denied). На Apache 2.2 нужен старый синтаксис (Order allow,deny + Deny from all) — конвертировать поможет конвертер 2.2↔2.4.
  • Проверить, что файл написан без ошибок и правила реально закрывают нужное — линтер .htaccess.
  • Подробнее про защиту файлов — /security/ → раздел 2.

5. Отключить / ограничить xmlrpc.php

xmlrpc.php — старый XML-RPC интерфейс WordPress. Он используется мобильным приложением WP, Jetpack и некоторыми плагинами, но также является вектором атак: pingback-DDoS и перебор паролей методом «батарейного» перебора (один запрос = сотни попыток логина). Если Jetpack и мобильное приложение не нужны — закройте полностью.

.htaccessкопировать
<Files xmlrpc.php>
    Require all denied
</Files>

Если Jetpack или мобильное приложение используются — разрешите только с нужных IP:

.htaccessкопировать
<Files xmlrpc.php>
    Require ip 203.0.113.0/24
</Files>
  • IP серверов Jetpack — динамические; смотрите актуальный список в документации Automattic.
  • Pingback можно отключить в настройках WordPress (Настройки → Обсуждение → «Разрешить уведомления с других сайтов») без блокировки всего xmlrpc.

6. Защита wp-login.php от перебора

Страница входа WordPress (wp-login.php) — постоянная цель брутфорс-атак. Первая линия обороны — ограничить к ней доступ по IP.

.htaccessкопировать
<Files wp-login.php>
    Require ip 203.0.113.0/24
    Require ip 192.0.2.15
</Files>

Вариант 2 — HTTP Basic Auth поверх формы входа (дополнительный пароль перед тем, как WordPress вообще покажет страницу):

.htaccessкопировать
<Files wp-login.php>
    AuthType Basic
    AuthName "WordPress Login"
    AuthUserFile /home/USER/.htpasswd
    Require valid-user
</Files>

7. Ограничить /wp-admin/ по IP

Закрыть весь каталог /wp-admin/ по IP — эффективная защита, но с обязательным исключением для admin-ajax.php: этот файл обрабатывает AJAX-запросы фронтенда (корзина WooCommerce, поиск, плагины) и должен быть доступен всем.

Создайте файл wp-admin/.htaccess (отдельный файл в каталоге wp-admin, а не в корне сайта):

.htaccessкопировать
# Исключение для admin-ajax.php — нужен фронтенду
<Files admin-ajax.php>
    Require all granted
</Files>

# Всё остальное в wp-admin — только с доверенных IP
<RequireAll>
    Require all granted
    Require ip 203.0.113.0/24
    Require ip 192.0.2.15
</RequireAll>

8. Запретить выполнение PHP в wp-content/uploads

Один из главных векторов взлома WordPress: через форму загрузки заливают shell.php в wp-content/uploads/ и запускают его напрямую. Контрмера — запретить выполнение и отдачу PHP-файлов из папки загрузок. Создайте файл wp-content/uploads/.htaccess:

.htaccessкопировать
<FilesMatch "\.ph(p[0-9]?|tml|ar)$">
    Require all denied
</FilesMatch>

# Дополнительно — на старых конфигурациях:
# RemoveHandler .php .phtml .php3 .php4 .php5
# RemoveType .php .phtml
  • Важная грабля: если на сервере разрешён AllowOverride FileInfo для каталога uploads, злоумышленник может залить туда собственный .htaccess, отменяющий запрет. Надёжная контрмера — AllowOverride None в конфиге сервера для папки uploads (это делает хостер, не вы).
  • Проверить, что правило написано правильно — линтер .htaccess.
  • Подробнее про запрет выполнения PHP — /security/ → раздел 6.

9. Кэш статики и gzip для WordPress

Браузерный кэш и сжатие ускоряют загрузку сайта. Блоки ставятся в корневой .htaccess — можно выше или ниже блока # BEGIN WordPress, но вне него.

.htaccessкопировать
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"
    ExpiresByType text/css "access plus 1 month"
    ExpiresByType application/javascript "access plus 1 month"
    ExpiresByType application/x-javascript "access plus 1 month"
    ExpiresByType font/woff2 "access plus 1 year"
    ExpiresByType font/woff "access plus 1 year"
</IfModule>
.htaccessкопировать
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/plain text/css
    AddOutputFilterByType DEFLATE application/javascript application/x-javascript
    AddOutputFilterByType DEFLATE application/json application/xml
    AddOutputFilterByType DEFLATE image/svg+xml font/woff font/woff2
</IfModule>
  • Важно: плагины кэширования WordPress (WP Super Cache, W3 Total Cache, LiteSpeed Cache) часто сами записывают в .htaccess свои блоки кэша и gzip. Не дублируйте их вручную — конфликтующие директивы могут дать непредсказуемый результат. При удалении плагина — почистите его блоки из .htaccess.
  • Собрать блоки кэша и gzip из чекбоксов — генератор (блоки «Кэш статики» и «GZIP»); подробный разбор — /performance/.

10. Заголовки безопасности для WordPress

Заголовки безопасности защищают посетителей от кликджекинга и части XSS. Ставятся через mod_headers в корневом .htaccess.

.htaccessкопировать
<IfModule mod_headers.c>
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-Content-Type-Options "nosniff"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    # HSTS — только если сайт полностью на HTTPS:
    # Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>
  • CSP в WordPress — осторожно: WordPress использует множество inline-скриптов и стилей в админке и на фронтенде. Строгий Content-Security-Policy почти наверняка сломает часть функций (редактор Gutenberg, плагины). Начинайте с Content-Security-Policy-Report-Only, смотрите отчёты в DevTools, и только потом переходите на боевой заголовок.
  • Подробный разбор каждого заголовка — справочник HTTP-заголовков → безопасность; собрать — генератор.

11. Скрыть версию WordPress и PHP

Версия PHP утекает через заголовок X-Powered-By — уберите его через mod_headers. Версия WordPress утекает через мета-тег generator и параметр ?ver= в URL статики — это убирается в functions.php, не в .htaccess.

.htaccessкопировать
<IfModule mod_headers.c>
    Header always unset X-Powered-By
</IfModule>
  • ServerSignature Off и ServerTokens Prod (убрать версию Apache из страниц ошибок) — не работают в .htaccess, только в конфиге сервера или виртхоста; просите хостера.
  • Убрать ?ver= из URL статики WordPress — через фильтр script_loader_src / style_loader_src в functions.php: это задача темы / плагина, а не .htaccess.

12. Частые проблемы WordPress + .htaccess

Разбор наиболее частых ситуаций, когда WordPress и .htaccess конфликтуют.

  • ЧПУ не работают после переноса / свежей установки. Причины: блок # BEGIN WordPress отсутствует или неполный; mod_rewrite не включён; неверный RewriteBase (сайт в подкаталоге). Решение: в админке зайдите в Настройки → Постоянные ссылки и нажмите «Сохранить» — WP перезапишет блок. Если не помогло — /errors/ → раздел 4.
  • 500 Internal Server Error после установки плагина кэширования. Плагин добавил php_value / php_flag в .htaccess, а ваш хостинг работает на PHP-FPM — эти директивы дают 500. Удалите строки php_value / php_flag или отключите плагин; настройки PHP на FPM задаются через .user.ini. Подробно — /errors/ → раздел 2.
  • Бесконечный редирект (ERR_TOO_MANY_REDIRECTS). Типично за CDN: HTTPS-правило в .htaccess проверяет %{HTTPS} off, но за прокси это всегда off. Замените на %{HTTP:X-Forwarded-Proto} !https (см. раздел 2). Или конфликт: WP-настройки тянут на https://, а редирект в .htaccess — обратно. Подробно — /errors/ → раздел 5.
  • «WP перезаписал мой .htaccess». WordPress перегенерирует блок # BEGIN WordPress … # END WordPress при каждом сохранении настроек постоянных ссылок. Свои правила кладите выше или ниже этого блока — WP их не тронет.
  • Мультисайт (Multisite). WordPress Multisite генерирует другие блоки для подкаталогов (subdirectory) и поддоменов (subdomain) — не редактируйте их вручную, используйте панель Network Admin → Настройки → Постоянные ссылки.
  • Разобрать чужой WP .htaccess построчно — инструмент «Объяснить .htaccess».

13. Чеклист: WP .htaccess за 5 минут

  1. Блок # BEGIN WordPress на месте, ЧПУ работают? — раздел 1, /errors/ → ЧПУ
  2. wp-config.php и .git закрыты (Require all denied)? — раздел 4, /security/
  3. xmlrpc.php отключён или ограничен по IP? — раздел 5
  4. wp-login.php под IP-фильтром или HTTP Basic? — раздел 6, /htpasswd/
  5. PHP в wp-content/uploads/ запрещён? — раздел 8
  6. Кэш статики и gzip настроены (и не конфликтуют с плагином кэша)? — раздел 9, /performance/
  7. Заголовки безопасности стоят (без агрессивного CSP, ломающего админку)? — раздел 10, /headers/
  8. Синтаксис .htaccess проверен линтером? — /check/, /audit/