Dat was vreemd, want van buitenaf deed alles het. De site, het inlogscherm en ook die REST API gaven gewoon antwoord. Toch vond WordPress zelf van niet: de eigen controle onder Sitediagnose meldde dat de REST API verboden terrein was.
Twee waarheden tegelijk. Voor een bezoeker werkte alles. Voor de site zelf niet. Dat verschil bleek de sleutel, maar zover was ik nog niet.
Eerst de verkeerde verdachte. Het beheer had die dag vaker dingen getoond die niet meer klopten: een plugin die verwijderd was en toch nog in de lijst stond, een melding die bleef hangen. Dat ruikt naar een cache die oude gegevens blijft uitdelen. Die cache heb ik uitgezet. Het maakte geen verschil.
De redenering klopte, en was toch fout. Een verklaring die goed in elkaar zit, is nog geen bewijs.
Wat er echt aan de hand was. Een site praat de hele dag met zichzelf. Om te controleren of iets werkt, vraagt de server zijn eigen pagina’s op, net zoals een bezoeker dat zou doen. De nieuwe plugin doet dat ook, voordat hij de tweestapsverificatie durft aan te zetten.
Die plugin heeft een firewall. En in de lijst met geblokkeerde adressen van die firewall stond het adres van de webserver zelf. De reden stond erbij: te veel pagina’s opgevraagd die niet bestaan.
De site had dus zijn eigen server buitengesloten. Elke keer dat de server iets aan zichzelf vroeg, zei de firewall nee. Bezoekers merkten er niets van. De plugin wel, en die weigerde daarom een instelling op te slaan die hij niet kon controleren.
Hoe het zover kwam, weet ik niet zeker. Eerder die dag had ik de verkeerde variant van de plugin geïnstalleerd en die er met moeite weer afgehaald. Een poos was de plugin half aanwezig. Ik vermoed dat de server in die tijd telkens om bestanden vroeg die er niet meer waren, en dat de firewall dat als een aanval telde. Gemeten heb ik dat niet.
De oplossing had nog een addertje. Mijn AI-assistent haalde het adres met een opdracht van de blokkeerlijst en zette het op de lijst met vertrouwde adressen. De opdracht meldde succes. De blokkade bleef.
De plugin bewaart die lijsten op twee plekken: in de database en in een bestand dat bij elk verzoek wordt gelezen. De opdracht had alleen de database bijgewerkt. In het bestand stond het adres nog. Pas toen het ook daar weg was, kon de server zichzelf weer bereiken, en bleef de schakelaar staan.
Wat ik ervan leer
Zet het adres van je eigen server op de lijst met vertrouwde adressen vóórdat je een firewall aanzet. Niet erna.
Dat wist ik eigenlijk al. Het stond in mijn aantekeningen van een andere site, waar hetzelfde speelde. Ik heb er niet op tijd in gekeken.
Als iets van buitenaf werkt en van binnenuit niet, zoek dan niet in de site maar in wat er tussen de server en zichzelf staat.
En een opdracht die succes meldt, heeft gedaan wat hij zegt. Niet per se wat jij nodig had. Kijk op de plek waar het telt.
Onderweg maakte de assistent zelf ook een fout. Om bij het meten de cache te omzeilen, plakte hij een verzonnen toevoeging achter het adres. Die toevoeging betekent in WordPress iets: het archief van een maand. Dat bestond niet, dus elke meting leverde een “pagina niet gevonden” op. Precies waar de firewall op telde.
Voor de techneuten: wat er precies gebeurt
Omgeving: WordPress 7 multisite, Really Simple Security Pro in de multisite-variant, gedeelde hosting waarbij de machine voor SSH een andere is dan de webserver.
De verschijnselen:
- de hoofdschakelaar voor 2FA springt na opslaan terug, met de melding “REST API blocked”
- Sitediagnose: de REST API-test geeft
(403) Forbidden - van buitenaf geeft
/wp-json/gewoon 200, en losse bestanden (css, js) ook vanaf de server
De oorzaak: het uitgaande adres van de eigen webserver staat op de blokkeerlijst van de firewall, met als reden dat de drempel voor 404’s is overschreden. Elk verzoek dat de server via PHP aan zichzelf doet (loopback) krijgt 403.
Meten zonder toegang tot de server. Laat WordPress zelf een pagina van de eigen site ophalen. Dat kan via de REST API, als ingelogde gebruiker:
GET /wp-json/wp-block-editor/v1/url-details?url=https://voorbeeld.nl/
Krijg je de titel van de pagina terug, dan bereikt de server zichzelf. Krijg je geen antwoord, vergelijk dan met een css-bestand van de eigen site en met een externe site. Lukken die wel, dan zit het in de loopback.
Let op met curl via SSH. Is de machine waarop je via SSH inlogt een andere dan de webserver, dan bewijst een geslaagde curl niets: het verzoek komt van een ander adres.
Vinden en oplossen:
wp rsssl show_blocked_ips
wp rsssl remove_firewall_ip_block <adres>
wp rsssl add_firewall_trusted_ip <adres>
Deze opdrachten pasten bij mij alleen de database aan. Het bestand wp-content/firewall.php, met de lijsten $blocked_ips en $white_list, bleef ongewijzigd. De firewall uitzetten hielp ook niet. Wat wel werkte: de regel met het adres uit $blocked_ips in dat bestand halen (eerst een kopie, daarna php -l).
Het bestand wordt opnieuw uit de database geschreven zodra je in het scherm van de firewall iets opslaat. De nette volgorde is dus: adressen toevoegen via de opdrachtregel, één keer opslaan in het scherm, en dan in het bestand controleren of ze er staan.
Niet als cache-omzeiler gebruiken: ?m=…. Dat is in WordPress het maandarchief. Een waarde die geen bestaande maand is, geeft een 404, en die telt mee voor de drempel. Neem een naam die WordPress niet kent.
Vooraf doen: het adres van de eigen webserver, en de adressen van diensten die de site van buitenaf beheren of bewaken, als vertrouwd instellen voordat de firewall aangaat. De plugin heeft daarvoor twee aparte lijsten: één voor de firewall en één voor het beperken van inlogpogingen.



