Skip to content

fix: harden the wordpress nginx configuration - #26

Merged
vitormattos merged 1 commit into
mainfrom
fix/harden-wordpress-nginx
Nov 14, 2026
Merged

vitormattos merged 1 commit into
mainfrom
fix/harden-wordpress-nginx

Conversation

@YvesCesar

@YvesCesar YvesCesar commented Nov 14, 2026 •

Copy link
Copy Markdown
Member

What was wrong

The WordPress nginx configuration served every file in the document root and executed every .php file it found:

  • Any .php file executed, including files under wp-content/uploads, wp-content/cache, the document root and wp-includes. Only vendor folders were excluded, so a single file write became remote code execution.
  • Logs, SQL dumps, archives, backups, editor leftovers, dotfiles (other than .ht* and .git*), composer.lock, package.json, phpunit.xml.dist and the tests folders of the cloned plugins were served as static files. Folders that plugins protect only with .htaccess (backups, wc-logs, woocommerce_uploads) were readable because nginx ignores .htaccess.
  • xmlrpc.php answered requests, although WORDPRESS_XMLRPC_ENABLED: 0 suggested otherwise.
  • client_max_body_size 0 removed the request size limit, so any request body was buffered to disk whatever its size.

What changed

  • PHP runs only for WordPress entry points: index, wp-login, wp-cron, wp-comments-post, wp-signup, wp-activate, wp-trackback and wp-links-opml in the root, wp-admin/*.php, wp-admin/network/*.php, wp-admin/user/*.php and wp-includes/js/tinymce/wp-tinymce.php. Any other .php, .php[0-9], .phtml, .phar or .pht path, in any letter case, returns 404. This covers the whole wp-content.
  • Denied with 403:
    • files ending in .sql, .sqlite, .bak, .old, .orig, .save, .swp, .swo, .log, .sh, .dist, .lock, .tar, .tgz, .gz, .bz2, .xz, .7z, .rar, .wpress, .env, .ini, .neon, .yml, .yaml, .md or ~;
    • composer.json, composer.lock, package.json, package-lock.json and phpunit*.xml(.dist);
    • .zip files, except in the wp-content/uploads/YYYY/MM/ media folders;
    • wp-content/debug.log, updraft, ai1wm-backups, aiowps_backups, backup*, upgrade, upgrade-temp-backup, cache and logs;
    • wp-content/uploads/wc-logs, woocommerce_uploads and debug-log-manager;
    • tests, stubs, node_modules and vendor-bin at the root of a plugin or theme, and any vendor or vendor-bin folder under wp-content, except stylesheets, scripts, images and fonts in vendor, which the theme (assets/vendor), WooCommerce and Kadence Blocks enqueue;
    • every dotfile except .well-known.
  • location = /xmlrpc.php is denied.
  • client_max_body_size is 10m, the same as post_max_size.

The same rules are proposed for the image in LibreCodeCoop/wordpress-docker#42 and LibreCodeCoop/wordpress-docker#43. Once they are merged and the submodule is bumped, this file can be dropped in favour of the image configuration plus the SaaS-specific settings in server.d.

How it was verified

  • Playwright suite on the saas-e2e stack built from scratch with this configuration: 14 passed. The nginx log of the run has no 403, 404, 413 or 5xx response.
  • Harmless test files were created in the E2E volume, requested through the main configuration and through this one, and removed afterwards:
Request main This PR
.php in uploads (body EXECUTOU on main) 200 404
.PHP and .phtml in uploads, .php in the root 200 404
.php in wp-content/cache 200 403
wp-includes/version.php, wp-admin/includes/file.php 200 / 500 404
.sql, .tar.gz and .zip in uploads, wp-content/debug.log, a backup folder, wp-config.php~, /.env 200 403
.zip in uploads/2026/11/ 200 200
plugin composer.lock, package.json, phpunit.xml.dist, tests/*.php 200 403
vendor/composer/installed.json 200 403
POST /xmlrpc.php 200 403
30 MB POST /wp-login.php 200 413
wp-login.php, wp-cron.php, wp-admin/admin-ajax.php, /wp-json/ 200 / 200 / 400 / 200 unchanged
theme and WooCommerce assets under vendor, wp-includes/js, /.well-known/ 200 200

Signed-off-by: YvesCesar <yvesamorim73@gmail.com>
@vitormattos
vitormattos merged commit b6d721b into main Nov 14, 2026
2 checks passed
@vitormattos
vitormattos deleted the fix/harden-wordpress-nginx branch November 14, 2026 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants