Guide

Your wp-config.php backup is public. Here’s how to fix it.

WordPress runs wp-config.php as PHP, so visitors never see what’s inside. A copy with any other ending — wp-config.php.bak, .old, .save, wp-config.php~ — isn’t PHP to the web server, so it is sent as plain text: the database name, user and password, the eight security keys and salts, and the table prefix.

Where the copies come from

DotenvScan checks /wp-config.php.bak, the most common one, and /wp-config.php itself. The steps below deal with all of them.

If wp-config.php itself is exposed

Normally a request for /wp-config.php returns an empty page, because PHP runs the file — DotenvScan shows that as Runs as PHP. If the scan shows it as Exposed, PHP isn’t handling .php files at that address, and the server is sending the source instead. It usually follows a PHP upgrade or a server move: PHP-FPM stopped, a removed PHP module, or a virtual host missing its PHP handler. Every .php file on the site is being sent as source at that point, not just this one.

Block the file at once with the rule in step 1 below — it refuses wp-config.php as well as its copies — then fix the PHP handler, and carry on from step 2: the database password and the keys and salts are leaked.

1. Delete the copies and block the pattern

List every copy on the server. Keep wp-config.php itself and wp-config-sample.php, which holds no secrets:

find /var/www -type f -name '*wp-config*' ! -name 'wp-config.php' ! -name 'wp-config-sample.php'

Delete what it finds — WordPress only ever reads wp-config.php — then make the web server refuse any future copy. On nginx, above the location ~ \.php$ block:

# wp-config.php and every copy of it (.bak, .old, .save, ~, .swp …)
location ~* /\.?wp-config\. {
    deny all;
}

On Apache, in the virtual host or .htaccess:

# wp-config.php and every copy of it (.bak, .old, .save, ~, .swp …)
<FilesMatch "^\.?wp-config\.">
    Require all denied
</FilesMatch>

Both rules leave posts whose address merely contains “wp-config” alone. If you’re rebuilding the whole .htaccess, the WPSalt .htaccess generator writes WordPress’s rewrite rules with hardening options; add the rule above to it. For .env files, .git and other backups, add the rules from the main guide too.

2. Change the database password

The leaked password may work in your host’s phpMyAdmin, from another account on a shared server, or anywhere else it was reused. Change it, then update DB_PASSWORD in wp-config.php straight away — the site is down until both match:

-- MySQL 5.7+ / MariaDB 10.2+; use the DB_USER value from wp-config.php
ALTER USER 'wp_user'@'localhost' IDENTIFIED BY 'a-new-long-random-password';

On cPanel, Plesk and similar panels, change it under the panel’s database users instead.

3. Replace the keys and salts

The eight constants — AUTH_KEY, SECURE_AUTH_KEY, LOGGED_IN_KEY, NONCE_KEY and their four _SALT partners — sign WordPress’s login cookies. Together with database access, they are enough to forge a logged-in session without knowing anyone’s password.

Generate eight fresh values with the WPSalt wp-config salts generator — it runs in your browser, with no request to api.wordpress.org — and paste them over the old block in wp-config.php. With WP-CLI, wp config shuffle-salts does the same. Everyone is signed out, you included; WPSalt explains when and why to change WordPress salts.

4. Look for what an intruder may have changed

Database access is full control of the site. Before you call it done, check for:

If you find any of these, the site is compromised, not just exposed. A cleanup that misses one backdoor gets reinfected: see the places WordPress cleanups usually miss, or have WP Server Guard audit and clean the site.

5. Stop it happening again

Then scan again. To find every wp-config.php and backup copy across all accounts on a server, run ServerSecretVault on the server itself.

Scan your WordPress site