REST API and author enumeration hardening
Opt-in, off by default. Read the limitations below before enabling it.
When enabled, NGINX answers 403 for anonymous requests that reveal the site's login names:
- the
wp/v2/usersREST route, as/wp-json/wp/v2/usersand as the/?rest_route=/wp/v2/usersfallback that works with pretty permalinks off - the author archive addressed by numeric id,
/?author=1, which redirects to/author/<login>/and thereby maps an id to a name
Matching runs on the normalized path, so percent-encoding, duplicate slashes and . segments do not get around it. Requests carrying a WordPress session cookie are exempt — without that, the block editor's author selector and the author filter in the wp-admin posts list would break, since both request these URLs from a logged-in browser. The pretty /author/<slug>/ archive and ordinary REST author filters such as /wp-json/wp/v2/posts?author=1 stay reachable.
restApiHardening:
mode: enforce # off (default) | enforce | report
extraDeniedPaths:
- '~*^/wp-json/wp/v2/comments(/|\?)'| Parameter | Description | Default |
|---|---|---|
restApiHardening.mode | off leaves every route reachable, enforce answers denied requests from anonymous callers with 403, report only logs them | off |
restApiHardening.extraDeniedPaths | Additional NGINX map regex patterns to deny, matched against "$uri?rest_route=$arg_rest_route&author=$arg_author" | [] |
What this does not do
It does not reliably prevent username disclosure, which is why it is not a default.
The session cookie is checked for presence, not validated — NGINX cannot verify a WordPress session — so sending a made-up wordpress_logged_in_ cookie gets past the check. Beyond that, the same login names remain reachable through the pretty author archive and through the oEmbed endpoint, neither of which can be blocked without removing a legitimate public feature.
What it reliably does is stop bulk automated enumeration: a scanner will report no users found. Treat that as noise reduction, not as a boundary — and be aware that the scanner's clean report is itself misleading.
The fix that actually holds
Author archives expose user_nicename, not user_login. These are separate fields; WordPress merely derives the first from the second at registration, which is why they usually match. Set them apart and no channel exposes a login name at all — no proxy rules, nothing to bypass, nothing to break:
wp user update 1 --user_nicename=redaktionThe author archive URL changes with it, so on an indexed site this is an SEO decision as much as a security one: set up redirects, or do it before launch.
To see what enabling the NGINX rules would refuse before turning them on, set mode: report and watch the container logs:
kubectl logs deploy/<release>-wordpress-nginx | grep '\[rest-hardening\]'