Most WordPress sites are not compromised through zero-day exploits. They are compromised through the boring set of misconfigurations that have been on the public web for ten years: a missing security header, an exposed readme.html, an open xmlrpc.php, a TLS certificate that expired three days ago, a default admin username that anyone can enumerate through ?author=1. This guide walks through the 18 checks that, together, block roughly 90 percent of the automated attacks aimed at WordPress in 2026. Each check ships with the exact config line you can paste into nginx, Apache or functions.php, and the whole thing is structured to be done in under 90 minutes if you are starting from scratch.
Table of contents
The state of WordPress security in 2026
WordPress still powers about 43 percent of the open web in 2026. That dominant position is the reason it remains the favourite target of automated attack toolkits. The attacker economics are simple: write one scraper that probes /xmlrpc.php and /wp-login.php on the whole IPv4 space and you cover a meaningful chunk of the internet in a week. The defender economics are equally simple: closing the eighteen well-known holes covered in this guide forces the attacker to fall back to authenticated plugin exploitation or social engineering, which is roughly two orders of magnitude more expensive per compromise.
The threat landscape has shifted in two important ways since 2023. First, AI-assisted reconnaissance now mass-generates plausible spear-phishing emails to WordPress administrators after harvesting their name from a leaked author archive. Second, the rise of “form spam as a service” means that a single misconfigured contact form can fund the attacker’s monthly hosting bill. Both shifts reward defenders that take the time to enumerate their public surface and close it methodically, which is exactly what this guide does.
HTTP security headers — the foundation
Security headers are the cheapest, fastest improvement you can make. They cost nothing to add, no functionality is sacrificed, and the protection is enforced by every modern browser. The 2026 baseline contains six headers that should be present on every public page of a WordPress site.
HSTS (Strict-Transport-Security)
HSTS tells the browser to refuse plain HTTP for your domain for a set duration. It blocks the entire class of downgrade attacks on hostile networks and prevents stolen-cookie replay over insecure links. The recommended configuration is one year with subdomain coverage and preload eligibility:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Add the header at the web-server level (nginx add_header directive, Apache Header always set) so it survives WordPress plugin churn. Once you have run with HSTS for a month without HTTPS issues, submit the domain to hstspreload.org so it ships preloaded with every major browser.
Content-Security-Policy
CSP is the single most powerful XSS mitigation available. A locked-down policy refuses to load scripts, stylesheets or inline JavaScript from domains you have not explicitly allow-listed. The full migration is rarely instant on a WordPress site because legacy themes pull resources from many origins, but a strict starting point is achievable:
Content-Security-Policy: default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';
Plan a two-week monitoring period with Content-Security-Policy-Report-Only before enforcing the policy in production. The report-only mode logs violations without blocking, so you can spot the third-party origins you need to allow before the policy actually breaks something.
X-Frame-Options and frame-ancestors
The clickjacking protection is either X-Frame-Options: SAMEORIGIN or frame-ancestors 'self' inside CSP. The latter is the modern replacement; the former is the legacy fallback for browsers that do not yet support CSP. Setting both is harmless and is the safest path.
The three “minor but free” headers
X-Content-Type-Options: nosniff stops browsers from second-guessing the declared content type, which closes a small but real XSS class. Referrer-Policy: strict-origin-when-cross-origin limits the data leaked through the Referer header. Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=() disables browser features your site does not need. The three together add about 200 bytes per response and noticeably tighten your privacy posture.
TLS certificate hygiene
A valid TLS certificate is the entry ticket. A correctly-renewed TLS certificate is the actual signal that operations are healthy. We have seen too many incidents where a site was technically secure but the certificate auto-renewal silently failed because of an upstream rate limit, a DNS challenge misconfiguration, or an account credential change at the hosting provider. The result is always the same: every browser on every device shows a scary warning, and the site loses traffic for hours.
Make TLS hygiene a recurring task, not a one-shot install. Verify the days remaining at least once a week through our SSL Certificate Checker or via SecurityWatch. Set up two independent renewal triggers (the Let’s Encrypt cron job AND an external monitor) so a failure on one is caught by the other. Use a wildcard certificate when you have multiple subdomains, and stick with the same CA for all subdomains to simplify the audit. In 2026, no TLS configuration is complete without TLS 1.3 as the preferred protocol and HTTP/3 / QUIC as an optional layer on top for mobile users.
WordPress version disclosure
An attacker who knows your exact WordPress version can search the CVE database for unpatched issues in roughly four seconds. By default, WordPress publishes its version in three places: a <meta name="generator" content="WordPress X.Y.Z"> tag on every page, the ?ver= query parameter on every enqueued CSS and JS file, and the /readme.html file at the document root. All three need to go.
Hide the generator tag with a four-line theme snippet:
// functions.php
remove_action('wp_head', 'wp_generator');
add_filter('the_generator', function () { return ''; });
Strip the ?ver= query string from assets with a filter:
add_filter('style_loader_src', 'pag_strip_ver', 9999, 2);
add_filter('script_loader_src', 'pag_strip_ver', 9999, 2);
function pag_strip_ver($src, $handle) {
return remove_query_arg('ver', $src);
}
Delete /readme.html from the document root after every WordPress core update, or block it at the web server level:
# nginx
location = /readme.html { deny all; }
# Apache
<Files "readme.html">Require all denied</Files>
xmlrpc.php, REST users and default exposures
xmlrpc.php is the historical brute-force entry point of WordPress. It accepts authentication over an RPC protocol that bypasses most rate-limiting plugins, supports the system.multicall method which lets an attacker test hundreds of credentials per request, and is also the source of the pingback DDoS amplification that turned WordPress sites into unwilling participants in denial-of-service campaigns. If you do not use the Jetpack mobile apps and you do not need remote publishing, block it:
# nginx
location = /xmlrpc.php { deny all; return 403; }
# Apache
<Files "xmlrpc.php">Require all denied</Files>
The REST API endpoint /wp-json/wp/v2/users is the second classic exposure. By default it returns the list of users with publish permission, which lets an attacker enumerate the admin login in one request. Disable it for unauthenticated callers with a filter:
add_filter('rest_endpoints', function ($endpoints) {
if (!current_user_can('list_users')) {
unset($endpoints['/wp/v2/users']);
unset($endpoints['/wp/v2/users/(?P<id>[\\d]+)']);
}
return $endpoints;
});
Directory listings on /wp-includes/ and /wp-content/uploads/ should never be enabled. They expose the file structure and version of core, plugins and themes. Disable autoindex globally on nginx (autoindex off;) and Apache (Options -Indexes). For extra safety, drop an empty index.html in each writable directory so even a misconfigured server returns nothing useful.
User enumeration and login hardening
The third large exposure on a default WordPress install is user enumeration via author archives. When a visitor hits https://yoursite.com/?author=1, WordPress redirects to /author/admin-login/, leaking the login name. Attackers then have a username and only need to brute-force the password. Block the redirect:
add_action('template_redirect', function () {
if (isset($_GET['author'])) {
wp_redirect(home_url(), 301);
exit;
}
});
Beyond the rename, login hardening rests on three controls. First, two-factor authentication for every administrator account. The Two Factor plugin maintained by the WordPress core team is the simplest path. Second, a strong password manager — Bitwarden, 1Password and KeePass all work; using the same password as your email is the single most exploited reuse pattern. Third, rate-limit failed logins; the WordPress core does not do this, so install a plugin like Limit Login Attempts Reloaded or terminate brute-force traffic at the web server with fail2ban.
Plugins, themes and the supply chain
The 2024-2026 wave of WordPress incidents has shifted from core vulnerabilities, which are now rare, to plugin and theme supply-chain attacks. Patchstack and Wordfence consistently report that the majority of new CVEs target plugins, not core. Three rules cut the risk dramatically:
- Minimise plugin count. Every plugin is a potential entry point. Audit the list once a quarter and remove anything you do not actively use. The “I might need it later” plugin is the one that gets compromised.
- Prefer plugins with recent updates. A plugin that was last updated 18 months ago is a yellow flag; 36 months is a red flag. Drop it for an actively-maintained alternative.
- Subscribe to a CVE feed. Patchstack, Wordfence and the WordPress vulnerability database all publish RSS or JSON feeds. Wire them into your Slack or your monitoring tool so a critical plugin advisory shows up the day it is published, not next quarter when an automated scan picks it up on your site.
For themes, the same logic applies, with one extra rule: never edit a theme directly. Use a child theme so updates are not blocked, and so a future audit can see what is custom and what is upstream. A modified parent theme is the configuration that produces “the site was running fine, we updated the theme, everything broke” incidents.
The full 18-point checklist
This is the consolidated list. Run through it once at site launch, then every quarter, then after every WordPress core update.
| # | Check | Severity if missing |
|---|---|---|
| 1 | HSTS header with max-age >= 15552000 + includeSubDomains | High |
| 2 | Content-Security-Policy at least at default-src ‘self’ | High |
| 3 | X-Frame-Options: SAMEORIGIN or frame-ancestors ‘self’ in CSP | Med |
| 4 | X-Content-Type-Options: nosniff | Low |
| 5 | Referrer-Policy: strict-origin-when-cross-origin | Low |
| 6 | Permissions-Policy with explicit deny on unused features | Low |
| 7 | TLS certificate with > 30 days remaining + auto-renew alert | High |
| 8 | TLS 1.3 enabled, TLS 1.0 / 1.1 disabled | Med |
| 9 | WordPress generator meta removed | Med |
| 10 | ?ver= stripped from asset URLs | Low |
| 11 | readme.html deleted or blocked at web server | Med |
| 12 | xmlrpc.php blocked (unless used by Jetpack) | Med |
| 13 | /wp-json/wp/v2/users blocked for unauthenticated callers | High |
| 14 | /wp-includes/ directory listing disabled | High |
| 15 | /wp-content/uploads/ directory listing disabled | Med |
| 16 | ?author=N redirect blocked or rewritten | Med |
| 17 | 2FA enforced on every administrator account | High |
| 18 | Plugin and theme inventory reviewed last 90 days | Med |
Want this scan automated?
SecuChecker runs all 18 checks against any URL in under 20 seconds and gives you a posture score plus the exact fix for every gap. Free, browser-based, non-intrusive.
FAQ
How often should I run a security audit on a WordPress site?
Quarterly at minimum, after every WordPress core update, and after every plugin install or removal. For high-traffic or e-commerce sites, weekly with our SecurityWatch tool is a reasonable cadence.
Is HSTS preload safe for a small site?
Yes, but it is one-way. Once submitted to the preload list, you cannot easily revert to HTTP. Run with HSTS in your server config for a month without issues, then submit. Removal takes weeks at best.
Will blocking xmlrpc.php break Jetpack?
It will break the Jetpack mobile apps and some remote publishing features. If you use those, allow-list xmlrpc.php from the Jetpack IPs published in their documentation. If you do not use Jetpack mobile or remote publishing, blocking xmlrpc.php is the right call.
Do I need a managed WAF if I do all this?
A WAF (Cloudflare, Sucuri, Wordfence Premium) adds defence in depth, especially against zero-day plugin vulnerabilities discovered between two of your audits. The 18 checks here are necessary; a WAF is recommended for sites where downtime is expensive.
How do I rotate the admin password without breaking automation?
Create a dedicated automation user with the role you actually need (often Editor, not Administrator), use that user in your CI / CMS automation, and rotate the admin password on a schedule. Keep the rotation interval realistic (90-180 days) so people do not write it on a sticky note.
My site already has a high SecuChecker score, what next?
Subscribe to a plugin CVE feed (Patchstack, Wordfence), enable file-integrity monitoring (Wordfence or iThemes Security), and set up automated daily backups offsite. Those three steps move the site from “hardened” to “resilient”.













