004 —WordPress
wp-config.php in productie: een veilige edit-procedure
wp-config.php is één regel van een wit scherm verwijderd. Zo bewerk je het op een live WordPress-site zonder ooit een request te laten vallen.
Het is woensdagmiddag 14:00. Een klant mailt waarom hun staging-URL in Google opduikt. Je opent SFTP, vindt wp-config.php en ziet dat de WP_HOME-constante hardcoded op het staging-domein staat. Die moet je veranderen, en de site serveert op dit moment zo'n veertig requests per minuut.
wp-config.php is het WordPress-bestand met de slechtste risk-to-edit ratio van het hele CMS. Eén verkeerd karakter (een vergeten puntkomma, een smart quote uit een Notion-document, een verdwaalde byte order mark) brengt de hele site naar een wit scherm of een half-gerenderde HTML-dump waarin het absolute bestandspad uitlekt. Geen waarschuwing, geen soft fail, geen retry. PHP kan het bestand niet parsen en de site ligt eruit tot je terugdraait of fixt.
Dit is de procedure die we gebruiken bij het bewerken van wp-config.php op een live WordPress-site, zonder requests te laten vallen. De aanname: SFTP-toegang, een shell op de server is fijn maar niet verplicht, en php staat op je laptop voor een lint-pass.
Wat er echt stuk gaat
Voordat we naar de procedure gaan, helpt het om te weten op welke manieren een wp-config.php-bewerking een productiesite onderuit haalt. Grofweg op volgorde van frequentie, in onze ervaring:
- Een achtergebleven sluit-tag
?>met witruimte erachter, die deContent-Type-header corrumpeert en redirects breekt. - Een
define()voor een constante die verderop al gedefinieerd is, wat een warning oplevert die sommige strenge PHP 8-configs als fatal naar boven brengen. - Smart quotes uit een copy-paste (
“in plaats van") die de PHP-parser niet kan lezen. - Een BOM bovenaan het bestand van een editor die op UTF-8-with-BOM stond, waardoor er drie onzichtbare bytes voor
<?phpverstuurd worden en alleheader()-calls breken. - De
$table_prefix-regel die onder derequire_oncenaar wp-settings.php is beland, wat stilletjes faalt omdat WordPress al gebooted is.
Geen van deze fouten haal je eruit met visueel kijken. Allemaal worden ze opgepakt door php -l of door de ruwe bytes van het bestand te lezen. Wij doen beide.
De pre-flight
De procedure rust op één regel: je bewerkt wp-config.php nooit in-place via SFTP. Je bewerkt altijd een kopie, lint hem, swapt hem, en houdt de vorige kopie nog een dag binnen handbereik voor het geval het nieuwe bestand een runtime-fout naar boven brengt.
Haal het live-bestand op met een datum in de naam:
sftp user@host:/var/www/site/wp-config.php ./wp-config.live.php
cp wp-config.live.php wp-config.bak.2026-05-12.php
cp wp-config.live.php wp-config.new.php
De .bak-kopie blijf je verder met rust laten. De .new-kopie is waar je de wijziging doet. Open hem in een editor waarvan je weet dat hij geen BOM toevoegt en geen quotes normaliseert. VS Code met de encoding in de statusbalk zichtbaar is prima. TextEdit op macOS in rich-text-modus niet.
Maak de aanpassing. Draai daarna lokaal een lint-pass:
php -l wp-config.new.php
# No syntax errors detected in wp-config.new.php
Als php -l iets anders teruggeeft dan precies die regel: stop. Niet uploaden. De PHP-parser op de server is dezelfde parser, en die zal identiek falen. De commando-line options-pagina van PHP legt uit dat -l het bestand inleest, parst en afsluit zonder code uit te voeren, waardoor je hem veilig kunt draaien op een config met echte database-credentials erin.
De lint-pass vangt zo'n 90% van wat wp-config.php-bewerkingen om zeep helpt. De resterende 10% is runtime: een dubbele constante, een environment-variabele die op je laptop bestaat maar niet op de server, een require-pad dat op macOS klopt maar niet op de Linux-machine. Daarvoor heb je de swap nodig.
De atomic swap
SFTP geeft je geen atomic rename tijdens schrijven zoals mv op POSIX dat doet. Een bestand met dezelfde naam uploaden overschrijft byte voor byte, en er is een window (klein, maar reëel) waarin het bestand half geschreven is en PHP het kan inlezen. Komt er in dat window een request binnen, dan krijg je een parse-error en gaat de request 500.
De oplossing: upload onder een andere naam en rename op de server. De meeste SFTP-servers behandelen rename als één filesystem-operatie.
sftp user@host <<'EOF'
cd /var/www/site
put wp-config.new.php wp-config.php.incoming
rename wp-config.php wp-config.php.previous
rename wp-config.php.incoming wp-config.php
EOF
Tijdens die twee renames (milliseconden op ext4 of APFS) wijst wp-config.php óf naar het oude bestand óf naar het nieuwe. Nooit naar een half geschreven bestand. Heeft het nieuwe bestand een runtime-probleem, dan staat wp-config.php.previous ernaast en zet één rename het terug.
Na de swap
Hit de site. Niet alleen de homepage. Hit een wp-admin-URL, een REST-endpoint, een pagina met een bekende plugin-shortcode. De reden: wp-config.php wordt geladen vóór alle plugin-code, maar plugin-code kan afhangen van constantes die je net hebt aangepast. WP_DEBUG die van false naar true gaat, brengt deprecation-warnings naar boven van een plugin-auteur die in 2024 nog PHP 7.4-syntax aanleverde. Beter dat je die nu ziet dan om 23:00.
Zodra de site weer netjes antwoordt, lock je het bestand:
chmod 440 wp-config.php
chown www-data:www-data wp-config.php
440 betekent dat de webserver-user kan lezen en niemand kan schrijven. 400 is strenger, maar breekt sommige shared-hosting-setups waar de web-user ook de FTP-user is. Kies de strengste van de twee die je stack niet sloopt. De WordPress hardening guide raadt 400 of 440 aan en waarschuwt expliciet tegen 644 of 666.
Laat wp-config.php.previous de rest van de dag op de server staan. Morgen weghalen. Vergeet je het, dan struikelt je volgende deploy er waarschijnlijk overheen, wat op zich ook een reminder is.
Credentials en key-rotatie
Heeft de bewerking ook de acht AUTH_KEY / SECURE_AUTH_KEY / LOGGED_IN_KEY / NONCE_KEY-salts (plus hun vier _SALT-tegenhangers) ververst, dan wordt elke ingelogde gebruiker uitgelogd. Dat is by design. Vervang je ze omdat je een compromittering vermoedt: nu doen. Vervang je ze omdat de codex zegt dat het periodiek moet en je klant heeft vierduizend ingelogde abonnees: plan het in een rustig uur en plak een banner op de site.
Genereer de nieuwe salts via de officiële WordPress salt-API in plaats van zelf iets in elkaar te zetten. Hij geeft 64 cryptografisch willekeurige bytes per constante, in precies het formaat dat wp-config.php verwacht. Plakken, linten, swappen.
Verander je de database-credentials (DB_USER, DB_PASSWORD, DB_HOST), test de nieuwe credentials dan eerst met de mysql-client vanaf dezelfde server. WordPress geeft je geen bruikbare fout als ze niet kloppen. Je krijgt Error establishing a database connection, en dat kan betekenen dat de credentials fout zijn, dat de DB plat ligt, dat de host onbereikbaar is, of dat je een constante-naam hebt vertypt. mysql -u newuser -p -h localhost vertelt je in minder dan een seconde welke van de vier het is.
Wanneer de procedure niet past
Deze procedure gaat uit van SFTP, een shell-gewoonte en een php-binary op je laptop. Voor een verouderde site die je vorige week hebt overgenomen, waarvan de SFTP-credentials in een mailtje uit 2019 staan en de vorige developer met FileZilla's drag-and-drop werkte, is dat met de hand lastig vol te houden. Dat was precies het geval waar we bij onze eigen shop steeds tegenaan liepen, en de reden waarom we Pier zijn gaan bouwen: een Mac-app die met de FTP/SFTP-server aanmeert en elke file-edit als een getrackte, terugdraaibare wijziging behandelt. De wp-config.php-swap hierboven is in Pier één toetsaanslag, met de lint-pass en een automatische entry in de version history die terug naar wat er om 14:00 stond letterlijk één klik maakt. Zelfde procedure, minder touw.
Het kleinste wat je vandaag kunt doen: trek een kopie van je productie-wp-config.php, draai er lokaal php -l overheen en bevestig dat hij schoon parst. Doet hij dat niet, dan zit er een latente bug te wachten op de volgende edit om zichzelf te onthullen.
— Vragen —
Kan ik wp-config.php bewerken vanuit de WordPress-admin?
Nee. Daar zit geen ingebouwde admin-UI voor. Sommige security-plugins tonen een read-only weergave, maar schrijftoegang is om hardening-redenen bewust alleen vanaf het filesystem mogelijk.
Welke file-permissions moet wp-config.php hebben?
440 in de meeste setups, 400 als je stack dat toelaat. Nooit 644 of 666. De webserver-user heeft lees-rechten nodig, niemand heeft tijdens runtime schrijfrechten nodig.
Worden alle gebruikers uitgelogd als ik de salts vervang?
Ja. Dat is precies waar de salts voor dienen. Plan de rotatie in een rustige periode en plak een banner als je site veel ingelogde gebruikers heeft.