WordPress Security Hardening: XML-RPC, Rate Limits, 2FA
Author
John CavilSecurity does not have to be complex. You can block the most common attacks with a few focused steps. This guide shows fast, safe wins you can apply today. It works on any host. It shines on LiteSpeed and Cloudflare. And yes—on Pofii’s Pofii-Tuned LiteSpeed stack, most of this plays nicely out of the box.
1) Reduce the attack surface: handle XML-RPC the right way
XML-RPC powers some remote features. However, attackers often abuse it for brute force and DDoS amplification. You have three safe options. Pick the one that matches your setup.
Option A — Disable XML-RPC completely (safest)
Use this if you do not need Jetpack, legacy mobile apps, or remote posting.
.htaccess (Apache/LiteSpeed):
<Files "xmlrpc.php">
Require all denied
</Files>
NGINX:
location = /xmlrpc.php { return 403; }
Option B — Allow only selected IPs
Use this if a known service needs XML-RPC.
.htaccess:
<Files "xmlrpc.php">
Require ip 203.0.113.10
Require ip 203.0.113.11
Require all denied
</Files>
Option C — Keep it on, but rate-limit it
Use this when you need XML-RPC for a tool you trust.
- At the CDN: add a rule that rate limits
/xmlrpc.phpby IP. - On the server: use your web server or a firewall (e.g., fail2ban) to limit requests per minute.
Tip: If you disable XML-RPC, also check that your site’s REST API endpoints remain accessible. Modern plugins rely on them.
2) Rate limits that don’t break your site
Attackers hammer login and XML-RPC endpoints. Rate limiting turns the firehose into a drip.
What to rate-limit
/wp-login.php/xmlrpc.php(if enabled)/wp-json/wp/v2/*(only if you see abuse; avoid blocking normal REST use)
Safe starting thresholds
- Login page: 10 requests / minute / IP → then block or challenge for 10 minutes.
- XML-RPC: 10 requests / minute / IP → then block or challenge for 10 minutes.
Where to set it
- Cloudflare or your CDN: Use Rate Limiting / Rules. Challenge first, then block on repeat.
- LiteSpeed/Apache:
mod_security/mod_evasiveor a WAF profile. - Server firewall: Use fail2ban with a regex that watches access logs for these paths.
Need a refresher on CDN setup? See: Cloudflare settings that actually speed things up.
3) 2FA on all admin accounts (non-negotiable)
Passwords leak. Two-factor stops most account takeovers.
- Enable 2FA for all users with Administrator and Editor roles.
- Support TOTP apps (Authy, Google Authenticator) or passkeys if your stack supports them.
- Keep one backup code per user in a safe place.
- Enforce strong passwords and no password reuse.
Roll it out in phases. Start with site owners. Then editors. Then remaining staff.
4) Lock down login without locking out humans
Small tweaks make brute force expensive.
- Rename or protect
/wp-login.php: a custom login URL frustrates basic bots. - Add a CAPTCHA or challenge after 3–5 failed attempts.
- Limit concurrent sessions per user.
- Auto-log out inactive sessions after 24 hours (or less for high-risk roles).
- Disable XML-RPC authentication if you don’t need it.
5) Principle of least privilege (roles, users, keys)
Keep access tight. Mistakes shrink when blast radius is small.
- Give each person the lowest role that lets them work.
- Remove old admins and stale accounts.
- Use a separate admin account for maintenance. Do daily work as Editor.
- Rotate application passwords, API keys, and salts on schedule.
- Store secrets outside the repo. Use environment variables.
6) Updates, plugins, and themes (boring but vital)
Old code is low-hanging fruit for attackers.
- Update WordPress core, themes, and plugins weekly.
- Remove plugins you do not use. Fewer plugins = fewer risks.
- Prefer maintained plugins with active updates.
- Avoid “all-in-one” mega plugins unless you really need them.
- Test updates on a staging site first. Then deploy.
- See our staging → production checklist when it ships.
7) Backups and a quick restore drill
Security is not only prevention. Recovery matters.
- Keep daily off-site backups (files + database).
- Retention: at least 7–14 days.
- Do a monthly restore drill to verify you can recover fast.
- After a restore, rotate passwords and keys.
- If you move hosts, use a clean cutover:
- Zero-downtime guide: DNS TTL Playbook
- Full walkthrough: Migrate to a faster host
8) Headers and server-side basics
Small headers create big friction for attackers.
- Force HTTPS. Add HSTS after you confirm all assets load over HTTPS.
- Set X-Frame-Options: SAMEORIGIN unless you need framing.
- Set X-Content-Type-Options: nosniff.
- Add Content-Security-Policy (CSP) in report-only first. Then enforce gradually.
- On LiteSpeed, keep HTTP/3 and Brotli on for speed and smaller attack surface per request.
9) Monitor and alert (noise-free)
You cannot fix what you do not see.
- Watch for login failures, file changes, and spikes on
/wp-login.phpand/xmlrpc.php. - Send email or Slack alerts for critical events only.
- Keep logs for at least 7 days.
Minimal security checklist (copy/paste)
- Disable or rate-limit XML-RPC
- Rate-limit /wp-login.php at CDN or WAF
- Enforce 2FA for Admins/Editors
- Strong passwords, session limits, CAPTCHA after fails
- Least privilege roles; remove stale users
- Weekly updates; remove unused plugins/themes
- Daily off-site backups; monthly restore drill
- HTTPS, HSTS, security headers; start CSP in report-only
- Monitor logins and spikes; alert on critical events
FAQ
Should I disable XML-RPC or just rate-limit it?
If you do not use it, disable it. If you must keep it, rate-limit it and allow only trusted IPs.
Will rate limits block real users?
Not if you set sane thresholds. Challenge first. Block only after repeated abuse.
Is 2FA overkill for small sites?
No. 2FA blocks most account takeovers. It is the highest-ROI control you can add.
Do security plugins replace a WAF or CDN rules?
They help, but they do not replace network-level protection. Use both when possible.
Want this done for you? On Pofii, we pair Pofii-Tuned LiteSpeed, strict rate limits, and fast edges with a hands-on rollout: XML-RPC policy, 2FA setup, and a working restore drill. You get speed and sane security—without breaking logins or checkouts.
Leave a Comment