
How to harden wp-config.php for better security?
- Sajjad
- WordPress, Security
- 06 Sep, 2026
To harden wp-config.php, you restrict who can read it, block direct web access to it, move it or its secrets out of the public web root where possible, use strong unique security keys, and add security-focused constants such as DISALLOW_FILE_EDIT and FORCE_SSL_ADMIN. Because this single file holds your database credentials and authentication salts, protecting it is one of the highest-value hardening steps you can take for a WordPress site.
This guide focuses specifically on the security side of wp-config.php. You'll learn why the file is such an attractive target, how to lock it down at the file system and web server level, which constants improve security, how to keep secrets out of the file entirely, and how to verify that your changes work. Every change here touches a critical file, so take a backup before you start.
Why Is wp-config.php a Target?
wp-config.php is the configuration file WordPress loads on every request. It typically contains:
- Database credentials: The database name, username, password, and host.
- Authentication keys and salts: Used to sign cookies and nonces.
- Table prefix: The prefix for all WordPress tables.
- Configuration constants: Debug settings, file editing, SSL, memory limits, and more.
If an attacker can read this file, they get your database password and can potentially read or change everything on your site. If they can write to it, they can inject code that runs on every page load. That makes wp-config.php both a high-value read target (for information disclosure bugs) and a high-value write target (for persistence after a compromise).
Common ways the file gets exposed include:
- A PHP handler failure that serves
.phpfiles as plain text. - Backup copies like
wp-config.php.bak,wp-config.old, orwp-config.php~left in the web root, which the server serves as text. - Path traversal or arbitrary file read vulnerabilities in plugins.
- Overly permissive file permissions on shared hosting.
Before You Start: Back Up
Mistakes in wp-config.php can take your whole site offline. Before editing:
-
Download a copy of your current
wp-config.phpvia SFTP or your host's file manager. -
Take a full site backup (files and database) using your host or a backup plugin.
-
Make sure you have SFTP or SSH access, so you can revert changes if the dashboard stops loading.
After each change, reload your site and the dashboard to confirm everything still works.
Step 1: Set Restrictive File Permissions
File permissions control which system users can read and write the file. The goal is that only the account that needs to read wp-config.php can do so, and nobody else on the server can.
Common recommendations:
640: Owner can read and write, group can read, others have no access. Good when the web server's group needs read access.600: Only the owner can read and write. Good when PHP runs as the same user that owns the files (common with PHP-FPM pools per site).440or400: Read-only, even for the owner. The tightest option, but you'll need to change permissions temporarily whenever you edit the file.
Set permissions via SSH:
cd /var/www/example.com/public_html
chmod 640 wp-config.php
ls -l wp-config.php
The output should look something like:
-rw-r----- 1 deploy www-data 3412 Sep 6 10:12 wp-config.php
The right choice depends on how PHP runs on your server. If your site shows a database connection error after changing permissions, PHP can no longer read the file. Switch back to 644 temporarily and check with your host which user and group PHP runs as. Never set wp-config.php to 777 or 666.
On managed WordPress hosts, you may not be able to change permissions, and the host usually handles this for you.
Step 2: Block Direct Web Access to wp-config.php
Under normal conditions, requesting wp-config.php in a browser executes the PHP and returns a blank page. But if PHP ever fails and the server serves the file as text, your credentials would be exposed. Blocking direct requests adds a second layer.
Apache
Add this to the .htaccess file in your WordPress root, outside the # BEGIN WordPress and # END WordPress markers so WordPress doesn't overwrite it:
<Files "wp-config.php">
Require all denied
</Files>
Nginx
Add this inside your site's server block, before the general PHP location block:
location = /wp-config.php {
deny all;
}
Then test and reload:
sudo nginx -t && sudo systemctl reload nginx
Block Backup Copies Too
Editors and some tools leave backup files behind. These are the real danger, because files ending in .bak or .old aren't executed as PHP and will be served as plain text. First, remove any you find:
find /var/www/example.com -maxdepth 2 -name "wp-config*" -not -name "wp-config.php" -not -name "wp-config-sample.php"
Then block common backup and swap file patterns. On Apache:
<FilesMatch "(^wp-config\.php\..*|\.(bak|old|orig|save|swp|swo)$|~$)">
Require all denied
</FilesMatch>
On Nginx:
location ~* (^/wp-config\.php\..+|\.(bak|old|orig|save|swp|swo)$|~$) {
deny all;
}
Test by requesting the files after the change. You should get 403 Forbidden:
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-config.php
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-config.php.bak
Step 3: Move wp-config.php Above the Web Root
WordPress has a built-in feature: if it can't find wp-config.php in the WordPress directory, it looks one directory up. That means you can move the file out of the publicly accessible folder.
For example, if WordPress lives in /var/www/example.com/public_html/, you can move the file to /var/www/example.com/wp-config.php:
mv /var/www/example.com/public_html/wp-config.php /var/www/example.com/wp-config.php
A few important caveats:
- This only works if the parent directory doesn't also contain its own WordPress installation (WordPress checks that there's no
wp-settings.phpalongside the moved file). - It only helps if the parent directory is outside the web root. If your web root is the parent directory, moving the file up gains nothing.
- Some hosts restrict PHP's
open_basedirso it can't read files outside the web root. If the site breaks, move it back.
Alternative: A Stub That Loads the Real Config
If WordPress lives in a subdirectory, or the parent-directory trick doesn't fit your setup, you can keep a minimal wp-config.php that loads the real file from a private location:
<?php
// wp-config.php in the web root: loads the real configuration from outside it.
require_once '/var/www/example.com/private/wp-config-real.php';
The real file must still define ABSPATH and require wp-settings.php at the end, just like a normal wp-config.php:
<?php
// ... database settings, keys, constants ...
if ( ! defined( 'ABSPATH' ) ) {
define( 'ABSPATH', '/var/www/example.com/public_html/' );
}
require_once ABSPATH . 'wp-settings.php';
Step 4: Use Strong, Unique Keys and Salts
WordPress uses eight constants to sign authentication cookies and nonces:
define( 'AUTH_KEY', 'put your unique phrase here' );
define( 'SECURE_AUTH_KEY', 'put your unique phrase here' );
define( 'LOGGED_IN_KEY', 'put your unique phrase here' );
define( 'NONCE_KEY', 'put your unique phrase here' );
define( 'AUTH_SALT', 'put your unique phrase here' );
define( 'SECURE_AUTH_SALT', 'put your unique phrase here' );
define( 'LOGGED_IN_SALT', 'put your unique phrase here' );
define( 'NONCE_SALT', 'put your unique phrase here' );
If yours still say "put your unique phrase here," or you suspect they've been exposed, replace them. You can generate fresh values from the official WordPress.org secret key service at https://api.wordpress.org/secret-key/1.1/salt/ and paste them in, or use WP-CLI:
wp config shuffle-salts
Changing the salts logs out every user, which is exactly what you want after a suspected compromise. Consider rotating them periodically and always after staff with server access leave.
Step 5: Add Security Constants
Several constants in wp-config.php improve security. Add them above the line that says /* That's all, stop editing! Happy publishing. */.
Disable the Theme and Plugin File Editor
define( 'DISALLOW_FILE_EDIT', true );
This removes the built-in file editors under Appearance > Theme File Editor and Plugins > Plugin File Editor. If an attacker gets into an admin account, they can't use the editor to inject PHP.
Optionally Disable All File Modifications
define( 'DISALLOW_FILE_MODS', true );
This goes further, blocking plugin and theme installation, updates, and deletion from the dashboard. It's excellent for sites deployed from version control, but it also disables automatic plugin updates, so only use it if you have another reliable update process.
Force HTTPS for Logins and the Admin
define( 'FORCE_SSL_ADMIN', true );
If your site sits behind a reverse proxy or load balancer that terminates TLS, WordPress may not detect HTTPS correctly. In that case, add this before the constant, but only if your proxy always sets the header and strips it from client requests:
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
$_SERVER['HTTPS'] = 'on';
}
Turn Off Debug Output in Production
Debug output can reveal file paths, queries, and other information useful to attackers. On production:
define( 'WP_DEBUG', false );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', '0' );
If you need logging on production, log to a file outside the web root rather than to the default wp-content/debug.log, which is publicly accessible unless blocked:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_DISPLAY', false );
define( 'WP_DEBUG_LOG', '/var/www/example.com/logs/wp-debug.log' );
Keep Automatic Core Updates On
define( 'WP_AUTO_UPDATE_CORE', 'minor' );
'minor' is the default and ensures security releases are applied automatically. You can use true for all core updates. Avoid setting it to false unless you have another process for applying security updates promptly.
Control the Environment Type
define( 'WP_ENVIRONMENT_TYPE', 'production' );
Plugins and themes can check wp_get_environment_type() to disable debug features on production.
Block Unfiltered HTML
define( 'DISALLOW_UNFILTERED_HTML', true );
By default, administrators and editors on single-site installs can post unfiltered HTML, including scripts. This constant removes that capability, which reduces the damage a compromised editor account can do. It may break content workflows that rely on embedding raw scripts, so test it first.
Step 6: Use a Dedicated, Least-Privilege Database User
The database credentials in wp-config.php should belong to a user that only has access to this one site's database. Never use the MySQL root user. For normal operation, WordPress needs these privileges on its own database:
CREATE USER 'wp_example'@'localhost' IDENTIFIED BY 'a-long-random-password';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP ON wp_example_db.* TO 'wp_example'@'localhost';
FLUSH PRIVILEGES;
Some plugins and core updates need CREATE, ALTER, INDEX, and DROP to create or modify tables, so keep those unless you manage schema changes another way. What matters most is that the user can't access other databases and doesn't have global privileges like FILE, SUPER, or GRANT OPTION.
Use a long, random password generated by a password manager.
Step 7: Keep Secrets Out of the File With Environment Variables
A modern approach is to keep credentials out of wp-config.php entirely and read them from environment variables. This keeps secrets out of version control and makes it easy to use different values in staging and production.
<?php
function sajjad_env( $key, $default = null ) {
$value = getenv( $key );
return ( false === $value || '' === $value ) ? $default : $value;
}
define( 'DB_NAME', sajjad_env( 'WP_DB_NAME' ) );
define( 'DB_USER', sajjad_env( 'WP_DB_USER' ) );
define( 'DB_PASSWORD', sajjad_env( 'WP_DB_PASSWORD' ) );
define( 'DB_HOST', sajjad_env( 'WP_DB_HOST', 'localhost' ) );
define( 'AUTH_KEY', sajjad_env( 'WP_AUTH_KEY' ) );
define( 'SECURE_AUTH_KEY', sajjad_env( 'WP_SECURE_AUTH_KEY' ) );
define( 'LOGGED_IN_KEY', sajjad_env( 'WP_LOGGED_IN_KEY' ) );
define( 'NONCE_KEY', sajjad_env( 'WP_NONCE_KEY' ) );
define( 'AUTH_SALT', sajjad_env( 'WP_AUTH_SALT' ) );
define( 'SECURE_AUTH_SALT', sajjad_env( 'WP_SECURE_AUTH_SALT' ) );
define( 'LOGGED_IN_SALT', sajjad_env( 'WP_LOGGED_IN_SALT' ) );
define( 'NONCE_SALT', sajjad_env( 'WP_NONCE_SALT' ) );
With PHP-FPM, you can set the variables in the pool configuration file (for example /etc/php/8.3/fpm/pool.d/example.conf):
env[WP_DB_NAME] = wp_example_db
env[WP_DB_USER] = wp_example
env[WP_DB_PASSWORD] = a-long-random-password
env[WP_DB_HOST] = localhost
Restart PHP-FPM after changes with sudo systemctl restart php8.3-fpm. Pool files should only be readable by root. If you prefer a .env file, keep it outside the web root and load it with a library like vlucas/phpdotenv, which is how the Bedrock WordPress boilerplate handles configuration.
The function name uses a sajjad_ prefix because wp-config.php loads before WordPress, so you can't rely on WordPress helpers here.
Step 8: Protect the File From Unauthorized Changes
Attackers who gain a foothold often modify wp-config.php to load malicious code early. Watch for:
- Unfamiliar
includeorrequirestatements. - Long base64-encoded strings or calls to
eval(),gzinflate(), orstr_rot13(). - Code added before the opening comment block or after
require_once ABSPATH . 'wp-settings.php';.
Ways to catch changes:
- Enable file change alerts in a security plugin such as Wordfence or Solid Security.
- Keep a known-good hash and compare it on a schedule:
# Record a baseline
sha256sum /var/www/example.com/wp-config.php > /root/wp-config.sha256
# Check later (prints OK or FAILED)
sha256sum -c /root/wp-config.sha256
- If you manage the site with Git, keep a template of the file (without secrets) in the repository so differences are easy to spot.
Step 9: Verify Your Hardening
After making changes, confirm they work:
# The file should not be retrievable over the web
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-config.php
# Check constants with WP-CLI
wp config get DISALLOW_FILE_EDIT
wp config get FORCE_SSL_ADMIN
wp config get WP_DEBUG
# Confirm permissions
stat -c "%a %U:%G %n" wp-config.php
The stat -c syntax is for Linux; on macOS use stat -f "%Lp %Su:%Sg %N". Then log in to the dashboard and confirm that Appearance > Theme File Editor is gone and that the site loads normally over HTTPS.
FAQ: Hardening wp-config.php
Usually 640 or 600, depending on how PHP runs on your server. Some hosts recommend 440 or 400. Never use 666 or 777. If the site breaks after a change, check with your host which user PHP runs as.
Yes. WordPress automatically looks one directory up for wp-config.php. It only adds security if that parent directory is outside the public web root.
No, but it will log out every user, including you. Everyone simply needs to log in again. That's why rotating salts is a standard step after a suspected compromise.
It removes the built-in theme and plugin file editors from the dashboard, so someone with admin access can't use them to inject malicious PHP code.
Only if you deploy updates another way, such as through Git or a managed host. It blocks installing and updating plugins and themes from the dashboard, including automatic updates.
Yes. You can read them from environment variables set in your PHP-FPM pool or server configuration, or from a .env file stored outside the web root.
Look for unfamiliar include statements, obfuscated code, or eval calls. File change monitoring in a security plugin or a stored checksum comparison will alert you to modifications.
Conclusion
wp-config.php holds the keys to your WordPress site, so a few careful changes here go a long way. Tighten its file permissions, block direct and backup-file access at the web server, move it or its secrets outside the web root, refresh your keys and salts, and add constants like DISALLOW_FILE_EDIT, FORCE_SSL_ADMIN, and production-safe debug settings.
Pair those steps with a least-privilege database user and file change monitoring, and you've turned one of the most sensitive files on your server into one of the best protected. Always back up before editing, test after every change, and keep a copy of a known-good version so you can spot and reverse unexpected modifications quickly.


