WordPress Database Security: Complete Protection Guide
Secure your WordPress database with table prefix changes, access controls, prepared statements, and encryption.
Your WordPress database contains everything: posts, users, passwords, settings, and customer data. Protecting it from SQL injection, unauthorized access, and data theft is critical.
Database Security Fundamentals
What the Database Contains
- User credentials (hashed passwords)
- Email addresses and personal data
- Plugin and theme settings
- Posts, pages, and media references
- E-commerce transactions
Table Prefix Security
Change Default Prefix
The default wp_ prefix makes SQL injection easier. Use a unique prefix.
// wp-config.php
$table_prefix = 'wpfs_8x7k_';
// Random prefix generator
$table_prefix = 'wp' . substr(md5(time()), 0, 6) . '_';
Changing Existing Prefix
-- Rename tables (backup first!)
RENAME TABLE wp_posts TO wpfs_8x7k_posts;
RENAME TABLE wp_users TO wpfs_8x7k_users;
-- Continue for all tables
-- Update options table
UPDATE wpfs_8x7k_options
SET option_name = REPLACE(option_name, 'wp_', 'wpfs_8x7k_')
WHERE option_name LIKE 'wp_%';
-- Update usermeta
UPDATE wpfs_8x7k_usermeta
SET meta_key = REPLACE(meta_key, 'wp_', 'wpfs_8x7k_')
WHERE meta_key LIKE 'wp_%';
SQL Injection Prevention
Always Use Prepared Statements
// WRONG - vulnerable to SQL injection
$wpdb->query("SELECT * FROM wp_posts WHERE ID = $_GET[id]");
// CORRECT - use prepare()
$wpdb->get_results(
$wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE ID = %d",
intval($_GET['id'])
)
);
// For strings
$wpdb->prepare(
"SELECT * FROM {$wpdb->users} WHERE user_email = %s",
sanitize_email($_POST['email'])
);
Parameterized Queries
// Insert with prepare
$wpdb->insert(
$wpdb->posts,
array(
'post_title' => sanitize_text_field($title),
'post_content' => wp_kses_post($content),
'post_status' => 'publish'
),
array('%s', '%s', '%s')
);
// Update with prepare
$wpdb->update(
$wpdb->posts,
array('post_title' => $new_title),
array('ID' => $post_id),
array('%s'),
array('%d')
);
Database User Permissions
Principle of Least Privilege
-- Create dedicated WordPress user
CREATE USER 'wp_user'@'localhost' IDENTIFIED BY 'strong_password';
-- Grant only necessary permissions
GRANT SELECT, INSERT, UPDATE, DELETE
ON wordpress_db.* TO 'wp_user'@'localhost';
-- Avoid granting these unless needed
-- DROP, ALTER, CREATE, INDEX, GRANT
Separate Users for Tasks
- Read-only user for reporting
- Backup user with SELECT only
- Admin user for migrations only
Database Connection Security
SSL/TLS Connection
// wp-config.php
define('MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_SSL);
// Or specify certificate
define('MYSQL_SSL_CA', '/path/to/ca-cert.pem');
Local Socket Connection
// Use socket instead of TCP when possible
define('DB_HOST', 'localhost:/var/run/mysql/mysql.sock');
Data Encryption
Encrypting Sensitive Data
// Encrypt before storing
function wpfs_encrypt($data) {
$key = defined('WPFS_ENCRYPTION_KEY')
? WPFS_ENCRYPTION_KEY
: AUTH_KEY;
$iv = random_bytes(16);
$encrypted = openssl_encrypt(
$data,
'AES-256-CBC',
$key,
0,
$iv
);
return base64_encode($iv . $encrypted);
}
function wpfs_decrypt($data) {
$key = defined('WPFS_ENCRYPTION_KEY')
? WPFS_ENCRYPTION_KEY
: AUTH_KEY;
$data = base64_decode($data);
$iv = substr($data, 0, 16);
$encrypted = substr($data, 16);
return openssl_decrypt(
$encrypted,
'AES-256-CBC',
$key,
0,
$iv
);
}
Backup Security
- Encrypt database backups
- Store backups off-server
- Test restoration regularly
- Secure backup credentials separately
Monitoring and Auditing
- Enable MySQL general log (temporarily)
- Monitor failed connection attempts
- Track schema changes
- Audit data modifications
Conclusion
Database security requires multiple layers: unique table prefixes, prepared statements, proper permissions, encrypted connections, and regular backups. Never trust user input in database queries.
Written by Sarah Chen
WP Folder Shield Team