031 —Architecture
Een geërfde .htaccess lezen: blok voor blok zonder paniek
Een site geërfd waarvan de .htaccess begint met drie blokken dode RewriteRules in het Nederlands? Lees hem blok voor blok. Zo pakken wij dat aan.
De eerste vijf minuten
Het is woensdag, 16:20. Een klant stuurt een mail door van zijn hostingmaatschappij: "Uw site is gezakt voor onze PHP 8.2-compatibiliteitsscan." Je logt via SFTP in op de document root, opent .htaccess, en treft 340 regels aan. De helft is uitgecommentarieerd in het Nederlands. Er staan drie aparte WordPress-rewriteblokken in, waarvan twee verouderd. Ergens tussendoor zit één mod_security-regel die al een maand stilzwijgend het contactformulier op 403 zet.
Voordat je iets aanraakt: kopieer het bestand van de server af. Noem het htaccess-baseline-2026-05-26.txt en zet het in een notitie-map. Elke bewerking vanaf nu is een diff tegen die baseline. Als er over twintig minuten iets stuk gaat, wil je het origineel met één scp-commando terug kunnen zetten, niet uit je geheugen reconstrueren.
Lees daarna van boven naar beneden, langzaam. Het .htaccess-bestand wordt directive voor directive geparset, in volgorde, en die volgorde telt. De laatst matchende regel voor een bepaalde response header wint. De eerste matchende RewriteRule met de [L]-flag stopt de keten. Een bestand dat er prima uitziet kan niets doen omdat een Redirect 301 op regel twee al alles eronder kortsluit.
De bloktaxonomie
De meeste geërfde .htaccess-bestanden zijn vijf blokken die aan elkaar geplakt zitten, ongeveer in deze volgorde:
- PHP-handler-switch (
AddHandler/SetHandler), vaak achtergelaten door de PHP-selector van cPanel of DirectAdmin. - Het CMS-rewriteblok, omsloten door
# BEGIN/# ENDmarkers. - Restanten van caching-plugins (W3 Total Cache, WP Super Cache, WP Rocket).
- Security hardening: deny-from-regels, wp-config-bescherming, mod_security-aanpassingen.
- Site-specifieke redirects: 301's die zich tijdens migraties hebben opgestapeld.
Lees het als vijf bestanden aan elkaar geplakt, niet als één. Elk blok heeft zijn eigen auteur, zijn eigen decennium, zijn eigen bug. Het CMS-blok is het blok dat je niet met de hand mag aanraken. De andere vier zijn waar jouw werk ligt.
De PHP-handler-regel
Op shared hosting ziet de bovenkant van het bestand er meestal zo uit:
# Use PHP 7.4
AddHandler application/x-httpd-ea-php74 .php
Migreer je naar PHP 8.2, dan is deze regel een valstrik. De MultiPHP-UI van de host lijkt de versie correct in te stellen, terwijl .htaccess hem stilletjes overruled. De fix: commentarieer de regel uit, sla op, herlaad de site, en bevestig op een phpinfo()-pagina dat de versie ook echt is omgeklapt. De documentatie van Apache over de AddHandler-directive is het nalezen waard als de syntax onbekend aandoet.
Het CMS-rewriteblok
Een standaard WordPress-blok ziet er zo uit:
# BEGIN WordPress
<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
Zes regels. Dat is het hele legitieme WordPress-rewriteblok. Alles tussen de BEGIN/END-markers dat niet overeenkomt met dit template is toegevoegd door een plugin of door een vorige ontwikkelaar die .htaccess als kladblok behandelde. De eigen permalinks-documentatie van WordPress bevestigt het: het core-blok verandert niet tussen releases.
Zie je twee BEGIN WordPress-blokken in hetzelfde bestand, dan is er één dood. Meestal de tweede. Verwijderen, opslaan, harde reload van de homepage plus één permalink. Laden ze allebei nog, dan was de snede schoon.
Rewriteregels lezen zonder gek te worden
Het blok dat de meeste hoofden breekt is de rewrite-keten. Lees elke regel als vier delen:
- De condities (elke
RewriteConderboven, tot aan de vorigeRewriteRule). - Het matchpatroon (de regex na
RewriteRule). - De substitutie (de URL waar hij naartoe herschrijft).
- De flags tussen rechte haken.
Een regel als deze:
RewriteCond %{HTTP_HOST} ^www\.oldsite\.nl$ [NC]
RewriteRule ^(.*)$ https://newsite.nl/$1 [R=301,L]
lees je dus als: "als de host-header www.oldsite.nl is, hoofdletter-ongevoelig, redirect dan elk pad naar hetzelfde pad op newsite.nl met een 301, en stop met verwerken." Die [L] is het stukje dat ontwikkelaars vergeten. Zonder kan de volgende regel opnieuw herschrijven, soms met de gevreesde /index.php/index.php-loop tot gevolg.
Twee flags zijn verantwoordelijk voor het grootste deel van de productiebranden: [L] (last, stop de keten) en [R=301] (permanente redirect, wordt maandenlang door browsers gecachet). Een verkeerde 301 is een langlopend incident, want gebruikers met de gecachete redirect kunnen het nieuwe gedrag pas bereiken nadat ze hun browser hebben geleegd. Wees zeker voordat je pusht.
De gevaarlijke stilte
De regels die voor uitval zorgen zijn niet de luidruchtige. Het zijn de stille, die er onschuldig uitzien.
Een Header set X-Frame-Options "SAMEORIGIN" die jaren geleden is toegevoegd kan een nieuwe iframe-embed voor een partnersite kapotmaken. Een <Files wp-config.php>-blok dat met de verkeerde bestandsnaam (wpconfig.php) is gekopieerd, beschermt niets. Een RewriteRule ^wp-admin/ - [F] die is achtergebleven na een hackherstel in 2019 zal de legitieme admin op 403 zetten zodra de IP-allowlist erboven is verlopen.
Zoek bij een audit eerst op drie patronen in het bestand:
grep -nE '^\s*(Redirect|RedirectMatch)\s' .htaccess
grep -nE '\[F\]|\[G\]|\[R=' .htaccess
grep -nE 'Header\s+(set|append|add)' .htaccess
Die drie commando's brengen de regels boven die het meest waarschijnlijk stilletjes het verkeer sturen. Lees elke hit in zijn context. Kun je niet in één zin uitleggen waarom hij er staat, dan is het een kandidaat om te verwijderen, maar nog niet. Commentarieer hem eerst uit met een datumstempel:
# 2026-05-26 BM: disabled, suspect stale hotlink block
# RewriteCond %{HTTP_REFERER} !^https?://(www\.)?site\.nl [NC]
# RewriteRule \.(jpg|jpeg|png|gif)$ - [F]
Een commentaar met datum is een grafsteen. Over zes maanden, als niemand klaagt, verwijder je het blok. Tot dan vertelt het commentaar de volgende persoon wat jij vermoedde en wanneer.
De kill-switch-test
De snelste manier om te weten of .htaccess de oorzaak van een incident is, is de kill switch. Hernoem het:
mv .htaccess .htaccess.disabled
Herlaad de site. Verdwijnt het symptoom, dan zit de bug in .htaccess. Blijft het symptoom, kijk dan elders (PHP, de database, de load balancer). Zet het bestand daarna terug en doe een binary search: commentarieer de onderste helft uit, herlaad, dan het onderste kwart, totdat het schuldige blok alleen staat.
Het is grof, en het kost je een paar seconden kapotte permalinks op een live site. Maar het is beslissend op een manier waarop alleen lezen dat niet is. Het Apache-project documenteert de verwerkingsvolgorde van secties en directives voor de gevallen waarin de bug niet in één blok zit, maar in de interactie tussen twee.
Wat wij uiteindelijk hebben gebouwd
Toen wij Pier bouwden, liepen we hier bij vrijwel elke onboarding van een legacy site tegenaan: een .htaccess die een archeologische opgraving was. Wij losten dat zo op: elke opgeslagen versie van het bestand krijgt een diff met tijdstempel in de versiegeschiedenis, zodat een kill-switch-test gevolgd door twintig bewerkingen met één klik teruggedraaid kan worden, in plaats van met een SCP-paniek.
Kies de kleinste variant hiervan die je vandaag kunt doen: open één .htaccess die je een jaar niet hebt gelezen, sla een baseline-kopie van de server af op, en draai de drie grep-commando's hierboven erop. Wat in de komende vijf minuten boven komt, is het begin van je audit.
— Vragen —
Wat gebeurt er als ik .htaccess helemaal verwijder?
WordPress genereert het core-blok opnieuw bij de volgende permalink-save, maar elke custom redirect, security-regel en PHP-handler-regel is voorgoed weg. Kopieer het bestand van de server af voordat je het aanraakt.
Hoe vind ik welke regel een 500-fout veroorzaakt?
Hernoem .htaccess naar .htaccess.disabled en herlaad de site. Stopt de 500, doe dan een binary search door telkens de helft van het bestand uit te commentariëren tot het schuldige blok alleen staat.
Zijn .htaccess-rewriteregels hoofdlettergevoelig?
Standaard wel. Gebruik de [NC]-flag op RewriteCond en RewriteRule om ze hoofdletter-ongevoelig te maken. Hostnames en paden worden anders letterlijk gematcht.