The wp-config.php file holds your database password and secret keys, and a few lines in it can turn off the admin file editor, rotate every session, and keep debug logs out of public view. On our test site, setting DISALLOW_FILE_EDIT removed the plugin and theme editors and returned a 403 to anyone who requested them directly. This guide covers the settings worth adding, every one verified on a WordPress 7.1.3 install, plus the file-copy mistake that leaks the whole file as plain text.
Lock the admin file editor
Add define( 'DISALLOW_FILE_EDIT', true ); to stop anyone, including an attacker who gets into wp-admin, from editing plugin and theme code through the dashboard. The built-in editor lets an administrator rewrite PHP files from the browser, which is exactly what an attacker wants after stealing an admin session.
On our test site, before the constant an admin could open plugin-editor.php (HTTP 200). After adding it and clearing the PHP cache, the same page returned 403 and the editor links disappeared from the admin menu (test T1b). Checking the capabilities directly, the admin lost edit_plugins and edit_themes but kept install_plugins and update_core, so updates still work; only the code editor is gone.
A caveat we hit: PHP’s opcache served the old config for a moment, so the change looked like it hadn’t taken until the cache refreshed. On a live site this clears on its own quickly; if you’re testing, reload PHP to see it.
Turn off all file modifications (stronger, with a trade-off)
For a site where plugins and themes are managed outside the dashboard, define( 'DISALLOW_FILE_MODS', true ); blocks every file change from the admin, not just code editing. It’s stronger than DISALLOW_FILE_EDIT, but it has a cost worth knowing.
On our test site, with this constant on, the admin lost install_plugins, update_plugins, delete_plugins, install_themes, update_core, and update_languages; plugin-install.php returned 403 (test T2). The install and update screens simply vanish.
The trade-off: it also stops automatic background updates, security releases included. In the WordPress 7.1.3 source, the automatic updater is disabled whenever file modifications aren’t allowed (WP_Automatic_Updater::is_disabled() checks wp_is_file_mod_allowed(), which returns false when DISALLOW_FILE_MODS is set). So use this only where something else keeps the site updated, such as deployment from version control or a managed host. On an ordinary site it does more harm than good, because it silently turns off the security updates that matter most.
Rotate your secret keys to force everyone out
The salts and secret keys in wp-config.php sign the cookies that keep users logged in, and changing them logs every user out at once. That’s your emergency “log everyone out” button after a compromise or a shared-password scare.
On our test site, an admin with a valid session cookie got the dashboard (HTTP 200). We ran wp config shuffle-salts, which replaces the eight keys, and after the cache cleared the same cookie was bounced to the login page with reauth=1 (test T3). Every existing session, including an attacker’s, is invalidated; everyone signs in again.
If you don’t have WP-CLI, generate a fresh set from the official secret-key service and paste them over the eight define() lines for AUTH_KEY through NONCE_SALT. Any install that still has the sample 'put your unique phrase here' placeholders is effectively unsigned; replace them immediately.
Keep debug logging out of public view
If you turn on debug logging, send the log somewhere the public can’t read it, because the default location is web-accessible and logs leak paths and sometimes secrets. Developers enable WP_DEBUG_LOG to capture errors, and by default WordPress writes them to wp-content/debug.log.
We reproduced the exposure (test T4). With WP_DEBUG, WP_DEBUG_LOG true, and WP_DEBUG_DISPLAY false, a test warning was written to wp-content/debug.log, and a plain browser request to https://sec.test/wp-content/debug.log returned it with HTTP 200, file path and all. Anyone who guesses that URL reads your errors.
Two fixes. Keep display off in production, which the settings above already do, so errors never show on the page:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', '/var/log/wordpress/debug.log' ); // a path outside the web root
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );
Pointing WP_DEBUG_LOG at a path outside the web root is the real fix. On our test, after moving the log out of the site folder and deleting the old one, the public URL wp-content/debug.log returned a 301 (nothing there to serve) while logging still worked to the new location (test T5). On a live site, turn debug logging off entirely once you’re done; leave it on only while actively debugging.
Don’t leave readable copies of wp-config.php
Never leave a backup or editor copy of wp-config.php in the site folder, because the server runs .php but serves anything else as text, so wp-config.php.bak hands out your database credentials. The live file is safe: the server executes it and the browser gets nothing. A copy with any other extension is not.
We tested the common editor and backup names (test T6). The real wp-config.php returned 200 with DB_NAME not visible (it ran as code). But wp-config.php.bak, wp-config.php~, wp-config.old, and wp-config.php.save each returned 200 with DB_NAME in plain text. An attacker trying those names reads your entire configuration: database host, user, password, and secret keys.
The fix is to not create such files, and to block them at the server as a backstop. One rule covers every copy except the real file:
# nginx: refuse any wp-config.* that isn't wp-config.php
location ~* /wp-config\.(?!php$) { deny all; }
# Apache .htaccess: same idea
<FilesMatch "^wp-config\.(?!php$)">
Require all denied
</FilesMatch>
With one rule each, the real file still loaded (200) while every copy returned 403 on both servers (test T6c).
Moving wp-config.php above the web root
WordPress will read wp-config.php from one directory above your site folder, which puts it outside the web root entirely, where no misconfiguration can ever serve it. It’s an optional extra step, with one condition.
The condition: WordPress uses the parent-directory copy only when there’s no wp-config.php in the site folder and the parent directory holds a wp-config.php but no wp-settings.php (wp-load.php checks for exactly that, so the parent folder must not itself be another WordPress root). On our first test the move failed with a 503 because the lab’s config used a relative path for its database; once we made that path absolute, moving the file to the parent directory worked, the site returned 200, and requesting wp-config.php in the web root returned 404 because it was no longer there (test T7b).
This is worth doing on a single-site server you control. On shared hosting where the parent directory is shared or inaccessible, skip it; the file-copy rule above already closes the realistic leak.
Frequently Asked Questions
What does DISALLOW_FILE_EDIT do in wp-config.php?
DISALLOW_FILE_EDIT removes the dashboard’s plugin and theme code editor, the tool an attacker uses to rewrite your PHP after stealing an admin session. On our test site, adding define( ‘DISALLOW_FILE_EDIT’, true ); returned a 403 for the editor and took it off the menu, while plugin installs and updates still worked. It’s safe on every site and one of the highest-value single lines you can add.
Does DISALLOW_FILE_MODS stop WordPress updates?
Yes, including automatic security updates, so use it carefully. It blocks every file change from the admin: installing and updating plugins and themes, and core updates. In the WordPress source, the automatic updater is disabled whenever file modifications aren’t allowed. Only set it where updates are handled another way, such as deployment from version control or a managed host; on an ordinary site it does more harm than good.
Where should WordPress debug logs be stored?
Outside the web root, never the default wp-content/debug.log. On our test site that default file was downloadable at its public URL, exposing paths and errors to anyone. Point WP_DEBUG_LOG at a path outside the site folder and keep WP_DEBUG_DISPLAY false in production, so errors are logged privately and never shown on the page. Turn debug logging off entirely once you’ve finished debugging.
Can someone read my wp-config.php file?
Not the live file: the server runs it as PHP and the browser gets nothing. But a copy with another extension is served as plain text, so a wp-config.php.bak or wp-config.old hands over your database password and secret keys. We confirmed this on the test site. Don’t leave such copies in the site folder, and add one server rule denying any wp-config file that isn’t wp-config.php.
The short list of wp-config.php settings
If you add nothing else to wp-config.php, add these:
DISALLOW_FILE_EDIT: removes the dashboard code editor. Safe on every site.- Fresh secret keys: replace any placeholder salts, and rotate them to force a global logout after trouble.
- Debug display off in production, and if you log, log outside the web root.
- No
.bak,.old, or~copies ofwp-config.phpin the site folder, plus the one server rule as a backstop.
Add DISALLOW_FILE_MODS only where updates are handled elsewhere, and move the file above the web root if you control the server. These settings protect the file that protects everything else; file permissions on that same file are the next layer, covered in WordPress file permissions. For the full pass, see the full WordPress security checklist.