Ви змінили .htaccess, щоб «лише додати редирект» або «затягнути безпеку», оновили сторінку — і тепер продакшен повертає 500, 403 або цикл редиректів, який гріє CPU як електрообігрівач. Дзвонять телефони. Маркетинг каже, що це «лише блог». Насправді ж під ударом — ваш чек-аут, документація, SEO і ваші вихідні.
Ось розсудливий вихід: відновити знану робочу базову конфігурацію, довести, що саме зламалося, і поставити запобіжні механізми, щоб наступне редагування не стало інцидентом. Зробимо це як SRE, який уже пояснював керівництву, чому «невелика правка в конфігу» вивела з ладу шлях доходу.
Що насправді робить .htaccess (і чому це ламається так драматично)
.htaccess — це конфігурація на рівні каталогу для Apache HTTP Server. Якщо ваш конфіг дозволяє це (AllowOverride), Apache читає .htaccess під час кожного запиту до цього каталогу та підкаталогів. У цьому й суть: ви можете вносити зміни без правки глобального конфига або перезапуску Apache. Але це й пастка: можна миттєво зламати маршрутизацію, права доступу та безпеку, і Apache буде вірно застосовувати зламану логіку на кожному запиті.
WordPress покладається на mod_rewrite для роботи «гарних пермалінків». Блок переписування за замовчуванням у WordPress маленький і нудний. Саме цього ви хочете в продакшені. Коли плагін, гайд із «загартування безпеки» або доброзичливий колега починають складати директиви — редиректи, заборони, прапорці PHP, підказки кешування — можуть виникнути взаємодії, що неочевидні до моменту виходу в продакшен: цикли переписування, посилення запитів, блоковані ресурси, заблокований адмін і раптові 500.
Ще одне: .htaccess оцінюється в контексті каталогу, а не всього сайту. Отже правило, яке «виглядає вірно» в корені, може поводитися інакше в /wp-admin/ або /wp-content/. Правила підmatch-у шляху тонкі. Інциденти в продакшені не бувають тонкими.
Короткий жарт №1: Редагувати .htaccess вживу — це як робити операцію ножем для масла: технічно можливо, але після доведеться багато пояснювати.
Цікаві факти й контекст (щоб поведінка була зрозуміла)
- .htaccess існує через шаринґ-хостинг. У ранні часи дешевого хостингу користувачам потрібен був обмежений контроль без root‑доступу. Переналаштування на рівні каталогу — це компроміс.
- Apache читає .htaccess під час запиту. Ця зручність коштує продуктивності; сервер має постійно перевіряти наявність файлу (та батьківських каталогів), якщо інше не налаштовано.
- Пермалінки WordPress стали очікуванням за замовчуванням. Коли блоги еволюціонували в CMS, людиночитні URL перестали бути опцією і стали базовою вимогою.
- Порядок правил mod_rewrite — звична пастка. Перше підходяще правило може переривати решту, і невелика зміна прапорця (
L,R,QSA) може повністю змінити поведінку. - 403 vs 404 часто питання політики, а не наявності. 403 від Apache може бути спричинений правами файлової системи, правилами доступу або модулями безпеки — не обов’язково відсутністю контенту.
- Багато «сніпетів безпеки» застаріли. Постійно копійовані блог‑пости все ще радять директиви, що конфліктують з сучасною поведінкою WordPress або з PHP‑FPM‑налаштуваннями.
- Nginx не використовує .htaccess. Коли команди мігрують з Apache на Nginx, вони часто продовжують редагувати
.htaccessі дивуються, чому нічого не змінюється. - AllowOverride — це рішення контрольної площини. Вмикати його скрізь — операційно зручно, але небезпечно з точки зору безпеки; вимикати скрізь — безпечно, але незручно. Більшість реальних систем обирає компроміс.
Швидкий план діагностики
Це порядок, що швидко знаходить вузьке місце, а не порядок, який здається ввічливим.
1) Визначте клас помилки: 500, 403, 404, цикл редиректів або порожня сторінка
- 500: Apache не зміг обробити запит. Мисліть про невірну директиву, відсутній модуль, проблему з правами в модулі безпеки або помилки обробника PHP, краще видимі через переписування.
- 403: Доступ заборонено. Думайте про права файлової системи, правила
Require/Denyабо WAF/модулі безпеки. - 404: Маршрутизація. Думайте про те, що переписування не спрацьовує, неправильний
RewriteBase, неправильний DocumentRoot або WordPress не отримує запит. - 301/302 цикл: Конфлікт логіки переписування або редиректів. Часто це канонізація схеми/хоста + плагін + заголовки проксі.
- Порожня сторінка: Може бути фатальна помилка PHP при вимкненому виведенні помилок; також може спричинятись переписуванням до PHP.
2) Дивіться спочатку лог веб‑сервера, а не WordPress
Якщо .htaccess зламаний, Apache часто каже вам, яка директива і чому. Якщо ви починаєте з WordPress, ви дебагуєте не той шар.
3) Підтвердіть, що Apache взагалі враховує .htaccess
Якщо для цього шляху встановлено AllowOverride None, ваші зміни не застосуються, і ви можете ганятися за привидами. Навпаки, якщо дозволені override, одна погана рядок може одразу вивести віртуальний хост з ладу.
4) Відкотіть до знано‑доброї бази, потім поступово вводьте зміни
У продакшені ви хочете швидко відновити сервіс. Корінну причину можна з’ясувати пізніше. Робіть це чисто: зробіть бекап поточного файлу, відновіть дефолт, перевірте логи, а потім додавайте зміни по одній.
Правильне відновлення безпечного WordPress .htaccess
Є два «безпечні дефолти», про які варто говорити:
- Блок переписування за замовчуванням від WordPress (мінімум, необхідний для роботи пермалінків на Apache з
mod_rewrite). - Операційно безпечна база: дефолтне переписування плюс невеликий набір не контроверсійних правил безпеки, які не зламають адмінку, REST API або завантаження.
База #1: Дефолтний блок переписування WordPress
Це те, що WordPress записує, коли ви натискаєте «Save Permalinks» в адмінці. Це навмисно мінімально.
cr0x@server:~$ cat /var/www/example.com/public_html/.htaccess
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
Так, рядок HTTP_AUTHORIZATION важлива для деяких налаштувань і плагінів. Якщо ви її видалите, можете зламати аутентифіковані запити через певні проксі або обробники PHP. Залишайте її, якщо немає причин прибирати.
База #2: Дефолтне переписування плюс безпечне операційне загартування
Загартування вимагає стриманості. Якщо ви не можете точно пояснити, які запити блокує правило і чому — воно не має бути в продакшені. Консервативний набір включає: заборону індексації каталогів, блокування доступу до чутливих файлів і додавання кількох заголовків, які не зламають WordPress.
Збережіть поведінку переписування ідентичною, і додавайте лише низькоризикові правила. Уникайте складних regex‑блоків для query string, поки не протестували їх з вашими плагінами та адмінськими потоками.
cr0x@server:~$ cat /var/www/example.com/public_html/.htaccess
# Basic hygiene
Options -Indexes
<FilesMatch "^(\.env|composer\.(json|lock)|wp-config\.php|readme\.html|license\.txt)$">
Require all denied
</FilesMatch>
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
# Safe headers (avoid breaking admin)
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>
Зверніть увагу на відсутність: агресивних директив кешування, брутального блокування xmlrpc.php, складних правил проти хотлінкінгу та «deny all у wp-admin, крім мого IP». Це не дефолти — це проєктні рішення.
Практичні завдання: команди, значення виводу та рішення
Вам потрібні повторювані завдання, що працюють під тиском. Нижче ті, які я фактично виконую. Кожне містить (a) команду, (b) що означає вивід, і (c) рішення, яке ви приймаєте.
Завдання 1: Підтвердити веб‑сервер і його активний конфіг
cr0x@server:~$ ps -eo pid,comm,args | egrep 'apache2|httpd|nginx' | head
1123 apache2 /usr/sbin/apache2 -k start
1450 apache2 /usr/sbin/apache2 -k start
Значення: Apache запущений (apache2). Якщо ви бачите лише nginx, припиніть редагувати .htaccess; він не бере участі в обробці запитів.
Рішення: Якщо Apache не є шаром обслуговування, знайдіть справжній вхід (load balancer, reverse proxy, Nginx) і виправляйте маршрутизацію там.
Завдання 2: Зняти точний HTTP‑симптом із сервера
cr0x@server:~$ curl -sS -D- -o /dev/null http://127.0.0.1/ | sed -n '1,12p'
HTTP/1.1 500 Internal Server Error
Date: Sat, 27 Dec 2025 12:10:13 GMT
Server: Apache/2.4.57 (Ubuntu)
Content-Type: text/html; charset=iso-8859-1
Значення: Це реальна 500 на веб‑сервері, а не проблема CDN‑edge.
Рішення: Йдіть прямо до логів Apache і валідації конфига, перед тим як лізти в WordPress.
Завдання 3: Хвости логів помилок Apache під час запиту
cr0x@server:~$ sudo tail -n 50 /var/log/apache2/error.log
[Sat Dec 27 12:10:13.492112 2025] [core:alert] [pid 1123] [client 127.0.0.1:39912] /var/www/example.com/public_html/.htaccess: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration
Значення: mod_rewrite не завантажений або Apache не розпізнає директиви переписування в цьому контексті.
Рішення: Увімкніть mod_rewrite (на Debian/Ubuntu) і перезавантажте Apache, або виправте образ контейнера/набір пакетів.
Завдання 4: Перевірити наявність модулів Apache (mod_rewrite, mod_headers)
cr0x@server:~$ apache2ctl -M | egrep 'rewrite|headers'
headers_module (shared)
Значення: headers присутній; rewrite відсутній. Це відповідає помилці в логах.
Рішення: Увімкніть rewrite, потім повторно протестуйте. Без нього пермалінки WordPress не працюватимуть як очікується.
Завдання 5: Увімкнути mod_rewrite і перезавантажити Apache (Debian/Ubuntu)
cr0x@server:~$ sudo a2enmod rewrite
Enabling module rewrite.
To activate the new configuration, you need to run:
systemctl restart apache2
Значення: Модуль тепер увімкнено в конфігурації Apache.
Рішення: Перезапустіть/перезавантажте Apache контрольовано. У HA‑середовищі спочатку злийте один вузол.
cr0x@server:~$ sudo systemctl reload apache2
Значення: Reload успішний (відсутність виводу — нормально). Якщо не вдається, systemctl status apache2 покаже причину.
Рішення: Негайно повторіть curl і слідкуйте за логами, щоб підтвердити зміну класу помилки.
Завдання 6: Перевірити, чи дозволено Apache враховувати .htaccess (AllowOverride)
cr0x@server:~$ sudo apache2ctl -S 2>&1 | sed -n '1,20p'
VirtualHost configuration:
*:80 example.com (/etc/apache2/sites-enabled/example.com.conf:1)
ServerRoot: "/etc/apache2"
Main DocumentRoot: "/var/www/html"
Значення: Маєте шлях до файлу vhost. Далі перегляньте директорію зі stanza для AllowOverride.
Рішення: Якщо встановлено AllowOverride None, то правила .htaccess ігноруються; виправляйте на рівні vhost або змініть AllowOverride лише для цього каталогу.
cr0x@server:~$ sudo sed -n '1,200p' /etc/apache2/sites-enabled/example.com.conf
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/example.com/public_html
<Directory /var/www/example.com/public_html>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Значення: Overrides дозволені. Тож .htaccess діє, на краще чи гірше.
Рішення: Якщо сайт зламався після редагування, відкат .htaccess матиме миттєвий ефект.
Завдання 7: Зробіть резервну копію на хості з часовими мітками перед будь‑якими змінами
cr0x@server:~$ cd /var/www/example.com/public_html
cr0x@server:~$ sudo cp -a .htaccess .htaccess.bak.$(date +%F-%H%M%S)
cr0x@server:~$ sudo ls -la .htaccess*
-rw-r--r-- 1 www-data www-data 821 Dec 27 12:11 .htaccess
-rw-r--r-- 1 www-data www-data 821 Dec 27 12:11 .htaccess.bak.2025-12-27-121102
Значення: Тепер у вас є запасний варіант. Якщо ви помилилися (таке буває), можна швидко відкотитись.
Рішення: Ніколи не редагуйте без бекапу. У інциденті «я ж пам’ятаю, як було» — це брехня собі.
Завдання 8: Перевірити власника та права файлової системи (403 часто починаються звідси)
cr0x@server:~$ namei -l /var/www/example.com/public_html/.htaccess
f: /var/www/example.com/public_html/.htaccess
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwxr-x--- www-data www-data example.com
drwxr-x--- www-data www-data public_html
-rw-r----- www-data www-data .htaccess
Значення: Apache (часто www-data) може прочитати файл, якщо він працює під www-data. Якщо будь‑який каталог позбавлений бітів виконання для користувача Apache, сервер не зможе зайти в нього і ви отримаєте 403/500 дивності.
Рішення: Якщо права занадто жорсткі, спочатку виправте біти виконання каталогів, перш ніж звинувачувати правила переписування.
Завдання 9: Знайти класичний цикл редиректів через заголовки
cr0x@server:~$ curl -sS -I http://example.com/ | sed -n '1,12p'
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
Значення: HTTP редиректить на HTTPS. Нормально.
Рішення: Тепер протестуйте HTTPS і перевірте, чи немає зворотного редиректу назад на HTTP або на інший хост.
cr0x@server:~$ curl -sS -I https://example.com/ | egrep 'HTTP/|Location'
HTTP/2 301
location: http://example.com/
Значення: Це цикл: HTTPS редиректить назад на HTTP. Зазвичай викликано змішаними заголовками проксі та умовами переписування, що неправильно визначають схему.
Рішення: Виправте канонізацію в одному місці (LB або Apache) і переконайтеся, що X-Forwarded-Proto обробляється коректно. Не складайте конкуруючі редиректи в .htaccess і плагінах.
Завдання 10: Тимчасово вимкнути .htaccess без видалення (безпечний тест)
cr0x@server:~$ cd /var/www/example.com/public_html
cr0x@server:~$ sudo mv .htaccess .htaccess.disabled
cr0x@server:~$ curl -sS -D- -o /dev/null http://127.0.0.1/ | sed -n '1,8p'
HTTP/1.1 200 OK
Date: Sat, 27 Dec 2025 12:12:44 GMT
Server: Apache/2.4.57 (Ubuntu)
Значення: Видалення .htaccess з пути запиту відновило сервіс. Це підтвердження причинності, але не рішення.
Рішення: Поверніть мінімальний відомо‑добрий .htaccess (блок WordPress за замовчуванням), потім знову ввімкніть файл.
Завдання 11: Безпечно відновити дефолтний блок переписування WordPress
cr0x@server:~$ sudo tee /var/www/example.com/public_html/.htaccess >/dev/null <<'EOF'
# BEGIN WordPress
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# END WordPress
EOF
cr0x@server:~$ curl -sS -D- -o /dev/null http://127.0.0.1/ | sed -n '1,8p'
HTTP/1.1 200 OK
Date: Sat, 27 Dec 2025 12:13:21 GMT
Server: Apache/2.4.57 (Ubuntu)
Значення: Кореневий шлях знову відповідає. Тепер перевірте пермалінки та адмінку.
Рішення: Якщо це виправляє проблему, ви знаєте, що попередні зміни в .htaccess були тригером. Поступово знову додавайте потрібні правила обачно.
Завдання 12: Перевірити маршрутизацію пермалінків (неіснуючий файл має потрапляти на index.php)
cr0x@server:~$ curl -sS -D- -o /dev/null http://127.0.0.1/this-should-not-be-a-file | sed -n '1,10p'
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Значення: Запит маршрутизується через WordPress, а не повертає 404 від Apache. WordPress може все одно видати сторінку 404, але це вже рівень програми.
Рішення: Якщо ви отримуєте Apache 404, переписування не працює. Перевірте mod_rewrite, AllowOverride і RewriteBase.
Завдання 13: Підтвердити, що WordPress бачить правильні site URL і home URL (цикли редиректів часто тут)
cr0x@server:~$ sudo -u www-data wp option get home --path=/var/www/example.com/public_html
https://example.com
cr0x@server:~$ sudo -u www-data wp option get siteurl --path=/var/www/example.com/public_html
https://example.com
Значення: WordPress погоджується щодо канонічної схеми та хоста.
Рішення: Якщо вони відрізняються (http vs https, www vs apex), виправте це перед додаванням редиректів у .htaccess. Нехай один шар відповідає за канонізацію.
Завдання 14: Перевірити здоров’я обробника PHP (500 можуть несправедливо звинуватити .htaccess)
cr0x@server:~$ curl -sS -D- -o /dev/null http://127.0.0.1/wp-login.php | sed -n '1,12p'
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Значення: Виконання PHP працює принаймні для цієї точки входу.
Рішення: Якщо це 500, а статичні ресурси 200 — проблема, ймовірно, у PHP‑FPM, правах або помилках застосунку, а не в переписуванні.
Завдання 15: Перевірити цілісність конфігу Apache після змін (відловити «не запуститься» після reload)
cr0x@server:~$ sudo apache2ctl configtest
Syntax OK
Значення: Глобальна конфігурація Apache розбирається. Це не перевіряє повністю кожний runtime‑випадок .htaccess, але виявляє багато катастроф.
Рішення: Якщо не OK — виправляйте перед reload. Зламаний перезапуск на однонодовій системі — самоіндукований аутейдж.
Завдання 16: Знайти точно, яка директива .htaccess ламає запити
cr0x@server:~$ sudo grep -RIn --color=never "Invalid command\|RewriteCond\|RewriteRule\|Require\|Deny" /var/log/apache2/error.log | tail -n 5
/var/log/apache2/error.log:[Sat Dec 27 12:10:13.492112 2025] [core:alert] [pid 1123] [client 127.0.0.1:39912] /var/www/example.com/public_html/.htaccess: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration
Значення: Лог вказує на файл і проблемну директиву.
Рішення: Виправте невідповідність модуль/конфіг, а не вилучайте рядки навмання.
Три корпоративні історії з практики
Міні‑історія 1: Аутейдж через невірне припущення
Компанія була в процесі ребрендингу: новий домен, нові TLS‑серти, нові кампанійні сторінки. Розробник додав, здавалося б, нешкідливий редирект у .htaccess, щоб примусово скеровувати все на новий хостнейм. Він тестував на ноуті. Там працювало.
У продакшені трафік ішов через load balancer, що термінував TLS і надсилав Apache plain HTTP. Логіка редиректу базувалася на %{HTTPS} і припускала, що якщо Apache бачить HTTP, клієнт насправді користується HTTP. Це припущення виявилося хибним. Клієнт уже був на HTTPS; просто Apache цього не знав.
Правило змушувало HTTPS, load balancer надсилав HTTP в Apache, Apache знову змушував HTTPS — і клієнт скакав між кінцями, поки браузер не здався. Моніторинг показував «здорові» інстанси, бо Apache швидко відповідав редиректами. Ззовні сайт був недоступний.
Виправлення було нудним: налаштувати Apache, щоб довіряти X-Forwarded-Proto (або робити канонізацію виключно на балансировщику), потім спростити .htaccess до лише WordPress‑переписування. Редирект перемістили на edge, де схема відома. Також додали синтетичну перевірку, яка фейлить при надмірній кількості редиректів, бо «200 — не те саме, що корисно».
Міні‑історія 2: Оптимізація, що дала протилежний ефект
Інженер, орієнтований на продуктивність, хотів знизити навантаження на PHP. Додав агресивні заголовки кешування в .htaccess для всього під /wp-content/, плюс набір переписувань для видачі попередньо стиснутих ресурсів. На папері: менше запитів, швидші сторінки, щасливі користувачі.
Потім почалися мовчазні збої. Плагін оновився та змінив імена файлів, але повторно використав шляхи. Заголовки кешування встановлено на довгу життя як immutable. Браузери тримали застарілий JavaScript. Деякі користувачі не могли надіслати форми, бо клієнтська валідація була стара. Пішли запити в підтримку: «кнопка checkout не працює». Інцидент був гірший за чистий аутейдж — часткові помилки, що обходили моніторинг.
Коли намагалися «швидко виправити», очистили CDN кеші, але кеши браузерів залишилися отруєними. Довелося відкотити заголовки, підвищити версії ресурсів і надсилати інструкції по очищенню кешу цільовим користувачам. Справжня вартість була не в CPU, а в довірі користувачів і часі.
Урок: кешування — це зміна продукту. Якщо ви не контролюєте версіювання ресурсів наскрізно, не ставте героїчні TTL на оригіні через .htaccess. Використовуйте CDN з продуманою політикою інвалідації або тримайте TTL короткими.
Міні‑історія 3: Сумна, але правильна практика, що врятувала день
В іншій компанії WordPress‑сайт стояв за внутрішнім процесом зміни конфігурації. Нічого особливого. Просто послідовно: кожна зміна конфига, включно з .htaccess, проходила через невелике репо і job деплою, який архівував попередні версії на сервері.
У п’ятницю підрядник застосував «пакет загартування безпеки», який включав блокування доступу до певних PHP‑файлів та додавання обмежувальних правил під /wp-admin/. Це одразу ж заблокувало адмінів — у той самий день були заплановані оновлення контенту. Передбачувано, всі звинуватили WordPress.
На‑черговому інженер не сперечався. Вони витягли останній відомо‑добрий артефакт .htaccess з логів деплою, відновили його і підтвердили відновлення за кілька хвилин. Жодної археології. Ніяких здогадок. Ніякого героїчного SSH‑лізання по кількох інстансах.
Потім спокійно побудували план тестування: перевірити логін в адмінку, REST API виклики, що використовує редактор, завантаження і cron. Зміни підрядника були знову введені через staging, відкориговані і задеплоєні з планом відкату. Ні в чому героїчного — працює.
Типові помилки: симптом → першопричина → виправлення
Цей розділ існує тому, що більшість .htaccess зламів римуються. Якщо ви впізнаєте симптом, можна уникнути багатьох драм.
500 Internal Server Error відразу після редагування .htaccess
- Симптом: Кожний запит повертає 500; лог Apache вказує на
.htaccess. - Першопричина: Невірна директива (опечатка), модуль не увімкнений (часто
mod_rewrite), або директива заборонена в контексті.htaccess. - Виправлення: Перевірте лог помилок для точної директиви, увімкніть модуль (
a2enmod rewrite) або перемістіть директиву у vhost‑конфіг, якщо її не можна використовувати в.htaccess.
403 Forbidden на всьому, включно з головною
- Симптом: 403 для
/і ресурсів; інколи після додавання «deny» правил. - Першопричина: Надто широке
Require all denied, застарілийDeny from all, застосований невірно, або порушені права обходження каталогів. - Виправлення: Приберіть/звужте блоки заборони; перевірте біти виконання каталогів через
namei -l; переконайтеся, що vhost міститьRequire all grantedдля DocumentRoot.
Пермалінки повертають Apache 404, але /index.php працює
- Симптом:
/about/падає з 404 на веб‑сервері;/index.php?p=123працює. - Першопричина: Переписування не виконується:
mod_rewriteвимкнено,AllowOverrideне дозволяє rewrite, або неправильнийRewriteBase, якщо WordPress у підкаталозі. - Виправлення: Увімкніть rewrite; встановіть
AllowOverride Allабо як мінімумAllowOverride FileInfo; вкажітьRewriteBase /subdir/, коли WordPress не в корені vhost.
Нескінченний цикл редиректів (браузер: «занадто багато редиректів»)
- Симптом: Цикл між http/https або між www і non‑www.
- Першопричина: Канонізація налаштована в кількох шарах (плагін +
.htaccess+ load balancer), або помилкова детекція схеми за TLS‑термінацією. - Виправлення: Оберіть один шар для канонізації. Переконайтеся, що
home/siteurlу WordPress співпадають. Якщо за проксі, налаштуйте довірені forwarded‑заголовки і базуйте редиректи на них.
Адмінка працює, але завантаження та зображення 403
- Симптом: Сайт завантажується, але медіатека показує зламані зображення; прямий доступ до
/wp-content/uploads/...повертає 403. - Першопричина: Правило заборони, призначене для PHP‑файлів або dotfiles, занадто широко підходить або права на uploads неправильні після міграції.
- Виправлення: Звузьте
FilesMatchдо конкретних чутливих файлів; перевірте власність і права наwp-content/uploads.
Сайт «вгору», але повільний після додавання правил безпеки/кешування в .htaccess
- Симптом: Збільшений TTFB; стрибки CPU; логи показують багато внутрішніх переписувань.
- Першопричина: Правила переписування спричиняють надлишкові файлові перевірки або ланцюги редиректів; також можливе, що
.htaccessтепер містить дорогі regex-и, що виконуються при кожному запиті. - Виправлення: Спрощуйте правила; перемістіть важку логіку у vhost‑конфіг; віддавайте перевагу явним location‑блокам на зворотньому проксі; вимірюйте за допомогою access логів і часу відповіді.
Загартування без ламання: заголовки, контролі доступу та ліміти
Загартування — це не копіпаст. Загартування — це моделювання загроз плюс суміснісне тестування. WordPress має UI адмінки, REST API, cron‑ендпоінт, потоки оновлень плагінів, завантаження файлів і інколи кешуючий плагін, що очікує певних заголовків. Якщо ви защемите його як статичний сайт, він почне поводитися як зачинені двері: закритий.
Правила, що зазвичай безпечні
- Вимкнути листинг каталогів за допомогою
Options -Indexes. Це запобігає випадковому перегляду каталогів без індексного файлу. - Блокувати доступ до явних чутливих файлів, як
wp-config.phpта.env. Використовуйте явні матчі, а не широкі шаблони. - Додати ненав’язливі заголовки безпеки (
X-Content-Type-Options,Referrer-Policy). Вони низькоризикові й високоефективні.
Правила, що ризиковані і мають розглядатися як зміни, а не дефолти
- Блокування
xmlrpc.phpбез розбору. Деякі сайти ще його використовують (мобільні додатки, інтеграції). Якщо блокуєте — перевірте, чи нічого не залежить від нього. - Allow‑листинг IP для
/wp-admin/. Чудово для внутрішніх сайтів; нічний кошмар для розподілених команд і інцидент‑レスпонсу. Якщо робите, додайте шлях «break‑glass». - Складні блоки по user‑agent або query string. Вони часто блокують легітимні запити і породжують «на моєму машині працює» дебаги.
- Надто агресивні заголовки кешування на оригіні. Корисно, коли ви контролюєте версіонування; небезпечно, коли ні.
Куди поміщати логіку: .htaccess проти vhost проти load balancer
Якщо ви контролюєте серверний конфіг, віддавайте перевагу vhost‑конфігу замість .htaccess. Це швидше (немає перевірок файлу при кожному запиті) і більш аудитується. Використовуйте .htaccess як сумісницький шар, а не як основний механізм політик.
Якщо у вас є load balancer або CDN, канонічні редиректи (www/non‑www, http/https) зазвичай належать на рівень edge. Цей шар бачить справжню схему клієнта і може забезпечити консистентну поведінку між origin‑ами.
Цитата (парафраз): Системи помиляються у несподівані способи; стійкість приходить від очікування збоїв і проєктування для відновлення.
— парафраз ідей, пов’язаних з operational reliability John Allspaw.
Короткий жарт №2: Найшвидший спосіб вивчити правила переписування — спричинити цикл редиректів і спостерігати, як ваш браузер стає кардіо‑ентузіастом.
Контрольні списки / покроковий план
Контрольний список A: Відновити сервіс (15‑хвилинний режим інциденту)
- Підтвердіть симптом локально за допомогою
curlдо127.0.0.1, щоб уникнути шуму CDN. - Слідкуйте за логами помилок Apache і відтворіть запит один раз.
- Зробіть бекап поточного
.htaccessз часовою позначкою. - Тимчасово вимкніть
.htaccessперейменуванням. Якщо сервіс повернувся — ви ізольовали причину. - Відновіть мінімальний дефолтний блок переписування WordPress і повторно протестуйте головну сторінку і один пермалінк.
- Підтвердьте точки входу адмінки, як
/wp-login.phpі/wp-admin/. - Зупиніться там. Не «поліпшуйте» під час інциденту. Спочатку стабілізуйте.
Контрольний список B: Діагностика першопричини (після відновлення сервісу)
- Порівняйте проблемний файл з дефолтом. Визначте, який блок спричинив поведінку.
- Перевірте увімкнені модулі (
rewrite,headers) і сумісність версії сервера. - Огляньте поведінку проксі/LB: TLS‑термінація, forwarded‑заголовки, канонічні редиректи.
- Тестуйте у staging з тією ж конфігурацією vhost і проксі‑заголовків. «Staging без load balancer» — це не staging; це хобі.
- Додайте синтетичні тести: один на кількість редиректів, один для статусу сторінки логіну, один для пермалінку, один для статичного ресурсу.
Контрольний список C: Безпечно повторно ввести потрібні правила
- Додавайте лише одну логічну зміну за раз (один блок редиректів, один блок deny, один блок заголовків).
- Після кожної зміни робіть три перевірки: головна сторінка, пермалінк і wp-login.
- Слідкуйте за логами під час тестування. Apache часто скаже те, що браузер мовчить.
- Віддавайте перевагу vhost‑конфігу для постійних правил, якщо контролюєте сервер; тримайте
.htaccessмінімальним. - Документуйте, навіщо існує правило у коментарях. Майбутній ви забуде, а майбутній ви буде на чергуванні.
Питання та відповіді
1) Чи можу я просто видалити .htaccess?
Можете, і це хороший тест ізоляції. Але якщо ви покладаєтеся на красиві пермалінки, видалення зазвичай деградує маршрутизацію до «plain» URL або ламає розв’язання сторінок. Використовуйте видалення/перейменування, щоб підтвердити причинність, а потім відновіть мінімальний дефолт.
2) Чому одна рядок у .htaccess забрала весь сайт?
Бо Apache парсить .htaccess під час обробки запиту. Синтаксична помилка або невідома директива може змусити Apache відхилити запит (часто з 500), ще до того, як WordPress запуститься. Це швидкий фейл, а не грайливе деградування.
3) Я на Nginx. Чому мої зміни в .htaccess нічого не роблять?
Nginx не читає .htaccess. Якщо ви за Nginx (або хост керує ним), еквівалентні правила треба впроваджувати в конфіг Nginx, а не у файл, який WordPress очікує, що Apache прочитає.
4) Який найбезпечніший дефолтний вміст WordPress .htaccess?
Блок переписування WordPress, показаний вище. Це найменший набір, що змушує пермалінки працювати. Додавайте лише ті додаткові правила, які можете обґрунтувати і протестувати.
5) Чи варто додавати заголовки безпеки в .htaccess?
Це прийнятно, якщо ви не можете редагувати vhost‑конфіг, але тримайте їх мінімальними. Деякі заголовки (особливо Content Security Policy) можуть ламати плагіни, редактор та вбудований контент. Почніть з низькоризикових заголовків і розширюйте після тестування.
6) Чому виникає цикл редиректів після примусу HTTPS?
Найчастіше: TLS завершується на load balancer, Apache бачить backend HTTP, а ваша логіка редиректу використовує %{HTTPS} замість довірених forwarded‑заголовків. Виправте канонізацію на edge або навчіть Apache бачити реальну схему.
7) Чи може плагін сам перезаписати мій .htaccess?
Так. Багато кешуючих і безпекових плагінів модифікують .htaccess. Це зручно, поки не стане ні. Якщо дозволяєте це, ставтеся до нього як до коду: відслідковуйте зміни, зберігайте бекапи і розумійте, що плагін записує.
8) Що робити, якщо не можу зайти в wp-admin, щоб «Save Permalinks» і регенерувати .htaccess?
Відновіть дефолтний блок вручну (як показано вище), потім знову отримайте доступ в адмінку. Альтернативно, використайте WP‑CLI для налаштування пермалінків, але веб‑серверне переписування все одно має бути коректним, інакше WordPress не побачить очікувані шляхи.
9) Чи погана ідея AllowOverride All?
Операційно зручно, але чутливе з точки зору безпеки. Якщо можете, обмежте його: дозвольте лише те, що потрібно (AllowOverride FileInfo для переписування, можливо Options, якщо потрібно). Чим менше дозволених директив, тим менше площа ураження від поганої правки.
10) Як запобігти цьому в майбутньому?
Припиніть ставитися до .htaccess як до чорновика. Помістіть його під контроль версій, деплойте як конфіг і додайте механізм відкату. Також додайте синтетичний моніторинг, що виявляє цикли редиректів та сплески 500 швидко.
Висновок: наступні кроки для запобігання повтору
Якщо ваш WordPress‑сайт впав після зміни .htaccess, рішення не містять містики. Це дисципліна:
- Швидко відновіть сервіс, зробивши бекап і повернувши дефолтний блок переписування WordPress.
- Використовуйте логи як джерело істини. Apache каже вам, коли не може розпарсити або застосувати директиви.
- Оберіть один шар для редиректів (edge або origin) і припиніть складати канонізацію в трьох місцях.
- Загартовуйте консервативно. Блокуйте явні чутливі файли, вимикайте індекси, додавайте кілька безпечних заголовків. Героїчні експерименти лишіть для staging.
- Операціоналізуйте файл: контроль версій, автоматизований деплой і артефакт відкату, який можна застосувати напівсонним.
Мета не в тому, щоб ніколи не ламати .htaccess. Мета — зробити його ламання не катастрофічним. Продакшен‑системи винагороджують нудну правильність. Вони карають кмітливість з відсотками.