AI QA Monkey
AI Security Intelligence
Fix Guide

Hardening wp-config.php: Essential WordPress Security Constants

wp-config.php is the most sensitive file in your WordPress installation. It contains your database credentials, security keys, and critical configuration constants. A single misconfiguration can expose your entire site to attackers.

This guide covers every security constant and best practice for locking down wp-config.php — from file permissions to advanced hardening techniques.

Why wp-config.php Matters

This file contains:

  • Database credentials — username, password, host, and database name
  • Security keys and salts — used to encrypt cookies and sessions
  • Table prefix — default wp_ makes SQL injection easier
  • Debug settings — can leak sensitive information if enabled in production
  • Site URLs — can be manipulated for phishing if exposed

In our analysis of WordPress sites scanned with AI QA Monkey, 23% had wp-config.php accessible via the web, and 41% were running with debug mode enabled in production.

File Permissions CRITICAL

# Set wp-config.php to read-only
chmod 440 wp-config.php

# Alternative: owner read-only (most restrictive)
chmod 400 wp-config.php

# Verify permissions
ls -la wp-config.php
# Should show: -r--r----- or -r--------
Move wp-config.php Above Web Root

WordPress automatically checks one directory above the web root for wp-config.php. Moving it there prevents direct web access entirely: mv /var/www/html/wp-config.php /var/www/wp-config.php

Block Web Access via .htaccess

# Add to .htaccess in WordPress root
<Files wp-config.php>
  Require all denied
</Files>

Security Keys & Salts CRITICAL

WordPress uses 8 security keys to encrypt cookies and session tokens. Weak or default keys make session hijacking trivial.

# Generate fresh keys at:
# https://api.wordpress.org/secret-key/1.1/salt/

define('AUTH_KEY',         'unique-random-string-here');
define('SECURE_AUTH_KEY',  'unique-random-string-here');
define('LOGGED_IN_KEY',    'unique-random-string-here');
define('NONCE_KEY',        'unique-random-string-here');
define('AUTH_SALT',        'unique-random-string-here');
define('SECURE_AUTH_SALT', 'unique-random-string-here');
define('LOGGED_IN_SALT',   'unique-random-string-here');
define('NONCE_SALT',       'unique-random-string-here');

Rotate keys immediately after any suspected compromise. Changing keys invalidates all existing sessions, forcing all users to re-login.

Disable File Editing HIGH

# Disable the built-in Theme Editor and Plugin Editor
define('DISALLOW_FILE_EDIT', true);

# Even stronger: prevent all file modifications (installs, updates, deletes)
define('DISALLOW_FILE_MODS', true);

DISALLOW_FILE_EDIT removes the editor from wp-admin, preventing attackers who gain admin access from injecting code. DISALLOW_FILE_MODS goes further by blocking all file changes — use this if you manage updates via CLI or CI/CD.

Debug Settings for Production HIGH

# PRODUCTION settings — never expose errors to visitors
define('WP_DEBUG', false);
define('WP_DEBUG_DISPLAY', false);
define('WP_DEBUG_LOG', false);
define('SCRIPT_DEBUG', false);

# If you need logging without display:
define('WP_DEBUG', true);
define('WP_DEBUG_DISPLAY', false);  // Hide from visitors
define('WP_DEBUG_LOG', true);       // Log to wp-content/debug.log
@ini_set('display_errors', 0);
Block debug.log Access

If you enable debug logging, block web access to the log file:

# Add to wp-content/.htaccess
<Files debug.log>
  Require all denied
</Files>

Database Security HIGH

# Change the default table prefix (set BEFORE installation)
# Default 'wp_' makes SQL injection attacks easier
$table_prefix = 'a7x_';  // Use a random prefix

# Force SSL for database connections
define('MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_SSL);

# Use a dedicated database user with minimal privileges
# GRANT SELECT, INSERT, UPDATE, DELETE ON wordpress_db.* TO 'wp_user'@'localhost';

Advanced Hardening

# Force SSL for admin area
define('FORCE_SSL_ADMIN', true);

# Limit post revisions (reduces database bloat)
define('WP_POST_REVISIONS', 5);

# Set autosave interval (seconds)
define('AUTOSAVE_INTERVAL', 120);

# Empty trash automatically (days)
define('EMPTY_TRASH_DAYS', 7);

# Disable WordPress cron (use server cron instead)
define('DISABLE_WP_CRON', true);
# Then add to server crontab:
# */15 * * * * cd /var/www/html && php wp-cron.php > /dev/null 2>&1

# Block external HTTP requests (whitelist specific hosts)
define('WP_HTTP_BLOCK_EXTERNAL', true);
define('WP_ACCESSIBLE_HOSTS', 'api.wordpress.org,downloads.wordpress.org');

# Set memory limits
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '512M');

Complete Hardened wp-config.php

<?php
// ** Security Keys — generate at https://api.wordpress.org/secret-key/1.1/salt/ **
define('AUTH_KEY',         'generate-unique-key');
define('SECURE_AUTH_KEY',  'generate-unique-key');
define('LOGGED_IN_KEY',    'generate-unique-key');
define('NONCE_KEY',        'generate-unique-key');
define('AUTH_SALT',        'generate-unique-key');
define('SECURE_AUTH_SALT', 'generate-unique-key');
define('LOGGED_IN_SALT',   'generate-unique-key');
define('NONCE_SALT',       'generate-unique-key');

// ** Database Settings **
define('DB_NAME',     'your_database');
define('DB_USER',     'your_db_user');
define('DB_PASSWORD', 'strong-password-here');
define('DB_HOST',     'localhost');
define('DB_CHARSET',  'utf8mb4');
define('DB_COLLATE',  '');
$table_prefix = 'a7x_';

// ** Security Hardening **
define('DISALLOW_FILE_EDIT', true);
define('FORCE_SSL_ADMIN', true);
define('WP_DEBUG', false);
define('WP_DEBUG_DISPLAY', false);
define('WP_DEBUG_LOG', false);
define('SCRIPT_DEBUG', false);
@ini_set('display_errors', 0);

// ** Performance **
define('WP_POST_REVISIONS', 5);
define('EMPTY_TRASH_DAYS', 7);
define('AUTOSAVE_INTERVAL', 120);
define('DISABLE_WP_CRON', true);
define('WP_MEMORY_LIMIT', '256M');

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

Protecting wp-config.php on shared hosting

Shared hosting environments present unique challenges. Multiple sites on the same server may run under different user accounts, but misconfigured servers can allow one account to read another's files. The correct protections depend on the hosting configuration.

  • Set permissions to 400 (not 644): On shared hosting, 644 allows other users on the server to read your wp-config.php. 400 restricts read access to the file owner only. Test that WordPress still loads after changing — if it breaks, try 440 instead.
  • Use environment variables instead of constants: Some managed hosting platforms (WP Engine, Kinsta, Cloudways) allow you to store database credentials as server-level environment variables, removing them from wp-config.php entirely. This is the most secure approach.
  • Confirm PHP runs as your user: On servers using suPHP or FastCGI/PHP-FPM with per-user pools, PHP executes as the file owner. On servers using mod_php, PHP runs as the Apache user (www-data) and all files must be readable by that user. Confirm which model your host uses before setting restrictive permissions.
  • Request dedicated PHP-FPM pool: If your host supports it, request a dedicated PHP-FPM pool for your site. This isolates your PHP processes from other sites on the server, preventing cross-site file access regardless of permission settings.

Database table prefix: changing from wp_ default

The default WordPress table prefix wp_ is well-known to attackers and simplifies SQL injection exploitation. Changing the prefix does not prevent SQL injection but adds a layer of obscurity that can slow automated exploitation.

# In wp-config.php — change BEFORE WordPress installation
# If changing on existing install, use a plugin or manually rename all tables
$table_prefix = 'xk72m_';  # Use a random prefix, not 'wp_'

# After changing the prefix, update these rows in the users table:
# UPDATE xk72m_usermeta SET meta_key = 'xk72m_capabilities'
#   WHERE meta_key = 'wp_capabilities';
# UPDATE xk72m_usermeta SET meta_key = 'xk72m_user_level'
#   WHERE meta_key = 'wp_user_level';
# UPDATE xk72m_options SET option_name = 'xk72m_user_roles'
#   WHERE option_name = 'wp_user_roles';

If changing the table prefix on an existing installation, use a plugin like Better Search Replace or WP-CLI to handle all necessary database updates atomically. Manual changes to only some tables will break the site.

Environment-based configuration: the modern approach

Storing all configuration in wp-config.php creates a single point of failure. The modern approach separates environment-specific values from configuration logic, keeping secrets out of files that might be accidentally committed or exposed.

# Store secrets as server-level environment variables (set in Nginx/Apache or hosting panel)
# nginx: fastcgi_param DB_PASSWORD "yourpassword";
# apache: SetEnv DB_PASSWORD "yourpassword"

# Then reference them in wp-config.php:
define('DB_PASSWORD', getenv('DB_PASSWORD') ?: 'fallback-for-local-dev');
define('AUTH_KEY',    getenv('WP_AUTH_KEY')  ?: 'local-dev-key-only');

# Benefits:
# - Credentials not in any file on disk
# - wp-config.php can be committed to version control without secrets
# - Different environments (dev/staging/prod) use different values automatically
# - Compromised file doesn't expose production credentials

Scan Your WordPress Configuration

Free scan — detect exposed wp-config.php, debug mode leaks, and security misconfigurations.

Scan WordPress Now

Frequently Asked Questions

What is wp-config.php and why is it important for security?

wp-config.php is the main WordPress configuration file containing database credentials, security keys, and site settings. If exposed or misconfigured, attackers can gain full access to your database and site.

What file permissions should wp-config.php have?

Set wp-config.php to 440 or 400 (read-only). This prevents other users on shared hosting from reading your database credentials.

How do I disable file editing in WordPress?

Add define('DISALLOW_FILE_EDIT', true); to wp-config.php. This removes the Theme Editor and Plugin Editor from wp-admin.

How often should I rotate WordPress security keys?

Rotate keys immediately after any suspected compromise, and at least once every 6-12 months. Generate new keys at WordPress Salt Generator.

Check Your Website Right Now

Run a free automated security scan — 75 checks in 60 seconds. No signup required.

Run Free Security Scan →