Je opende je access logs en zag wat elke WordPress-beheerder uiteindelijk ziet: een muur van POSTs naar /wp-login.php vanaf IPs die je niet kent, of een piek verkeer naar /xmlrpc.php die PHP-FPM-workers liet spiken en wp-admin loom maakte. Het doel van dit artikel is eenvoudig en afgebakend: stop dat gehamer nu, voorkom herhaling, en doe dat zonder Jetpack, de mobiele app of de integraties die stilletjes op XML-RPC leunen om zeep te helpen.
De korte versie is dat brute force tegen WordPress een probleem in drie lagen is en ook een antwoord in drie lagen nodig heeft. Rate limiting op server- of edge-niveau stopt verkeer voordat PHP geladen wordt, authenticatie-hardening (2FA, Application Passwords, unieke credentials) beschermt de login-flow zelf, en monitoring vertelt je of de andere twee lagen het nog wel houden. Elke laag afzonderlijk bezwijkt onder een echte aanval; alle drie samen maken van een actief incident een schouderophalen.
Dit artikel is de specifieke how-to voor de twee URLs die het brute force-verkeer opvangen. De bredere checklist staat in WordPress beveiliging verharden, die je eerst even zou moeten doorlezen als je nog nooit aan beveiliging op deze site hebt gezeten.
Hoe een brute force-aanval eruit ziet in je logs
De symptomen zijn consistent genoeg dat je ze binnen dertig seconden log-tailen herkent. Op nginx laat tail -f /var/log/nginx/access.log | grep -E "(wp-login|xmlrpc)" tijdens een actieve aanval een van de twee patronen hieronder zien.
Credential stuffing tegen wp-login.php ziet er uit als honderden tot duizenden POSTs per minuut naar dezelfde URL, vanaf veel verschillende IPs, elk met een andere username/wachtwoord-combinatie:
203.0.113.42 - - [09/Apr/2026:14:22:11 +0200] "POST /wp-login.php HTTP/1.1" 200 3124
198.51.100.7 - - [09/Apr/2026:14:22:11 +0200] "POST /wp-login.php HTTP/1.1" 200 3124
192.0.2.188 - - [09/Apr/2026:14:22:12 +0200] "POST /wp-login.php HTTP/1.1" 200 3124
De response code is meestal 200 (WordPress rendert de login-pagina opnieuw met een foutmelding) en de body-grootte is bijna constant. Klassieke IP-based rate limiting vangt dit patroon makkelijk omdat de hoeveelheid requests in het oog springt.
Amplification via xmlrpc.php ziet er heel anders uit en is een stuk sluwer. Een handvol POSTs, soms maar drie of vier per minuut, met grote request bodies:
203.0.113.42 - - [09/Apr/2026:14:22:11 +0200] "POST /xmlrpc.php HTTP/1.1" 200 512 "-" "Mozilla/5.0"
203.0.113.42 - - [09/Apr/2026:14:22:41 +0200] "POST /xmlrpc.php HTTP/1.1" 200 512 "-" "Mozilla/5.0"
Elk van die requests kan een system.multicall payload bevatten die honderden wachtwoorden test in één HTTP-round-trip. Sucuri's originele disclosure en de analyse van Cloudflare documenteren de mechaniek: één POST droeg vroeger tot 1.999 authenticatie-pogingen. Rate limiting op request-count mist dit omdat de hoeveelheid requests laag is; je hebt method-level awareness of agressieve xmlrpc.php-throttling nodig.
Neveneffecten tijdens beide patronen: PHP-FPM-workers raken verzadigd, CPU-gebruik stijgt, wp-admin wordt traag of onbereikbaar, en je host stuurt je mogelijk een waarschuwing. Het artikel over hoog CPU-gebruik legt uit waarom: elke request die PHP bereikt verbruikt een worker-slot, ongeacht of het een echte bezoeker is of een brute force-bot, en een uitgeputte worker-pool queuet gewoon alles.
Waarom deze twee URLs, en wat de aanvallers eigenlijk doen
wp-login.php is het interactieve login-endpoint. Elke WordPress-site heeft het op hetzelfde pad, het accepteert POSTs zonder enige rate limiting uit de doos, en het geeft een onderscheidbaar succes- of faalsignaal terug. Geautomatiseerde botnets draaien er op schaal dictionary-aanvallen tegenaan, langs de gebruikelijke usernames (admin, wordpress, je merknaam) en gangbare wachtwoordlijsten.
xmlrpc.php is het legacy XML-RPC-endpoint. Het staat in elke WordPress-versie sinds 3.5 (december 2012) standaard aan, wat de XML-RPC-documentatie van Kinsta bevestigt. De reden dat het een magneet voor brute force is, is dat XML-RPC een non-interactief protocol is: het profiteert niet van 2FA, CAPTCHA of welke interactieve anti-automatisering dan ook die voor wp-login.php hangt. Een aanvaller met de juiste methode kan een authenticated credential-test tegen xmlrpc.php draaien en de normale WordPress login-beschermingen gaan nooit af.
Twee historische noten bepalen hoe je vandaag de dag naar het xmlrpc.php-pad moet kijken.
Het system.multicall amplification-venster. Voor WordPress 4.4 liet system.multicall een aanvaller tot 1.999 authenticatiepogingen in één HTTP POST bundelen. WordPress Trac ticket #34336 patchte dit in WordPress 4.4 (december 2015): na de eerste auth-fout binnen een multicall-batch wordt de rest van de batch meteen afgebroken. Sites op WordPress 4.4 of nieuwer lopen niet meer het volledige amplification-probleem, maar de aanval bestaat nog wel als trage, gedistribueerde sequentiële XML-RPC-calls. Draai je iets ouder dan 4.4? Stop met lezen en update eerst; de rest van dit artikel gaat je niet redden.
Jetpack en legitiem XML-RPC-verkeer. Jetpack gebruikt nog steeds XML-RPC voor zijn communicatie met WordPress.com, volgens Jetpacks eigen XML-RPC-documentatie, laatst bijgewerkt in mei 2025. Het werkt met token-based authenticatie in plaats van user credentials, wat een echte security-nuance is, maar het loopt wél over xmlrpc.php. Een blanket-blokkade van xmlrpc.php op webserverniveau breekt Jetpack. De WordPress mobiele app in oudere versies gebruikte ook XML-RPC; huidige versies zijn overgestapt op de REST API. Third-party publishing tools (MarsEdit, legacy Windows Live Writer-installaties) en sommige oudere WooCommerce-integraties kunnen er ook nog op leunen.
De praktische implicatie: je hebt twee strategische keuzes voor xmlrpc.php. Óf je gebruikt geen enkele legitieme XML-RPC-functie en je blokkeert het hele endpoint op webserverniveau, óf je houdt het bereikbaar en rate-limit het stevig terwijl je de specifieke gevaarlijke methodes uitzet. De rest van dit artikel behandelt beide paden.
Laag 1: rate limiten op de server of aan de edge
De snelste, goedkoopste, meest effectieve laag is request-rate-limiting die gebeurt voordat PHP laadt. Elke request die op deze laag wordt afgewezen kost geen PHP-workertijd en kan je worker-pool niet verzadigen. Dat is belangrijk, want plugin-gebaseerde verdedigingen draaien binnen WordPress, wat betekent dat de WordPress-bootstrap, databaseverbinding en plugin-load allemaal al uitgevoerd zijn voordat de block afgaat. Bij een echte aanval is dat al te laat.
Kies de laag die bij jouw infrastructuur past. Je hebt maar één van deze drie nodig; alle drie tegelijk draaien mag, hoeft niet.
nginx: limit_req
Nginx levert een limit_req-module die op client-IP rate limit. De WordPress Advanced Administration brute force-guide publiceert de canonieke snippet. Voeg dit toe aan de nginx-config van je site en herlaad nginx met nginx -t && systemctl reload nginx:
# In het http {} block, een keer per server
limit_req_zone $binary_remote_addr zone=logins:10m rate=10r/m;
limit_req_zone $binary_remote_addr zone=xmlrpc:10m rate=20r/m;
# In het server {} block van je WordPress-site
location = /wp-login.php {
# Tot 20 requests in een burst, eerste 20 zonder vertraging
limit_req zone=logins burst=20 nodelay;
# Geef de request gewoon door aan PHP-FPM
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
location = /xmlrpc.php {
# xmlrpc.php is drukker; stem dit af op je Jetpack-verkeer
limit_req zone=xmlrpc burst=10 nodelay;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
De 10r/m op wp-login.php betekent dat elk client-IP tien requests per minuut mag doen, met een burst-allowance van twintig. Een aanvaller die vijf requests per seconde uitspuugt mag de eerste twintig zonder vertraging doorlaten, en elke verdere request in die burst krijgt 503 Service Unavailable. Echte logins trippen de limiet bijna nooit; een mens die vier keer zijn wachtwoord intypt haalt geen tien requests per minuut.
Je weet dat het werkt als een snelle aanvalssimulatie de limiet raakt. Draai vanaf een tweede machine for i in $(seq 1 30); do curl -s -o /dev/null -w "%{http_code}\n" -X POST https://jouwsite.nl/wp-login.php; done en kijk hoe de response codes omslaan van 200 naar 503 zodra de burst op is.
Apache: mod_ratelimit of mod_security
Apache heeft geen ingebouwd equivalent van limit_req. De twee ondersteunde paden zijn mod_qos (third-party, precies hiervoor ontworpen) en ModSecurity met de OWASP Core Rule Set DOS-protection rules. De ModSecurity-regels REQUEST-912-DOS-PROTECTION.conf detecteren request-floods en passen een tijdelijke blok toe; stel tx.dos_burst_time_slice, tx.dos_counter_threshold en tx.dos_block_timeout in het CRS setup-bestand in op waardes die bij je verkeer passen.
Een WordPress-georiënteerde ModSecurity-ruleset die OWASP CRS uitbreidt is de Rev3rseSecurity-ruleset; die bevat XML-RPC-specifieke regels voor het detecteren van authenticatie-fouten en blokkeren na N fouten binnen een tijdvenster. Voor Apache-deployments zonder ModSecurity is het realistische antwoord: zet Cloudflare of een andere edge-WAF ervoor en rate limit daar.
Cloudflare (elk plan, inclusief free)
Als je site al door Cloudflare loopt, is de snelste winst de Rate Limiting-feature van Cloudflare, die sinds 2024 ongemeten beschikbaar is op alle plannen, inclusief free. Configureer twee regels in het Cloudflare-dashboard onder Security > Rate Limiting Rules:
- Path contains
/wp-login.php, drempel 5 requests per minuut per IP, actie Block voor 1 uur. - Path contains
/xmlrpc.php, drempel 10 requests per 30 seconden per IP, actie Block voor 1 uur.
Deze waardes komen uit community-consensus en de productie-defaults van WordPress VIP (zie onder). Ze zijn strak genoeg om geautomatiseerde aanvallen te stoppen en ruim genoeg dat Jetpacks polling er niet overheen gaat.
Cloudflare onderhoudt ook een managed WAF-regel die historisch als WP0018 is gelabeld en system.multicall amplification-patronen detecteert. Die is beschikbaar op Pro en hoger, niet op free. Zit je op free? Dan is de rate-limit-regel hierboven het juiste alternatief.
Eén Cloudflare-valkuil die mensen later bijt. Als je site achter Cloudflare staat, tonen je nginx access logs de IP-adressen van Cloudflare, niet die van de echte aanvallers. Draai je ook fail2ban (verderop)? Dan moet je nginx configureren om de CF-Connecting-IP-header te loggen in plaats van $remote_addr, anders bant fail2ban Cloudflare. De fail2ban + Cloudflare-gids van RunCloud loopt het log-formaat door.
Laag 2: de xml-rpc-beslissing
Zodra de rate-limit-laag leeft, beslis je strategisch wat je met xmlrpc.php doet. Drie opties, van grof naar chirurgisch.
Optie A: xmlrpc.php volledig blokkeren (geen Jetpack, geen XML-RPC-clients)
Gebruik je geen Jetpack, geen XML-RPC publishing tool, en draait je mobiele app een moderne versie (REST API + Application Passwords)? Dan blokkeer je het bestand op de webserver:
# nginx: return 444 sluit de verbinding zonder response
location = /xmlrpc.php {
deny all;
access_log off;
log_not_found off;
return 444;
}
# Apache: in .htaccess in de WordPress root, boven het WordPress rewrite block
Require all denied
Verifieer met curl -I https://jouwsite.nl/xmlrpc.php. Je zou 403 Forbidden, een verbroken verbinding, of een 444-response moeten krijgen. Wat je niet mag zien is 200 OK met als body XML-RPC server accepts POST requests only.
Optie B: selectieve method removal (Jetpack werkend houden, gevaarlijke delen weghalen)
Heb je Jetpack of een andere XML-RPC-integratie nodig? Dan blokkeer je het endpoint niet. Je haalt de specifieke methodes die misbruikt worden eruit. WordPress biedt het xmlrpc_methods-filter, waarmee je method-namen uit de dispatch table kunt halen. Het volgende laat de twee methodes vallen die er voor brute force en reflected DDoS toe doen, en laat Jetpacks methodes intact:
// In een mu-plugin op wp-content/mu-plugins/xmlrpc-harden.php
<?php
add_filter( 'xmlrpc_methods', function ( $methods ) {
// system.multicall is de amplification-vector. WP 4.4+ breekt
// al af na de eerste auth-fout, maar de methode volledig weghalen
// ontneemt de aanval sowieso zijn aanloop.
unset( $methods['system.multicall'] );
// pingback.ping wordt misbruikt voor reflected DDoS: een aanvaller
// vraagt jouw site om een target-URL te "pingbacken", en jouw site
// valt dan namens hen het target aan.
unset( $methods['pingback.ping'] );
unset( $methods['pingback.extensions.getPingbacks'] );
return $methods;
} );
Belangrijke nuance over het xmlrpc_enabled-filter. Je ziet online wel adviezen die suggereren dat add_filter( 'xmlrpc_enabled', '__return_false' ); de manier is om "XML-RPC uit te schakelen". Dat is het niet. Dat filter schakelt alleen de authenticated XML-RPC-methodes uit. Onauthenticated methodes zoals pingback.ping blijven gewoon werken. Wil je echt volledig uit? Dan heb je server-level blocking nodig (Optie A). Wil je selectief hardenen? Gebruik dan het xmlrpc_methods-filter hierboven, niet xmlrpc_enabled.
Optie C: rate limit xmlrpc.php en Jetpack-IPs op de allowlist
Voor sites op WordPress VIP of vergelijkbare productie-platforms is het patroon strakker: alleen de gepubliceerde IP-ranges van Jetpack mogen xmlrpc.php bereiken, al het andere wordt geblokkeerd. De XML-RPC security controls-documentatie van WordPress VIP publiceert de exacte regels die zij draaien, inclusief de 10 requests per 30 seconden per IP-limiet met een uur blokkade op overtredingen. Voor de meeste self-hosted sites is dat overkill; voor een grote publicatie die op Jetpack draait en geen enkele XML-RPC-attack-surface kan hebben is het uitstekend.
Laag 3: authenticatie-hardening
Rate limits stoppen de vloed. Authenticatie-hardening stopt de request die erdoorheen komt. Deze laag is waar de echte login-poging de echte WordPress ontmoet, en het is ook de laag die de meeste beheerders overslaan omdat "de rate limit het wel oplost", totdat er een aanval komt vanaf één IP die langzaam genoeg gaat om onder de limiet door te glippen.
Tweefactor-authenticatie op elke administrator. WordPress core levert geen 2FA mee. De Two-Factor plugin van de core-contributors is een juiste default: TOTP-codes plus backup codes, zonder dat hele keukenkastje aan extra features. WP 2FA van Melapress is de andere juiste default voor minder technische teams. Zet er eentje aan en handhaaf hem voor elke gebruiker met edit_posts of hoger. Een gekraakt wachtwoord zonder de tweede factor is niets waard. De complete walkthrough, inclusief de grace period die voorkomt dat je je team op dag één buitensluit, staat in tweestapsverificatie (2FA) voor WordPress.
Application Passwords voor alles wat geen mens is. Application Passwords zijn toegevoegd in WordPress 5.6. Elk is een 24-karakter alfanumerieke credential (142 bits entropie), individueel intrekbaar, per integratie scoped. Ze werken met de REST API (zie REST API-beveiliging in WordPress voor het user-enumeratie aanvalsoppervlak) en met XML-RPC, maar niet met interactieve wp-login.php-logins. Zet elke mobiele app, CI-pipeline en third-party-integratie van je persoonlijke admin-wachtwoord af op een eigen Application Password. Het punt is dat een application password niets doet tegen een interactief loginformulier en dus geen springplank naar je hoofdaccount is.
Eén kanttekening. Application Passwords zijn een gedeeltelijke mitigatie van het XML-RPC brute force-probleem, geen oplossing. Gewone user-wachtwoorden werken vandaag nog steeds tegen xmlrpc.php. WordPress Trac #62789 stelt voor Application Passwords af te dwingen voor alle authenticated XML-RPC-requests (en gewone user-wachtwoorden over XML-RPC te blokkeren), wat de overgebleven credential-stuffing-vector zou dichten. Per april 2026 is het ticket open en niet gemerged, gemarkeerd als breaking change die vooraankondiging nodig heeft. Het is de moeite waard om te volgen; het is alleen nog niet live.
Limit Login Attempts en audit van fouten. Limit Login Attempts Reloaded is de minimale plugin-optie: failed logins per IP tellen, na een drempel uitsluiten, de poging loggen. Defaults zijn vier pogingen, twintig minuten lockout, opgeschaalde lockout van vier uur na drie rondes. Die defaults zijn prima. Draai je al een volledige security suite (Wordfence, iThemes Security, Sucuri)? Dan zit deze functie er al in; stapel niet twee plugins die beide tegen dezelfde database failed logins zitten te tellen.
Sterke, unieke administrator-credentials. Dit is geen WordPress-control; dit is operationele hygiëne. Elke administrator gebruikt een password manager. Elke administrator heeft een uniek e-mailadres dat hij zelf beheert. Elke gedeelde admin@bedrijf.nl-mailbox breekt het wachtwoord-reset-herstelpad. Verwijder accounts van ex-medewerkers op de dag dat ze weggaan, niet "binnenkort".
Het hardening-artikel behandelt deze punten uitgebreider; de korte versie is dat een sterk wachtwoord plus 2FA plus Application Passwords voor integraties de authenticatie-baseline is, en niets in dit artikel vervangt dat.
Laag 4: monitoring met fail2ban en log-review
Rate limits en hardening werken stilletjes. Je hebt telemetrie nodig om te weten of ze het houden en of de aanval van vorm is veranderd. Twee praktische tools doen het werk.
fail2ban voor dynamische IP-blokkades. fail2ban kijkt naar logbestanden voor patronen en drukt matches in iptables of nftables voor een ban-periode. Het patroon voor WordPress brute force is: match herhaalde POSTs op /wp-login.php en /xmlrpc.php. Een minimale filter (in /etc/fail2ban/filter.d/wordpress.conf):
[Definition]
failregex = ^<HOST> -.*"(GET|POST).*(/wp-login\.php|/xmlrpc\.php).*" 200
ignoreregex =
En de bijbehorende jail in /etc/fail2ban/jail.local:
[wordpress]
enabled = true
filter = wordpress
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 1800
bantime = 86400
usedns = no
Vijf hits binnen dertig minuten triggert een ban van 24 uur. Zet bantime terug naar 3600 (een uur) als je minder wrijving wilt, of omhoog naar een week voor sites met aanhoudende aanvallen. Test je regex altijd met fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress.conf voordat je de jail activeert.
Belangrijke fail2ban-kanttekening. fail2ban werkt op OS/iptables-niveau. Het vangt bans nadat de PHP-worker al is gestart en de request al is gelogd. Dat is langzamer dan server-level rate limiting (nginx limit_req blokkeert voordat PHP laadt). Gebruik fail2ban als de vangnet-laag voor telemetrie en retaliatie, niet als primaire verdediging. De nginx rate limit is sneller.
Zit je achter Cloudflare? Dan heeft fail2ban hulp nodig. Zoals hierboven genoemd: je nginx-logs tonen Cloudflare-IPs. Configureer of nginx om $http_cf_connecting_ip te loggen, of gebruik Cloudflare Firewall Rules rechtstreeks in plaats van fail2ban.
Log-review cadans. Check je access logs wekelijks de eerste maand na het toepassen van deze controls, daarna maandelijks. Het doel is om verschuivingen in aanvalsvorm te spotten: als een aanval van één IP naar een gedistribueerd botnet verschuift, moet je rate limit misschien opnieuw worden afgesteld of is de aanvaller verhuisd naar een laag die je niet in de gaten houdt. Een rappe grep -c "POST /wp-login.php" /var/log/nginx/access.log.1 is meestal genoeg om te weten of de aanval nog leeft.
Mythes die je tijd verspillen
Een korte lijst van dingen die op verdediging lijken en het niet zijn.
"Ik heb mijn login-URL veranderd naar /geheime-login/, dus ik ben veilig." Je hebt bot-ruis verminderd. Je hebt geen security control toegevoegd. Vastberaden scanners nemen login-path-discovery mee; de 2024 CVE-lijst bevat een "hide login URL"-plugin-kwetsbaarheid die de verborgen URL lekte door een bug in de plugin zelf. Het is ook nutteloos tegen xmlrpc.php, dat op hetzelfde pad blijft. Rate limiting en 2FA doen het echte werk.
"Een security plugin stopt op zichzelf brute force wel." Nee. Plugins draaien binnen WordPress, dus elke request die zij zien heeft de PHP-worker-pool al bereikt. Onder een echte aanval raken de workers verzadigd door de CPU-kosten van de plugin zelf, en de site valt om. Server- of edge-rate-limits zijn verplicht; plugins voegen daar authenticatie-intelligentie bovenop toe, niet in plaats van.
"xmlrpc.php blokkeren breekt niks belangrijks." Dat doet het wel als je Jetpack of een XML-RPC publishing tool draait. Het is ook een veilige keuze als je dat niet doet. Het juiste antwoord is "eerst auditen, dan blokkeren of filteren". Plak nooit zomaar een deny all-regel zonder te weten welke delen van je site xmlrpc.php aanroepen.
"Het xmlrpc_enabled-filter schakelt XML-RPC uit." Alleen de authenticated methodes. Unauthenticated methodes zoals pingback.ping blijven werken. Wil je volledig uit? Dan heb je server-level blocking nodig.
"Na WordPress 4.4 is de XML-RPC-aanval opgelost." De amplification is opgelost: multicall breekt af na de eerste auth-fout. Gedistribueerde, trage, sequentiële XML-RPC brute force werkt nog steeds. Het endpoint heeft nog steeds rate limiting of selectieve blokkade nodig.
Wanneer je hulp moet vragen
Bel een specialist als een van deze waar is, en verzamel de lijst hieronder voordat je dat doet.
- Rate limits staan aan, maar PHP-FPM-workers raken nog steeds verzadigd. Iets omzeilt de limiet, of de aanval is verplaatst naar een endpoint dat je niet bewaakt.
- Je logs tonen een aanval die doorgaat vanuit duizenden roterende IPs zelfs met Cloudflare rate limiting aan. Dat is een gedistribueerd botnet en vraagt waarschijnlijk om een Cloudflare Pro-plan of een eigen WAF-ruleset.
- Je kunt zelf niet inloggen omdat de rate limit je echte verkeer vangt. Lees eerst het artikel niet kunnen inloggen op WordPress; het probleem hoeft niet in je brute force-verdediging te zitten.
- Je vermoedt dat de aanval al is geslaagd (onverwachte admin-users, onverwachte berichten, redirects naar vreemde domeinen). Brute force-bescherming is dan niet meer de juiste vraag; je zit in incident-response.
- De site ligt of is intermitterend onder de aanval en je wil hem nu terug.
Verzamel voordat je vraagt:
- De WordPress-versie, PHP-versie, webserver (nginx of Apache), en of je achter Cloudflare of een andere CDN zit.
- Of Jetpack, de mobiele app of een XML-RPC-integratie actief in gebruik is.
- De laatste 500 regels access log rond het aanvalsvenster (
tail -500 /var/log/nginx/access.log), specifiek gefilterd op wp-login.php en xmlrpc.php. - De rate-limit-configuratie die je tot nu toe hebt geprobeerd (nginx
limit_req_zone/limit_req-regels, screenshots van de Cloudflare-regel, of de fail2ban jail-config). - Een lijst van actieve plugins met versies (
wp plugin list --format=csvofwp-admin > Plugins). - Of je een van de symptomen uit het artikel PHP workers uitgeput hebt gezien, want dat is het gevolg-effect van een niet-ingedamde brute force-aanval.
De complete server-configuratie, samengevoegd
Dit is de volledige nginx-snippet voor een site die xmlrpc.php volledig blokkeert en wp-login.php rate-limit. Pas het PHP-FPM-socketpad en de WordPress-root aan op je eigen install.
# In het http {} block, een keer per server
limit_req_zone $binary_remote_addr zone=logins:10m rate=10r/m;
server {
server_name jouwsite.nl www.jouwsite.nl;
root /var/www/jouwsite.nl;
index index.php;
# xmlrpc.php volledig blokkeren: geen Jetpack op deze site
location = /xmlrpc.php {
deny all;
access_log off;
log_not_found off;
return 444;
}
# wp-login.php rate limiten
location = /wp-login.php {
limit_req zone=logins burst=20 nodelay;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
# Gewone WordPress rewrite
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
}
Voor een site die xmlrpc.php bereikbaar houdt voor Jetpack, vervang je het location = /xmlrpc.php-blok door:
limit_req_zone $binary_remote_addr zone=xmlrpc:10m rate=20r/m;
location = /xmlrpc.php {
limit_req zone=xmlrpc burst=10 nodelay;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
En zet de mu-plugin uit Optie B in wp-content/mu-plugins/xmlrpc-harden.php om system.multicall en de pingback-methodes op WordPress-niveau weg te halen.
Herlaad nginx met nginx -t && systemctl reload nginx en verifieer met de curl-commando's eerder in dit artikel. Als de verificatie slaagt, houdt het brute force-verkeer binnen één request-cyclus op PHP-workers te verbranden, wordt wp-admin weer snel, en worden je logs rustig.