2026年11月14日
2026年11月14日
Secure WordPress wp-config.php: Salts, Debug, Permissions, Access
This article is a localized edition of the Japanese original. Read the Japanese original
wp-config.php holds your database username and password and the secret keys that protect login cookies. If its contents leak, attackers can get into the database or forge logged-in sessions. This article walks through the settings to review in wp-config.php and how to protect the file itself.
The steps were tested on WordPress 7.1, WP-CLI 2.12 and Apache 2.4 as of November 2026. Before you start, save a copy of wp-config.php outside the document root.
Update the secret keys (authentication keys and salts)
wp-config.php contains the following eight constants, which are used to encrypt and verify login cookies.
<?php
define( 'AUTH_KEY', 'put a unique string here' );
define( 'SECURE_AUTH_KEY', 'put a unique string here' );
define( 'LOGGED_IN_KEY', 'put a unique string here' );
define( 'NONCE_KEY', 'put a unique string here' );
define( 'AUTH_SALT', 'put a unique string here' );
define( 'SECURE_AUTH_SALT', 'put a unique string here' );
define( 'LOGGED_IN_SALT', 'put a unique string here' );
define( 'NONCE_SALT', 'put a unique string here' );
If the values are still the default (put your unique phrase here) or are shared with another site, replace them. There are two ways.
- Open the WordPress.org generator at https://api.wordpress.org/secret-key/1.1/salt/ and replace the existing eight lines with the eight lines it shows. It generates new values every time you open it.
- If you have WP-CLI, this command replaces all eight values at once.
cd /var/www/html
wp config shuffle-salts
Changing the keys invalidates every logged-in user's cookie, so everyone is logged out. When you suspect unauthorized access (a leaked password, a compromised account and so on), update the keys along with the passwords. On sites where customers stay logged in, do it at a quiet time.
Do not display errors in production
Errors shown on screen in production reveal file paths on the server and your plugin setup to outsiders. Use these settings in production.
<?php
// Production: do not display errors
define( 'WP_DEBUG', false );
define( 'WP_DEBUG_DISPLAY', false );
When you need a log temporarily for troubleshooting, write it to a file instead of the screen. Setting WP_DEBUG_LOG to true writes to wp-content/debug.log, but that location can be opened by URL. Give a file path instead so the log is written outside the document root.
<?php
// Only while troubleshooting: log outside the document root
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_DISPLAY', false );
define( 'WP_DEBUG_LOG', '/var/log/wordpress/debug.log' ); // Use a directory the web server can write to
When you are done, set WP_DEBUG back to false. For details, see how to turn off WP_DEBUG in production.
Security-related constants
Disable file editing in the admin (DISALLOW_FILE_EDIT)
<?php
define( 'DISALLOW_FILE_EDIT', true );
This disables Appearance → Theme File Editor and Plugins → Plugin File Editor. If an administrator account is taken over, the attacker cannot rewrite PHP code from the admin. In our test, administrators could still update plugins (the update_plugins capability) after setting it.
It does not stop attacks that upload files, though. The official WordPress documentation also describes it as something that "might stop some attacks."
Block installing and updating plugins and themes (DISALLOW_FILE_MODS)
<?php
define( 'DISALLOW_FILE_MODS', true );
This blocks installing, updating and deleting plugins and themes from the admin. The catch is that automatic updates of WordPress core, including security releases, also stop. In our test, adding this constant made the automatic updater report itself as disabled.
If you use it, set up your own way to apply updates, for example by running WP-CLI from cron on a schedule.
cd /var/www/html
wp core update --minor # Apply only minor (security) core updates
wp plugin update --all
On sites that do not manage updates through Git-based deployment or similar, it is safer to use only DISALLOW_FILE_EDIT rather than DISALLOW_FILE_MODS.
Require HTTPS for the admin and login (FORCE_SSL_ADMIN)
<?php
define( 'FORCE_SSL_ADMIN', true );
This redirects the admin and login screens from HTTP to HTTPS and sends login cookies only over HTTPS. Confirm that your site works over HTTPS before enabling it. On servers behind a load balancer or CDN, WordPress may not detect that the request arrived over HTTPS, which can cause a redirect loop. In that case, review the server-side configuration as well.
Table prefix ($table_prefix)
The default $table_prefix is wp_. The official documentation says changing it can block some SQL injection attacks that assume wp_. The effect is limited, however, and changing it does not make a site secure by itself. It is easiest to change at install time; on an existing site you also have to rename the tables and update the prefixed values in the options and usermeta tables.
Keep the file from being read over the web
Deny access in Apache
Because wp-config.php runs as PHP, opening its URL normally shows nothing (our test returned an empty page). But if PHP stops working, for example after a server misconfiguration, the contents could be served as plain text. Deny web access to be safe.
On Apache 2.4, add the following at the top of the .htaccess in your WordPress directory (before # BEGIN WordPress).
<Files "wp-config.php">
Require all denied
</Files>
In our test, requests to /wp-config.php returned 403 afterward and the site worked as usual. Order allow,deny and Deny from all are Apache 2.2 syntax; on Apache 2.4 use Require all denied.
On Nginx, add this to the server block.
location = /wp-config.php {
deny all;
}
Do not leave backup copies behind
Copies such as wp-config.php.bak, wp-config.php.old or an editor's wp-config.php~ do not end in .php, so they are not run as PHP and are served as text. In our test, a wp-config.php.bak file exposed its full contents, password included. Do not keep copies of wp-config.php in the document root.
# Find copies left in the document root
find /var/www/html -maxdepth 1 -name 'wp-config*' ! -name 'wp-config.php' ! -name 'wp-config-sample.php'
Set permissions and ownership
The official WordPress documentation recommends making wp-config.php readable only by you and the web server, which generally means 400 or 440. Which one you need depends on who owns the file and which user runs PHP.
| Situation | Owner:group | Permissions |
|---|---|---|
| PHP runs as the same user that owns the files (most shared hosting) | Your user | 400 or 440 |
| PHP runs as the web server user (www-data and so on) and another user owns the files | Your user:www-data | 440 |
In our test with root as owner and www-data as group, the site loaded with 440, while with 400 PHP could not read wp-config.php and returned an error (HTTP 500).
# Example: owner deploy, group www-data, mode 440
sudo chown deploy:www-data /var/www/html/wp-config.php
sudo chmod 440 /var/www/html/wp-config.php
ls -l /var/www/html/wp-config.php
With 440, neither WordPress nor plugins can modify wp-config.php. When you install something that writes to wp-config.php during setup, such as a caching plugin, allow writing temporarily or add the lines yourself.
For everything else, the baseline is 755 for directories and 644 for files. See how to set WordPress file permissions correctly for the overall approach.
Move wp-config.php up one level
If wp-config.php is not in the WordPress directory, WordPress looks in the directory above it. With a document root of /var/www/html, placing it at /var/www/wp-config.php works.
mv /var/www/html/wp-config.php /var/www/wp-config.php
In our test, the site still loaded after the move and the /wp-config.php URL returned 404. Keep the following in mind, though.
- It is not loaded if the parent directory contains
wp-settings.php(that is, another WordPress install lives there). - If WordPress is in a subdirectory (for example
/var/www/html/blog), the parent is/var/www/html, still inside the document root, so the move gives no protection from the web. - The official documentation notes that some people consider the benefit of moving it minimal. Set permissions and deny access first.
Checklist
- The secret keys are not the defaults
- In production,
WP_DEBUGisfalseandWP_DEBUG_DISPLAYisfalse - No
wp-content/debug.logis left behind (delete it if there is one) DISALLOW_FILE_EDITistrue- Direct requests to
/wp-config.phpare denied - There are no copies of
wp-config.phpin the document root wp-config.phpis 400 or 440 and the site works normally
Related articles
- How to rotate WordPress secret keys
- How to block access to wp-config.php
- List of wp-config.php settings
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)