Hardening
Three plugins narrow what an anonymous visitor can learn or reach: instance-security-hardening, instance-security-headers and instance-disable-rest-api.
Enumeration and Login
Section titled “Enumeration and Login”instance-security-hardening closes the routes that hand out valid usernames, because a username turns password guessing from two unknowns into one.
| Rule | Why |
|---|---|
/?author=1 probes return a 404 | The redirect to /author/<login>/ otherwise confirms a valid login |
| Author rewrite rules are never registered | No author archives to enumerate in the first place |
author_link points at the home page | Author links stop appearing in the front end |
| The users sitemap provider is removed | wp-sitemap-users-1.xml is a list of valid logins |
/wp/v2/users routes are unregistered | Same list, over REST |
| Login errors are replaced with one message | Core otherwise says whether the username or the password was wrong |
| Application passwords are disabled | They authenticate over Basic auth on any request, never expire, and bypass 2FA |
The X-Pingback header is removed | Nothing to advertise, the XML-RPC methods are filtered out |
The one error let through is the login limiter lockout, which names no account and has to reach the person it is stopping.
Security Headers
Section titled “Security Headers”instance-security-headers sends a baseline on front end, admin and login responses:
| Header | Value |
|---|---|
X-Content-Type-Options | nosniff |
Referrer-Policy | strict-origin-when-cross-origin |
X-Frame-Options | SAMEORIGIN |
Cross-Origin-Opener-Policy | same-origin |
X-Permitted-Cross-Domain-Policies | none |
Strict-Transport-Security | max-age=31536000; includeSubDomains, over HTTPS only |
There is deliberately no Content-Security-Policy: a useful policy depends on the embeds, analytics and form scripts a given site loads, so it belongs in the project.
add_filter('instance_security_headers', function (array $headers): array { $headers['Content-Security-Policy'] = "default-src 'self'; img-src 'self' data:;";
return $headers;});Disabled Core REST API
Section titled “Disabled Core REST API”instance-disable-rest-api unregisters the namespaces WordPress core ships, so their routes 404 instead of 401, and removes REST and oEmbed discovery from <head>, the HTTP headers and RSD.
Unregistered by default: wp/v2, wp-block-editor/v1, oembed/1.0, batch/v1.
Left alone:
wp-site-health/v1— capability gated, and the Site Health screen needs it- your own namespaces — anything the theme registers keeps working
Hand one back when something needs it:
add_filter('instance_disabled_rest_namespaces', function (array $namespaces): array { return array_diff($namespaces, ['oembed/1.0']);});
