Home  /  Blog  /  wp-config.php security

WordPress wp-config.php security: the settings most sites get wrong

If wp-config.php is your house, your database credentials are the key, your secret keys are the deadbolt, and the file permissions are whether the door is locked at all. After auditing several hundred WordPress sites we keep finding the same eight or nine mistakes — some inherited from old tutorials, some from one-click installers, all of them fixable in under ten minutes.

Why wp-config.php is special

The wp-config.php file is the only file in a default WordPress install that contains, in plaintext, every piece of information an attacker needs to take over your site: the database hostname, the database user, the database password, the cryptographic secrets that protect session cookies, and the configuration switches that decide what a logged-in administrator is allowed to do.

Compromise wp-config.php and you compromise the site. Every other file in WordPress is either generic (the same on every install) or recoverable from a backup. wp-config.php is the one you cannot afford to leak. And yet it is also the one that the most "WordPress for beginners" tutorials get wrong.

Secret keys and salts: the most commonly weak part

WordPress uses eight cryptographic values to sign and encrypt authentication data:

define( 'AUTH_KEY',         '...' );
define( 'SECURE_AUTH_KEY',  '...' );
define( 'LOGGED_IN_KEY',    '...' );
define( 'NONCE_KEY',        '...' );
define( 'AUTH_SALT',        '...' );
define( 'SECURE_AUTH_SALT', '...' );
define( 'LOGGED_IN_SALT',   '...' );
define( 'NONCE_SALT',       '...' );

These are what makes a stolen session cookie unusable on another site. The most common failure modes we see, in order:

  1. The placeholders were never replaced. The file still says 'put your unique phrase here' for one or more of the keys. This is usually a one-click install that did not call the WordPress.org API to fetch real values. You can verify in seconds — grep for "put your unique phrase" in your wp-config.php. If it is there, your keys are not unique.
  2. The keys are short or low-entropy. Someone generated them with a weak password generator instead of the official endpoint. The keys are supposed to be 64+ characters of high-entropy randomness.
  3. The keys have never been rotated. They were generated correctly five years ago, the site has had three different admins since, and the keys have never changed. If your admin ever leaks (laptop stolen, repository made public, hosting account compromised), the keys leak with it.

The fix is the same for all three:

  1. Visit api.wordpress.org/secret-key/1.1/salt/. The endpoint returns eight fresh, unique, high-entropy define() lines.
  2. Replace all eight in your wp-config.php.
  3. Rotating keys invalidates every existing session. Plan around a brief logout window for active users.

Rotate at least every 90 days, and immediately after any suspected compromise, admin offboarding, or hosting migration.

What Lockora's AI checks here

The audit reads your wp-config.php, checks for placeholder strings, measures the entropy of each key, and flags any value that looks reused, weak, or stale.

Database credentials: rotate them, scope them

Three rules:

  1. Never use the database root account for WordPress. Create a dedicated user (wp_yoursite) and grant it only what it needs. GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, REFERENCES ON wp_yoursite.* TO ... is the right shape. Skip SUPER, FILE, PROCESS, and anything global.
  2. Use a unique, long, random password. 32+ characters, generated by a password manager. Do not reuse it for anything else.
  3. If the database lives on a separate host, restrict the user to a specific source IP. CREATE USER 'wp_yoursite'@'10.0.1.20' is much safer than 'wp_yoursite'@'%'.

If you have ever shared wp-config.php with a developer, exported it to a backup, or committed it to a repository, the password should be considered burned. Rotate it.

$table_prefix is not security — but use it anyway

The folk wisdom that "change $table_prefix from wp_ to something random and you are protected from SQL injection" is wrong. SQL injection in modern plugins is rare, and when it happens, the attacker can introspect the database to discover whatever prefix you chose. The prefix does not protect against the actual threat.

That said: a non-default prefix raises the bar for the lowest-effort scanners that hard-code wp_options in their payloads. The benefit is marginal but real, and the cost on a fresh install is zero. Pick something like wp_3kn_ on new installs.

Do not attempt to retroactively change the prefix on an established site without a proper migration plan — it touches every plugin's option keys, and the failure modes are confusing.

DISALLOW_FILE_EDIT and DISALLOW_FILE_MODS

By default, an administrator can edit plugin and theme PHP files from the WordPress admin UI. That sounds harmless, except: if any account with the edit_themes capability is ever compromised — via password reuse, phishing, or a session token leak — the attacker has immediate, persistent, write-the-PHP-from-the-browser code execution. No file upload required.

Two constants protect against this:

define( 'DISALLOW_FILE_EDIT', true );
define( 'DISALLOW_FILE_MODS', true );

DISALLOW_FILE_EDIT removes the in-admin code editor. DISALLOW_FILE_MODS goes further — it disables plugin and theme installs, updates, and deletions from the admin entirely. Updates then have to come from the file system (FTP, SSH, WP-CLI, or your deployment pipeline).

On a single-admin site, DISALLOW_FILE_EDIT alone is the right setting. On a multi-admin or agency-managed site, set both — and run updates from a deployment pipeline.

FORCE_SSL_ADMIN and friends

If your site is on HTTPS (and it should be), force the admin to be too:

define( 'FORCE_SSL_ADMIN', true );

This ensures that login forms, admin sessions, and cookies never travel over HTTP. It is rare in 2026 to see a WordPress site that is not on HTTPS, but the failure mode — admin cookie sent in cleartext over a coffee-shop Wi-Fi network — is still real on the long tail of un-migrated sites.

Also useful, depending on your setup:

// Tell WordPress how to detect HTTPS behind a reverse proxy / load balancer:
if ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
     $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) {
    $_SERVER['HTTPS'] = 'on';
}

Without this, WordPress behind Cloudflare or AWS ALB sometimes generates mixed-content URLs.

WP_DEBUG and the production-debug leak

The single most common information disclosure we find in audits is WP_DEBUG or WP_DEBUG_DISPLAY left on in production. The symptom: every PHP notice and warning leaks file paths, database queries, and sometimes plugin internals into the page HTML for any visitor to see.

The right production setting:

define( 'WP_DEBUG', false );
define( 'WP_DEBUG_DISPLAY', false );
define( 'WP_DEBUG_LOG', false );  // unless you are actively investigating
@ini_set( 'display_errors', 0 );

If you genuinely need debug logging on, write to a file outside the document root, never to the screen:

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_DISPLAY', false );
define( 'WP_DEBUG_LOG', '/var/log/wordpress/debug.log' );  // outside webroot

Automatic updates — on or off?

Our recommendation, after running many of these audits: auto-updates on for core minor releases and security patches, manual for major releases.

define( 'WP_AUTO_UPDATE_CORE', 'minor' );

The argument for fully-on (true) is that you never miss a security patch. The argument against is that a major release occasionally breaks a plugin you depend on. 'minor' is the compromise: security patches arrive immediately, breaking changes wait for you to test them.

For plugins and themes, individual auto-updates are best managed in the admin (Plugins → Enable auto-updates) rather than via a global wp-config constant — you almost always want auto-update on for security-relevant plugins (your firewall, your contact form) and off for ones that occasionally introduce breaking changes (your page builder).

Move wp-config.php (or at least protect it)

WordPress will look for wp-config.php one directory above the install root before falling back to the install root itself. If your WordPress lives at /var/www/example.com/public/, you can move wp-config.php to /var/www/example.com/wp-config.php — one level up, outside the document root. The web server cannot serve it even if a misconfiguration tries to.

If you cannot move it (managed hosting often blocks this), at minimum block direct HTTP access. Apache:

<Files wp-config.php>
    Require all denied
</Files>

Nginx:

location = /wp-config.php { deny all; }

File permissions and ownership

The defaults from a one-click installer are sometimes too generous. A reasonable hardened baseline:

This is the inverse of the common "just chmod 777 everything until it works" approach. chmod 777 on wp-config.php means any compromised PHP process anywhere on the server can read your database password. It is also a finding we still see weekly.

If WordPress complains that it cannot update plugins, the right fix is not to loosen permissions — it is to configure FS_METHOD in wp-config.php to use SSH or direct filesystem with appropriate ownership:

define( 'FS_METHOD', 'direct' );  // when the deploy user IS the web server user
// or use ssh2 with explicit credentials for shared environments

A hardened wp-config.php, end to end

Putting it all together. Adapt to your environment:

<?php
// ---------- Database ----------
define( 'DB_NAME',     'wp_yoursite' );
define( 'DB_USER',     'wp_yoursite_app' );           // not 'root'
define( 'DB_PASSWORD', getenv('WP_DB_PASSWORD') );    // pulled from env, not hard-coded
define( 'DB_HOST',     'localhost' );
define( 'DB_CHARSET',  'utf8mb4' );
define( 'DB_COLLATE',  '' );

// ---------- Secret keys (rotate at least every 90 days) ----------
define( 'AUTH_KEY',         '...64+ chars of entropy...' );
define( 'SECURE_AUTH_KEY',  '...64+ chars of entropy...' );
define( 'LOGGED_IN_KEY',    '...64+ chars of entropy...' );
define( 'NONCE_KEY',        '...64+ chars of entropy...' );
define( 'AUTH_SALT',        '...64+ chars of entropy...' );
define( 'SECURE_AUTH_SALT', '...64+ chars of entropy...' );
define( 'LOGGED_IN_SALT',   '...64+ chars of entropy...' );
define( 'NONCE_SALT',       '...64+ chars of entropy...' );

// ---------- Table prefix ----------
$table_prefix = 'wp_3kn_';

// ---------- Production: no debug output ----------
define( 'WP_DEBUG',         false );
define( 'WP_DEBUG_DISPLAY', false );
define( 'WP_DEBUG_LOG',     false );
@ini_set( 'display_errors', 0 );

// ---------- Hardening ----------
define( 'DISALLOW_FILE_EDIT', true );
define( 'DISALLOW_FILE_MODS', false );  // true if updates go through a deploy pipeline
define( 'FORCE_SSL_ADMIN',    true );
define( 'WP_AUTO_UPDATE_CORE', 'minor' );
define( 'AUTOMATIC_UPDATER_DISABLED', false );

// ---------- HTTPS behind a reverse proxy ----------
if ( isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
     $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) {
    $_SERVER['HTTPS'] = 'on';
}

// ---------- Revisions and trash ----------
define( 'WP_POST_REVISIONS', 10 );
define( 'EMPTY_TRASH_DAYS',  14 );

if ( ! defined( 'ABSPATH' ) ) {
    define( 'ABSPATH', __DIR__ . '/' );
}
require_once ABSPATH . 'wp-settings.php';

Notice getenv('WP_DB_PASSWORD') for the database password — if your hosting supports environment variables, pulling the password from one keeps the secret out of any file you might inadvertently share or back up.

Common questions

How do I know if my wp-config.php is leaking?

From any browser, visit https://yoursite.com/wp-config.php. The correct response is a 403 or an empty 200. A page of plaintext PHP — including DB credentials — means PHP is not executing the file, which means it is being served as raw text. This is rare but catastrophic. It usually means the web server is misconfigured and is treating .php as plain text. Stop the bleeding by adding the deny rule above, then fix the PHP handler config.

Will rotating my secret keys log everyone out?

Yes. Every active session will need to log in again. Schedule the rotation for a low-traffic window if that matters, and warn your team.

Should I use environment variables for secrets?

If your hosting supports it, yes. The advantage is that secrets stay out of files that might end up in a repository, a backup, or a developer's laptop. The disadvantage is operational complexity — you now have to manage the environment too. For most small sites, a strict chmod 640 on wp-config.php with the file outside the document root is sufficient.

What about the wp-content/uploads directory?

It has its own hardening rules — mostly "do not execute PHP from here." See the relevant section in our Contact Form 7 post for the exact web-server rules.

How often does Lockora re-read wp-config.php?

Every audit. Most checks complete in under three seconds for this file alone.

Let the AI read your wp-config for you.

Lockora's audit checks every constant in this article — plus the dozen we did not have room for — in one pass.

Install the plugin