Skip to content

WordPress core files generate errors when opened directly

Warning Whole domain Security

General instructions. Open this article from your PageCheck report to see it filled in with your site's address and the rules for its web server.

What it is

Some of WordPress's core files, such as wp-settings.php and the files in wp-includes/, are meant to be loaded only by WordPress itself. Opened directly in a browser, they run and stop with a PHP error.

What is wrong

Each time one of these core files is opened, it generates an error. If error display is on, the error is also shown on the page with the full path of the site's folder on the server, which tells attackers how the server is laid out.

Why it must be fixed: anyone — bots or someone with bad intent — can open these files over and over. Every request runs PHP and writes another error to the server's log, so the logs can be bloated on purpose: they fill the disk and bury the errors that matter. Each answer also confirms that the site runs WordPress.

How to fix

1. Hide PHP errors on the live site. Errors should go to a private log, never to the page. In the hosting control panel, set display_errors to Off:

  • Plesk: Websites & Domains → PHP Settings → display_errors off.
  • cPanel: MultiPHP INI Editor → display_errors Disabled.

In wp-config.php, keep debugging on but send every error only to a log file outside the site's public folder, so nothing reaches the page and nobody can download the log:

wp-config.php

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_DISPLAY', false );
define( 'WP_DEBUG_LOG', '/path/outside/the/web/root/logs/wp-debug.log' );
@ini_set( 'display_errors', '0' );

These lines do not cover the files this check opens: opened directly, they stop before WordPress reads wp-config.php, so the hosting setting above is what hides their errors. Rotate the log (for example daily, keeping a few weeks): with debugging on it also collects notices and can grow quickly.

2. Close WordPress core files to direct requests. Use the rules for your web server (your host can tell you which one it is):

Apache or LiteSpeed: in the .htaccess file in the site's main folder, above the "# BEGIN WordPress" line

# BEGIN Block WordPress internal files
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/(?!ms-files\.php$)[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
<Files "wp-settings.php">
    Require all denied
</Files>
# END Block WordPress internal files

nginx: in the site's server block, above the block that handles .php files

location ~* ^/wp-admin/includes/ { deny all; }
location ~* ^/wp-includes/(?!ms-files\.php$)[^/]+\.php$ { deny all; }
location ~* ^/wp-includes/js/tinymce/langs/.+\.php$ { deny all; }
location ~* ^/wp-includes/theme-compat/ { deny all; }
location = /wp-settings.php { deny all; }

These are the rules from WordPress.org's hardening guide, with one change: they leave wp-includes/ms-files.php open. WordPress Multisite networks set up before WordPress 3.5 serve their uploaded files through that one file, and the guide's advice for Multisite — leaving out the whole wp-includes/*.php line — would open every other file there again. On a single site the file only answers "Multisite support not enabled". If WordPress is in a subfolder, put that folder in RewriteBase and the nginx paths.

3. Check it. Open https://example.com/wp-includes/theme-compat/embed.php: it should now answer "Forbidden" (403) with no error text. Open a few pages of the site and the admin area to see that they still work, then run this check again.

Stop WordPress's core PHP files on my site from running and generating errors when opened directly.
Why it matters: anyone (bots included) can request these core files repeatedly, and every request writes a PHP error to the server log, so the logs can be bloated on purpose.

Steps:
1. Turn display_errors off for the live site (hosting PHP settings or php.ini). In wp-config.php keep WP_DEBUG true, set WP_DEBUG_DISPLAY false, and point WP_DEBUG_LOG to a file outside the web root (not wp-content/debug.log), with log rotation. Do not set WP_DEBUG to false.
2. Deny direct HTTP requests to wp-admin/includes/, wp-includes/*.php, wp-includes/js/tinymce/langs/*.php, wp-includes/theme-compat/ and wp-settings.php — WordPress.org's hardening rules in .htaccess on Apache/LiteSpeed (above # BEGIN WordPress), or deny location blocks placed before the PHP handler on nginx. Exempt only wp-includes/ms-files.php (Multisite networks from before WordPress 3.5 serve uploads through it): use ^wp-includes/(?!ms-files\.php$)[^/]+\.php$ rather than dropping the wp-includes/*.php rule.
3. Verify those addresses answer 403 with no error text, and that the site, the admin area, the block editor and uploads still work.
Tell me exactly which files you changed.

Resources:
- Hardening WordPress, securing wp-includes (WordPress.org): https://developer.wordpress.org/advanced-administration/security/hardening/#securing-wp-includes
- Debugging in WordPress (WordPress.org): https://developer.wordpress.org/advanced-administration/debug/debug-wordpress/
- display_errors (PHP manual): https://www.php.net/manual/en/errorfunc.configuration.php#ini.display-errors

Check your site to see whether it has this problem. Check a website