— Artikel — № 001

001 —WordPress

Legacy WordPress .htaccess: de regels die 80% oplossen

De .htaccess-regels die stilletjes de meeste redirect loops, mixed content en kapotte pretty permalinks op overgenomen WordPress-sites oplossen.

Een messing sleutel op ivoren papieren kaart naast kleirode lakzegel, leren regelboek en schuifmaat op bone werkblad.
Hero · gestileerd stilleven№ 001

Je hebt een WordPress-installatie uit 2017 overgenomen. De vorige developer is onbereikbaar, de staging-URL lekt nog steeds door in canonical tags, en de helft van de uploads geeft 403 terug achter Cloudflare. Voordat je PHP aanraakt: open .htaccess. Negen van de tien keer zit de fix daar.

Wat volgt is het korte lijstje waar we als eerste naar grijpen bij een audit van een legacy site. Elke regel is copy-paste-klaar. Zet ze binnen het <IfModule mod_rewrite.c>-blok, boven het standaard WordPress-rewrite-stanza — Apache leest van boven naar beneden en stopt bij de eerste match, dus volgorde is bepalend.

Forceer HTTPS zonder loop achter een proxy

De klassieke RewriteCond %{HTTPS} off werkt op een kale server. Zet de site achter Cloudflare, een load balancer of een andere reverse proxy die TLS termineert, en de server ziet eeuwig platte HTTP. Je krijgt een redirect loop en rond lunchtijd een supportticket.

Controleer in plaats daarvan de forwarded header:

RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

Beide condities moeten falen voordat we redirecten, dus een proxy die X-Forwarded-Proto: https zet wordt vertrouwd en de loop sterft. Houd de HTTPS off-check erbij voor het geval de site direct op poort 80 wordt benaderd.

Canoniseer de host

Legacy sites stapelen aliassen op: www en niet-www, de oude staging-hostname, soms het rauwe IP van de server. Google indexeert wat hij als eerste tegenkomt. Kies er één en 301 de rest.

RewriteCond %{HTTP_HOST} !^example\.com$ [NC]
RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]

Vervang example.com door je canonieke host. Deze ene regel doodt en passant ook het "onze staging staat in Google"-probleem, omdat de staging-hostname nu naar productie redirect — ervan uitgaande dat staging dezelfde .htaccess gebruikt, wat meestal het geval is als iemand twee jaar geleden de bestanden heeft gersynced en het verder vergeten is.

Sluit xmlrpc.php af

Strikt genomen geen rewrite, maar hij hoort in hetzelfde bestand. xmlrpc.php is het meest misbruikte endpoint op legacy WordPress — pingback-amplification, brute-force login via system.multicall. Als je de Jetpack-app of de WordPress-mobiele app niet gebruikt, sluit hem af.

<Files xmlrpc.php>
    Require all denied
</Files>

Houd eerst een dag je access logs in de gaten. Als er iets legitiems op binnenkomt, whitelist dat IP dan met Require ip 203.0.113.0/24 in plaats van de deur open te laten.

Repareer pretty permalinks na een padwijziging

Verplaats WordPress naar een subdirectory, of juist eruit, en pretty permalinks breken op een manier die lastig te diagnosticeren is omdat de homepage nog gewoon werkt. Het symptoom: / laadt, /about/ geeft 404 van Apache, niet van WordPress. De fix is de RewriteBase-directive en de fallback naar index.php.

RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

Als WordPress in /blog/ staat, zet dan RewriteBase /blog/ en schrijf om naar /blog/index.php. De twee RewriteCond-regels zijn de hele truc: stuur de request alleen naar index.php als het geen echt bestand of directory is. Laat ze weg en je gaat PHP serveren voor elke image request, waar niemand vrolijk van wordt.

Redirect oude permalink-structuren zonder plugin

Overgenomen sites dragen vaak twee generaties permalink-structuur met zich mee. De versie uit 2014 gebruikte /?p=123, de redesign uit 2018 ging naar /%postname%/, en de redirect plugin die de brug sloeg werd tijdens een security-audit in 2021 gedeactiveerd. Nu geeft de helft van je backlinks 404.

Voor de veelvoorkomende migratie van datum-gebaseerd naar postname:

RewriteRule ^([0-9]{4})/([0-9]{2})/([0-9]{2})/([^/]+)/?$ /$4/ [R=301,L]

Voor URL's met een category-prefix waar dat category-segment nu weg is:

RewriteRule ^category/(news|blog|press)/(.+)$ /$2 [R=301,L]

Test met curl -I voordat je commit. De regex-engine van Apache is greedy, en het verkeerde anchor-teken redirect je admin-URL's het niets in.

curl -I https://example.com/2019/03/14/some-post/
# HTTP/2 301
# location: https://example.com/some-post/

Bescherm wp-config.php en de uploads-directory

Nog twee regels die hun plek verdienen. De eerste weigert directe toegang tot wp-config.php, die Apache gewoon serveert als PHP-processing ooit verkeerd staat ingesteld — en op shared hosting staat dat regelmatig verkeerd. De tweede blokkeert PHP-executie binnen wp-content/uploads, de standaardroute voor exfiltratie bij plugin-kwetsbaarheden.

<Files wp-config.php>
    Require all denied
</Files>

# In wp-content/uploads/.htaccess
<FilesMatch "\.(php|phtml|phar|php7|php8)$">
    Require all denied
</FilesMatch>

De uploads-regel komt in een aparte .htaccess binnen wp-content/uploads/, niet in de root. Apache voegt directory-level configs tijdens de request samen; houd de scope strak zodat je niet per ongeluk een legitiem endpoint elders sloopt.

Deployen zonder kopzorgen

Rewrites werken perfect in je editor en breken productie op een manier die het admin-paneel meeneemt. Houd vóór elke edit een back-up met timestamp aan (cp .htaccess .htaccess.$(date +%s)). Valideer syntax met apachectl configtest als je shell-toegang hebt, of httpd -t op sommige distributies. En test één regel tegelijk — als er drie tegelijk ingaan en de site 500't, zit je om 23:00 handmatig te bisectioneren.

Toen we Pier bouwden, liepen we telkens tegen ditzelfde patroon aan — één aanpassing in .htaccess, site plat, geen makkelijke rollback over SFTP. We losten het uiteindelijk op met een volledige version history van elk bestand dat via de app wordt aangeraakt, zodat een slechte rewrite terugdraaien één toetsaanslag is in plaats van een zoektocht door back-ups.

Als je vandaag één ding doet: open je productie-.htaccess, kijk of de HTTPS-redirect X-Forwarded-Proto gebruikt, en zo niet, fix dat voordat de volgende TLS-terminerende proxy voor je site komt te staan.

— Vragen —

Waar horen deze regels precies in .htaccess?

Binnen het <IfModule mod_rewrite.c>-blok, boven het door WordPress gegenereerde stanza dat begint bij de # BEGIN WordPress-comment. Apache verwerkt van boven naar beneden en stopt bij de eerste match.

Werken deze regels ook op Nginx?

Nee. Nginx negeert .htaccess volledig. De equivalenten horen in je server block als return 301- en location-directives. De logica vertaalt, de syntax niet.

Waarom X-Forwarded-Proto gebruiken in plaats van alleen HTTPS off?

Achter een reverse proxy die TLS termineert ziet de origin server bij elke request HTTP. Alleen op HTTPS off checken levert een oneindige redirect loop op. De forwarded header weerspiegelt het werkelijke client-protocol.