← Back to Blog

WordPress Database Security: 10 Best Practices

Protect your WordPress database with these 10 best practices covering table prefix changes, SQL injection prevention, user privileges, backups, and wp_options hardening.

WordPress Database Security: 10 Best Practices

Introduction: Your Database Is the Crown Jewel

Every WordPress site runs on a database. It contains your content, user accounts (including hashed passwords), site configuration, plugin settings, customer data, and if you run WooCommerce, order histories and payment records. The WordPress database is, without exaggeration, the single most valuable target for an attacker. A compromised database means total loss of data confidentiality, integrity, and availability.

Despite this, database security is one of the most overlooked aspects of WordPress hardening. Most administrators focus on file permissions, login security, and plugin updates — all important — but rarely examine their database configuration, user privileges, or exposure to SQL injection. This article covers ten concrete, actionable best practices for securing your WordPress database, from simple configuration changes to architectural defenses.

We also cover how the VistoShield Firewall & WAF provides a critical layer of defense by blocking SQL injection attacks at the application level before they reach your database, and how the Security Scanner audits your database configuration for common weaknesses.

1. Change the Default Table Prefix

WordPress uses wp_ as the default table prefix for all database tables (wp_posts, wp_users, wp_options, etc.). This is universally known, and SQL injection attacks that target WordPress are written to exploit this default prefix. Changing the prefix does not prevent SQL injection, but it does make exploitation harder for automated attacks that assume the default.

Changing the Prefix During Installation

The easiest time to change the table prefix is during initial installation. In the WordPress installer, change the "Table Prefix" field from wp_ to something unique:

// In wp-config.php (set during installation)
$table_prefix = 'vs7x_';  // Use a random, short prefix

Choose a prefix that is short (4-6 characters), alphanumeric with underscores, and not easily guessable. Avoid obvious alternatives like wordpress_ or site_.

Changing the Prefix on an Existing Site

Changing the prefix on a live site requires database changes and is more involved. Follow these steps carefully and back up your database first:

-- Step 1: Rename all tables (example for common tables)
RENAME TABLE wp_posts TO vs7x_posts;
RENAME TABLE wp_postmeta TO vs7x_postmeta;
RENAME TABLE wp_users TO vs7x_users;
RENAME TABLE wp_usermeta TO vs7x_usermeta;
RENAME TABLE wp_options TO vs7x_options;
RENAME TABLE wp_comments TO vs7x_comments;
RENAME TABLE wp_commentmeta TO vs7x_commentmeta;
RENAME TABLE wp_terms TO vs7x_terms;
RENAME TABLE wp_termmeta TO vs7x_termmeta;
RENAME TABLE wp_term_relationships TO vs7x_term_relationships;
RENAME TABLE wp_term_taxonomy TO vs7x_term_taxonomy;
RENAME TABLE wp_links TO vs7x_links;

-- Step 2: Update references in wp_options
UPDATE vs7x_options SET option_name = REPLACE(option_name, 'wp_', 'vs7x_')
WHERE option_name LIKE 'wp_%';

-- Step 3: Update references in wp_usermeta
UPDATE vs7x_usermeta SET meta_key = REPLACE(meta_key, 'wp_', 'vs7x_')
WHERE meta_key LIKE 'wp_%';

Then update wp-config.php:

$table_prefix = 'vs7x_';

2. Use Strong Database Credentials

The database username and password stored in wp-config.php are the keys to your data. Weak credentials make direct database access trivial for an attacker who can read configuration files or access the database port.

Best Practices for Database Credentials

  • Never use "root" as the database user for WordPress. Create a dedicated user with limited privileges.
  • Use a strong, random password — at least 32 characters, generated by a password manager or command-line tool.
  • Use a non-obvious database name — avoid wordpress, wp, or your domain name.
# Generate a strong random password on Linux
openssl rand -base64 32
# Output example: kR9Fj2mPqX7vL1nT0wYh3dBs5gAeC4uI6oZx8NlKf=

# Create the database and user in MySQL/MariaDB
mysql -u root -p

CREATE DATABASE vs_prod_7f3a CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'vs_web_7f3a'@'localhost' IDENTIFIED BY 'kR9Fj2mPqX7vL1nT0wYh3dBs5gAeC4uI6oZx8NlKf=';
GRANT SELECT, INSERT, UPDATE, DELETE ON vs_prod_7f3a.* TO 'vs_web_7f3a'@'localhost';
FLUSH PRIVILEGES;

3. Apply the Principle of Least Privilege

Most WordPress installations grant the database user ALL PRIVILEGES on the WordPress database. This is far more than WordPress needs for day-to-day operation. An overprivileged database user means that a SQL injection vulnerability can be used to drop tables, create new users, or even read files from the server filesystem.

Minimum Required Privileges

PrivilegeNeeded ForRequired?
SELECTReading data (posts, users, options)Yes — always
INSERTCreating posts, comments, usersYes — always
UPDATEEditing content, updating settingsYes — always
DELETERemoving content, cleaning transientsYes — always
CREATEPlugin/theme installation, updatesOnly during updates
ALTERDatabase schema changes during updatesOnly during updates
DROPPlugin uninstallation (removing tables)Only during uninstalls
INDEXAdding/removing database indexesOnly during updates

Two-User Strategy

The most secure approach is to use two database users:

  1. Runtime user — Used in wp-config.php for normal site operation. Has only SELECT, INSERT, UPDATE, DELETE.
  2. Admin user — Used temporarily for updates, plugin installations, and maintenance. Has full privileges. Switch to this user in wp-config.php only when needed, then switch back.
-- Runtime user (daily operation)
CREATE USER 'wp_runtime'@'localhost' IDENTIFIED BY 'strong-random-password-1';
GRANT SELECT, INSERT, UPDATE, DELETE ON wordpress_db.* TO 'wp_runtime'@'localhost';

-- Admin user (updates and maintenance only)
CREATE USER 'wp_admin_ops'@'localhost' IDENTIFIED BY 'strong-random-password-2';
GRANT ALL PRIVILEGES ON wordpress_db.* TO 'wp_admin_ops'@'localhost';

FLUSH PRIVILEGES;

4. Protect Against SQL Injection

SQL injection (SQLi) remains one of the most dangerous and common attack vectors against WordPress sites. An attacker injects malicious SQL code through user input (forms, URL parameters, cookies) that gets executed by the database. Successful SQLi can extract all data from the database, modify content, create admin accounts, or even gain server-level access.

How SQL Injection Attacks WordPress

Most SQLi vulnerabilities in WordPress come from plugins and themes that construct SQL queries by directly concatenating user input instead of using prepared statements. Here is a vulnerable pattern:

// VULNERABLE - Never do this
$user_id = $_GET['id'];
$query = "SELECT * FROM wp_users WHERE ID = $user_id";
$results = $wpdb->get_results($query);

// An attacker sends: ?id=1 UNION SELECT user_login,user_pass FROM wp_users--
// This dumps all usernames and password hashes

Using Prepared Statements

WordPress provides the $wpdb->prepare() method for parameterized queries. Always use it when incorporating user input into SQL:

// SECURE - Always use prepared statements
$user_id = $_GET['id'];
$query = $wpdb->prepare("SELECT * FROM {$wpdb->users} WHERE ID = %d", $user_id);
$results = $wpdb->get_results($query);

// The %d placeholder ensures the input is treated as an integer
// SQL injection is impossible because the input is never interpreted as SQL

VistoShield WAF: SQL Injection Protection

Even with careful coding, you cannot guarantee that every plugin on your site uses prepared statements. The VistoShield Firewall & WAF provides a critical safety net by inspecting all incoming HTTP requests for SQL injection patterns before they reach PHP. The WAF detects and blocks:

  • Union-based SQL injection (UNION SELECT)
  • Error-based SQL injection (triggering database errors to extract data)
  • Blind SQL injection (boolean and time-based inference attacks)
  • Stacked queries (; DROP TABLE)
  • Common SQLi payloads in GET parameters, POST data, cookies, and HTTP headers

This application-level filtering is your first line of defense against SQLi. See the Firewall documentation for configuration details.

5. Restrict Database Network Access

By default, MySQL/MariaDB listens on port 3306 on all network interfaces. If your database is on the same server as WordPress (the most common setup), there is no reason for the database to accept connections from anywhere except localhost.

Bind to Localhost Only

# In /etc/mysql/mariadb.conf.d/50-server.cnf (or my.cnf)
[mysqld]
bind-address = 127.0.0.1
skip-networking = 0

Firewall the Database Port

Even with bind-address set, add a firewall rule to block external access to port 3306:

# Using nftables (recommended for modern Linux)
nft add rule inet filter input tcp dport 3306 drop

# Using iptables (legacy)
iptables -A INPUT -p tcp --dport 3306 -j DROP
iptables -A INPUT -p tcp --dport 3306 -s 127.0.0.1 -j ACCEPT

If your database is on a separate server, restrict access to only the web server’s IP address and use an encrypted connection (TLS) between them.

6. Disable Remote Database Access Tools

Tools like phpMyAdmin, Adminer, and other web-based database management interfaces are extremely convenient for development but extremely dangerous in production. They provide a web-accessible interface to your entire database, authenticated only by the database password.

The Risks

  • phpMyAdmin has a long history of security vulnerabilities
  • Web-accessible database tools are targeted by automated scanners
  • Brute-force attacks against the phpMyAdmin login are common
  • A compromised phpMyAdmin session gives full database access

Recommended Actions

  • Remove phpMyAdmin from production servers entirely
  • If you must keep it, restrict access by IP address in your web server configuration
  • Use SSH tunneling to access the database remotely instead
# Access MySQL remotely via SSH tunnel (secure)
ssh -L 3307:127.0.0.1:3306 user@your-server.com

# Then connect with a local MySQL client to port 3307
mysql -h 127.0.0.1 -P 3307 -u wp_user -p wordpress_db

7. Implement Regular Database Backups

Database backups are your last line of defense. If all other security measures fail and your database is compromised, corrupted, or destroyed, backups are the only way to recover. Backups are not a security measure in themselves, but they are an essential component of a security strategy.

Automated Backup Strategy

# Daily database backup with mysqldump
#!/bin/bash
BACKUP_DIR="/var/backups/mysql"
DB_NAME="wordpress_db"
DB_USER="backup_user"
DB_PASS="backup-password"
DATE=$(date +%Y-%m-%d_%H%M)

# Create backup
mysqldump --single-transaction --routines --triggers \
    -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" | \
    gzip > "$BACKUP_DIR/${DB_NAME}_${DATE}.sql.gz"

# Remove backups older than 30 days
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete

# Verify backup integrity
gunzip -t "$BACKUP_DIR/${DB_NAME}_${DATE}.sql.gz" && \
    echo "Backup verified: ${DB_NAME}_${DATE}.sql.gz"

Backup Security

  • Encrypt backups — Database backups contain all your data, including password hashes and personal information. Encrypt them with GPG or AES-256.
  • Store backups off-server — If the server is compromised, on-server backups may also be compromised. Use remote storage (S3, Google Cloud Storage, or a separate backup server).
  • Test restoration regularly — A backup that cannot be restored is worthless. Test the restoration process at least quarterly.
  • Restrict backup file permissions — Backup files should be readable only by the backup user: chmod 600 *.sql.gz

8. Secure the wp_options Table

The wp_options table is one of the most important tables in the WordPress database. It stores site configuration, plugin settings, transients, cron schedules, and API keys. It is also one of the most abused tables, both by poorly written plugins and by attackers.

Common wp_options Security Issues

  • Autoloaded bloat — Plugins that store large amounts of data with autoload = 'yes' cause the options table to be loaded into memory on every page request, increasing attack surface and degrading performance.
  • Sensitive data in plain text — API keys, SMTP passwords, and other secrets stored in wp_options are readable by anyone with database access.
  • Orphaned plugin data — Deactivated plugins often leave data in wp_options that is never cleaned up.

Auditing wp_options

-- Find the largest autoloaded options (potential bloat)
SELECT option_name, LENGTH(option_value) AS size
FROM wp_options
WHERE autoload = 'yes'
ORDER BY size DESC
LIMIT 20;

-- Check total autoloaded data size
SELECT SUM(LENGTH(option_value)) AS total_autoload_bytes
FROM wp_options
WHERE autoload = 'yes';

-- Find options from deactivated plugins (manual review needed)
SELECT option_name FROM wp_options
WHERE option_name LIKE '%transient%'
ORDER BY option_name;

Cleaning Up wp_options

-- Remove expired transients
DELETE FROM wp_options WHERE option_name LIKE '%_transient_timeout_%'
AND option_value < UNIX_TIMESTAMP();

DELETE FROM wp_options WHERE option_name LIKE '%_transient_%'
AND option_name NOT LIKE '%_transient_timeout_%'
AND option_name IN (
    SELECT REPLACE(t.option_name, '_timeout', '')
    FROM (SELECT option_name FROM wp_options
          WHERE option_name LIKE '%_transient_timeout_%'
          AND option_value < UNIX_TIMESTAMP()) AS t
);

9. Enable Database Connection Encryption

By default, the connection between WordPress (PHP) and MySQL/MariaDB is unencrypted. On a single server, this is acceptable because the connection is over localhost. However, if your database is on a separate server, unencrypted connections transmit all queries and results (including passwords and personal data) in plain text across the network.

Configuring MySQL/MariaDB TLS

# In /etc/mysql/mariadb.conf.d/50-server.cnf
[mysqld]
ssl-ca=/etc/mysql/ssl/ca-cert.pem
ssl-cert=/etc/mysql/ssl/server-cert.pem
ssl-key=/etc/mysql/ssl/server-key.pem
require-secure-transport=ON

Configuring WordPress to Use TLS

Add the following to wp-config.php to force WordPress to connect to MySQL over TLS:

// Force MySQL connection over TLS
define('MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_SSL);

// If using a custom CA certificate
define('MYSQL_SSL_CA', '/etc/mysql/ssl/ca-cert.pem');

Verifying the Encrypted Connection

-- Check if your connection is encrypted
SHOW STATUS LIKE 'Ssl_cipher';
-- Should return a cipher name like TLS_AES_256_GCM_SHA384, not empty

10. Monitor Database Activity

Monitoring database activity helps you detect anomalies that may indicate an attack in progress or a successful breach. Unusual query patterns, unexpected data exports, or new database users appearing are all signs of compromise.

MySQL/MariaDB General Query Log

The general query log records every SQL statement executed. It is extremely verbose and should only be enabled temporarily for debugging or investigation:

# Enable general query log temporarily
SET GLOBAL general_log = 'ON';
SET GLOBAL general_log_file = '/var/log/mysql/general.log';

# Check the log for suspicious queries
grep -i "UNION\|INTO OUTFILE\|LOAD_FILE\|INFORMATION_SCHEMA" /var/log/mysql/general.log

# Disable when done (it has significant performance impact)
SET GLOBAL general_log = 'OFF';

WordPress-Level Database Monitoring

The VistoShield Activity Log tracks WordPress-level database operations including option changes, user modifications, content edits, and plugin settings changes. This provides a higher-level view of database activity that is more actionable than raw SQL logs.

The VistoShield Firewall logs all blocked SQL injection attempts, giving you visibility into ongoing attack patterns targeting your database. These logs integrate with the Activity Log for a unified security timeline.

Database Security Audit Checklist

Use this checklist to audit the security of your WordPress database. The VistoShield Security Scanner automates many of these checks.

CheckExpected ResultRisk Level
Table prefix is not wp_Custom prefix in useMedium
Database user is not rootDedicated user with limited privilegesCritical
Database password is strong32+ character random passwordCritical
User has minimal privilegesSELECT, INSERT, UPDATE, DELETE onlyHigh
MySQL bound to localhostbind-address = 127.0.0.1High
Port 3306 firewalledExternal access blockedHigh
phpMyAdmin removed or IP-restrictedNot publicly accessibleCritical
Automated backups runningDaily backup with off-server storageHigh
Backups encryptedGPG or AES-256 encryptionHigh
WAF blocking SQLiVistoShield Firewall activeCritical

Performance and Security: Finding the Balance

Some database security measures have performance implications. Here is how to balance security with performance:

Table Optimization

Over time, WordPress database tables accumulate overhead from deleted rows, expired transients, and auto-drafts. Regular optimization improves both performance and reduces the data available to an attacker:

-- Optimize all WordPress tables
OPTIMIZE TABLE wp_posts, wp_postmeta, wp_options, wp_comments, wp_commentmeta;

-- Remove old post revisions (keep last 5)
DELETE FROM wp_posts WHERE post_type = 'revision'
AND ID NOT IN (
    SELECT * FROM (
        SELECT ID FROM wp_posts WHERE post_type = 'revision'
        ORDER BY post_date DESC
        LIMIT 5
    ) AS recent_revisions
);

-- Limit revisions in wp-config.php
define('WP_POST_REVISIONS', 5);

Query Caching

For read-heavy sites, query caching reduces database load and limits the window of exposure for SQL injection attacks (cached queries bypass the database entirely):

# MariaDB query cache configuration
[mysqld]
query_cache_type = 1
query_cache_size = 64M
query_cache_limit = 2M

Getting Started with VistoShield

Database security is foundational, but it works best as part of a layered security strategy. The VistoShield security suite protects your WordPress database and the rest of your site with five interconnected plugins:

  • Firewall & WAF — Blocks SQL injection attacks at the application level before they reach your database. The WAF inspects all incoming requests for SQLi patterns and blocks them in real time.
  • Security Scanner — Audits database configuration, checks for weak credentials, monitors file integrity, and scans for malware.
  • Login Guard — Prevents brute-force attacks and credential stuffing that could lead to admin-level database access via the WordPress dashboard.
  • Bot Detector — Identifies and blocks automated scanners and attack bots that probe for SQL injection vulnerabilities.
  • Activity Log — Tracks all WordPress-level database operations including settings changes, user modifications, and security events.

All five plugins are available in the free tier under the GPLv2 open-source license. For advanced features, the Single Pro plan starts at €19/site/year, the Pro Bundle is €49/site/year, and the Agency Bundle covers 25 sites for €149/year. Visit the VistoShield homepage to get started, or explore the Firewall documentation for WAF configuration details.

Ready to try VistoShield?

Free and open source. Get started in 60 seconds.

Get Started Free