Found by Claude.AI Opus 4.7:
Bug: xmlrpc.php deny rule in WordPress nginx config is unreachable due to location ordering
Summary
The WordPress app's nginx configuration contains a location block intended to deny public access to xmlrpc.php, but the rule never fires. Requests to /xmlrpc.php reach PHP-FPM and are handled by WordPress (which responds with 405 Method Not Allowed for GET, and would happily process POST), defeating the intended hardening.
Affected file
/etc/nginx/conf.d/<domain>.d/wordpress.conf (installed by the WordPress YunoHost package)
Reproduction
- Install the WordPress app on a YunoHost instance.
- Request
https://<domain>/xmlrpc.php:
curl -i https://<domain>/xmlrpc.php
- Expected:
403 Forbidden (per the # Deny public access to xmlrpc.php rule).
- Actual:
405 Method Not Allowed (response from WordPress, i.e. the request was passed to PHP-FPM).
Root cause
In wordpress.conf, inside location / { ... }, the generic PHP handler is declared before the xmlrpc deny rule:
location / {
...
location ~ [^/]\.php(/|$) { # ← matches xmlrpc.php first
fastcgi_pass unix:/var/run/php/phpX.Y-fpm-wordpress.sock;
include fastcgi_params_no_auth;
}
...
location ~* xmlrpc\.php { # ← never reached
deny all;
return 403;
}
}
For regex (~ / ~*) location blocks, nginx uses the first match in the order they are declared, not the most specific one. Since [^/]\.php(/|$) matches xmlrpc.php, the deny block is unreachable.
The same bug exists in the wordpress.conf snippets shipped for related WordPress variants (e.g. wordpress__2.conf, wordpress__3.conf), where the rule additionally uses an unescaped dot (xmlrpc.php instead of xmlrpc\.php) — minor, but worth fixing while there.
Suggested fix
Move the xmlrpc deny block (and similarly wp-config.php) above the generic PHP handler, and escape the dot:
location / {
...
client_max_body_size 512M;
# Deny public access to xmlrpc.php
location ~* xmlrpc\.php {
deny all;
return 403;
}
# Deny public access to wp-config.php
location ~* wp-config\.php {
deny all;
return 403;
}
location ~ [^/]\.php(/|$) {
fastcgi_pass unix:/var/run/php/phpX.Y-fpm-wordpress.sock;
include fastcgi_params_no_auth;
}
...
}
After reload (nginx -t && systemctl reload nginx), curl -i https://<domain>/xmlrpc.php correctly returns 403.
Impact
- Hardening that operators (and the YunoHost docs) believe is in place is silently ineffective.
- xmlrpc.php remains reachable for brute-force / pingback amplification attacks against every WordPress instance installed via the package.
Environment
- YunoHost WordPress app (current Debian-packaged version)
- nginx (any version — this is a config-ordering issue, not version-specific)
- Reproduced on production instance running PHP 8.5-FPM
Found by Claude.AI Opus 4.7:
Bug:
xmlrpc.phpdeny rule in WordPress nginx config is unreachable due to location orderingSummary
The WordPress app's nginx configuration contains a
locationblock intended to deny public access toxmlrpc.php, but the rule never fires. Requests to/xmlrpc.phpreach PHP-FPM and are handled by WordPress (which responds with405 Method Not Allowedfor GET, and would happily process POST), defeating the intended hardening.Affected file
/etc/nginx/conf.d/<domain>.d/wordpress.conf(installed by the WordPress YunoHost package)Reproduction
https://<domain>/xmlrpc.php:403 Forbidden(per the# Deny public access to xmlrpc.phprule).405 Method Not Allowed(response from WordPress, i.e. the request was passed to PHP-FPM).Root cause
In
wordpress.conf, insidelocation / { ... }, the generic PHP handler is declared before the xmlrpc deny rule:For regex (
~/~*)locationblocks, nginx uses the first match in the order they are declared, not the most specific one. Since[^/]\.php(/|$)matchesxmlrpc.php, the deny block is unreachable.The same bug exists in the
wordpress.confsnippets shipped for related WordPress variants (e.g.wordpress__2.conf,wordpress__3.conf), where the rule additionally uses an unescaped dot (xmlrpc.phpinstead ofxmlrpc\.php) — minor, but worth fixing while there.Suggested fix
Move the xmlrpc deny block (and similarly
wp-config.php) above the generic PHP handler, and escape the dot:After reload (
nginx -t && systemctl reload nginx),curl -i https://<domain>/xmlrpc.phpcorrectly returns403.Impact
Environment