Your database is where everything lives - user accounts, content, configuration, session data. If an attacker gets access to your CMS database, they own your site. Yet database security is often treated as an afterthought, especially on shared hosting where people use whatever phpMyAdmin gives them and move on.

This guide covers practical database security measures that apply to any CMS platform, whether you are running Joomla, WordPress, Drupal, or something else entirely.

One Database User Per Site

This is the most commonly ignored best practice. If you manage multiple sites on the same server, each site should have its own dedicated database user with access only to its own database.

Here is why. If one site gets compromised through a SQL injection vulnerability, the attacker can only access that site’s data. If all your sites share a single database user with global privileges, one compromised Joomla extension can be used to read your WordPress user table, your Drupal configuration, and everything else.

Create a dedicated user in MySQL:

CREATE USER 'site1_user'@'localhost' IDENTIFIED BY 'strong-random-password';
GRANT SELECT, INSERT, UPDATE, DELETE ON site1_db.* TO 'site1_user'@'localhost';
FLUSH PRIVILEGES;

Notice that we are only granting SELECT, INSERT, UPDATE, and DELETE. We are not granting DROP, ALTER, CREATE, or any administrative privileges. This is the principle of least privilege.

Least Privilege in Practice

For day-to-day CMS operation, your database user needs exactly four permissions: SELECT, INSERT, UPDATE, and DELETE. That covers reading content, creating posts, editing settings, and removing data.

The catch is that CMS updates and plugin installations often need additional privileges like CREATE TABLE, ALTER TABLE, and DROP TABLE. There are two approaches to handle this:

Option 1: Temporarily elevate privileges during updates. Grant the additional permissions, run your update, then revoke them. This is more secure but requires manual intervention.

GRANT CREATE, ALTER, DROP, INDEX ON site1_db.* TO 'site1_user'@'localhost';
-- Run your CMS update --
REVOKE CREATE, ALTER, DROP, INDEX ON site1_db.* FROM 'site1_user'@'localhost';
FLUSH PRIVILEGES;

Option 2: Keep a separate admin database user for updates. Store the limited-privilege credentials in your CMS config and use the admin credentials only when running updates through a command line tool or a separate script.

Option 1 is more practical for most people. Option 2 is more appropriate for high-security environments.

Change Default Table Prefixes

We have covered this before in the context of Joomla and WordPress individually, but it bears repeating. Default table prefixes are well-known:

  • Joomla: jos_
  • WordPress: wp_
  • Drupal: (no prefix by default, but table names are predictable)

Automated SQL injection tools use these defaults in their payloads. Changing the prefix does not prevent SQL injection, but it makes blind injection attacks harder because the attacker has to guess your table names.

Set a random prefix during installation. Changing it after the fact is possible but involves renaming every table and updating references in configuration files and sometimes in the data itself. Do it once at install time and save yourself the hassle.

Regular Backups

A backup is your last line of defense. If everything else fails and your database gets compromised or corrupted, a recent backup lets you recover.

Set up automated backups using a cron job:

# Daily database backup at 3 AM
0 3 * * * mysqldump -u backup_user -p'password' site1_db | gzip > /path/to/backups/site1_$(date +\%Y\%m\%d).sql.gz

Important considerations:

  • Store backups outside the web root. A backup file in your public directory can be downloaded by anyone.
  • Use a separate database user for backups with only SELECT and LOCK TABLES privileges.
  • Test your backups regularly. A backup you have never tested restoring is not really a backup.
  • Rotate old backups. Keep 7 daily, 4 weekly, and 3 monthly backups. Delete anything older.

Prepared Statements in Custom Code

If you write any custom code for your CMS - a custom plugin, a theme function, a module - always use prepared statements or the CMS database abstraction layer. Never concatenate user input into SQL strings.

In WordPress:

$wpdb->prepare("SELECT * FROM {$wpdb->posts} WHERE ID = %d", $post_id);

In Joomla 1.5:

$db = JFactory::getDBO();
$query = "SELECT * FROM #__content WHERE id = " . $db->Quote($id);

In Drupal:

db_query("SELECT * FROM {node} WHERE nid = %d", $nid);

Each CMS provides its own method. Use it. There is no valid reason to write raw concatenated SQL in a CMS environment.

Remote Database Access

If your database server accepts connections from anywhere (bind-address = 0.0.0.0), change it to only accept local connections:

In /etc/mysql/my.cnf:

bind-address = 127.0.0.1

If you need remote access for administration, use an SSH tunnel instead of opening MySQL to the internet. On shared hosting, this is usually handled by the host, but verify that your database is not accessible from external IPs.

Monitoring

Check your MySQL slow query log and general query log periodically. Unusual query patterns - especially queries that return errors or take abnormally long - can indicate someone probing for injection points.

Enable the slow query log in my.cnf:

slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2

Database security is not glamorous work. But getting these fundamentals right eliminates one of the most common attack vectors against CMS installations. Take an hour to set it up properly, and you will have significantly reduced your risk.