Why WordPress sites score lower (and what to fix)
In our scans of 1000 popular domains we noticed a clear bimodal distribution by stack. Static sites and modern frameworks cluster at 70-85/100. WordPress sites cluster at 50-65/100. The gap isn't WordPress-the-software being bad. It's the default configuration WordPress ships with, plus the typical plugin ecosystem, plus the kind of ops team that runs WordPress in 2026. Concrete findings and how to fix them.
What we see on the average WordPress install
| Finding | Frequency | Severity |
|---|---|---|
WordPress version disclosed in <meta> | ~85% | LOW (information disclosure → CVE targeting) |
/readme.html reachable | ~70% | LOW (full version disclosure) |
/wp-json/wp/v2/users exposes usernames | ~60% | MEDIUM (user enumeration → password attack) |
/xmlrpc.php reachable | ~55% | MEDIUM (brute-force amplification, pingback abuse) |
| Outdated jQuery in active plugins | ~75% | MEDIUM (known XSS sinks) |
| No CSP header | ~80% | MEDIUM (no defense in depth) |
| X-Powered-By, Server banners with versions | ~50% | LOW (CVE targeting) |
?author=1 redirect leaks admin username | ~40% | MEDIUM (admin username for password spray) |
Each of these is small. Stacked on a single install they account for the 15-20 point gap vs a hardened static site.
Fix #1: hide the version
The easy one. WordPress emits <meta name="generator" content="WordPress 6.4.3">
by default. Drop it via a single hook:
// in functions.php or a security plugin
remove_action('wp_head', 'wp_generator');
add_filter('the_generator', '__return_empty_string');
Also block /readme.html and /license.txt at the web server:
# nginx
location ~* /(readme|license)\.(html|txt)$ {
return 404;
}
Net: the attacker doesn't know your exact version. CVE targeting becomes guesswork.
Fix #2: shut down user enumeration
Two endpoints leak usernames:
/wp-json/wp/v2/users— JSON list of all users with login names./?author=1— redirects to/author/admin-username/.
Mitigation:
// Block /wp-json/wp/v2/users for unauthenticated requests
add_filter('rest_endpoints', function($endpoints) {
if (isset($endpoints['/wp/v2/users'])) {
unset($endpoints['/wp/v2/users']);
}
if (isset($endpoints['/wp/v2/users/(?P<id>[\\d]+)'])) {
unset($endpoints['/wp/v2/users/(?P<id>[\\d]+)']);
}
return $endpoints;
});
// Block ?author= scans
if (preg_match('/\\?author=\\d+/', $_SERVER['REQUEST_URI'])) {
wp_die('Not allowed', 403);
}
Or use a plugin (Wordfence, iThemes Security) that does both with a checkbox.
Fix #3: kill xmlrpc.php (or rate-limit it)
XML-RPC is rarely used in 2026 (REST API replaced its purpose). It's commonly abused
for brute-force amplification (system.multicall tries 1000 password
combos in one request) and pingback DDoS (use your site to launch attacks against
others).
# Hard block at nginx
location = /xmlrpc.php { return 403; }
If a Jetpack feature you actually use needs XML-RPC, allowlist Jetpack's IP ranges and rate-limit aggressively (5 requests/minute per IP).
Fix #4: add security headers
WordPress doesn't ship security headers. Add at the web server (so they survive plugin deactivations):
# nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com" always;
Caveat: WordPress (and most plugins) inline scripts and styles. A strict CSP without
'unsafe-inline' breaks most themes. The CSP above is permissive but still
a defense improvement over no CSP. Tighten over time as you replace inline scripts with
external ones (or migrate to a static-export workflow).
Fix #5: source the plugin ecosystem
Most WordPress sites we scan run 15-30 plugins. The CVE feed (OSV.dev) tracks ~5000 WordPress plugins. The probability that some plugin you have installed has a known CVE in the past 90 days is high.
Practical maintenance:
- Subscribe to Wordfence's threat feed or Patchstack alerts.
- Auto-update minor versions of plugins (in
wp-config.php:define('WP_AUTO_UPDATE_CORE', 'minor');and the equivalentauto_update_pluginfilter). - Audit "set and forget" plugins quarterly. If a plugin hasn't been updated in 12 months, replace it or remove it.
- Use a CDN with WAF (Cloudflare's free tier blocks the most-exploited WordPress patterns).
Fix #6: source maps and dev leftovers
WordPress builds increasingly include webpack-bundled JS (Gutenberg blocks, page
builders). About a third of installs we scan ship source maps (.map files)
that expose plugin internals + sometimes API tokens. Block at nginx:
location ~* \.(map)$ { return 404; }
And block the usual leak suspects:
location ~ /\.(env|git|svn|aws) { return 404; }
location ~ \.(bak|backup|swp|sql)$ { return 404; }
What this gets you
A typical WordPress install in our 2026 dataset starts at ~58/100. Apply the 6 fixes above, no plugin updates, just config: ~74/100 (+16 points). Apply CVE patching on the plugin set: ~80-85/100. The gap from "default WordPress" to "hardened WordPress" is real and entirely on the operator side.
A static site with the same domain DNS/TLS posture and equivalent business value scores 80-90 from the start. The framework choice is a security choice. WordPress is fine when you accept the operational discipline; it's a liability when you don't.
What's your score?
Run a free Basic scan on your WordPress install. Get the specific findings + remediations.
Run a scan