MySQL replication streams every write from a master (source) server to one or more replica (slave) servers in near real-time. For WordPress, this enables read scaling — sending all SELECT queries to replicas while writes go to the master — using the HyperDB drop-in or a custom db.php.
Problem: A high-traffic WordPress or WooCommerce site generates more concurrent database reads than a single MySQL server can handle — adding read replicas improves capacity but requires routing read queries away from the primary.
Solution: Set up MySQL replication with CHANGE REPLICATION SOURCE TO (MySQL 8) pointing the replica at the primary's binary log position. In WordPress, use the HyperDB drop-in to route SELECT queries to replicas and writes to the primary — configure each server with a read weight in db-config.php.
The examples below configure binary logging on the master, create a replication user, run the initial CHANGE MASTER TO on the replica, and show a minimal WordPress db.php that routes reads to a replica.
# ── MASTER SERVER (my.cnf) ──
[mysqld]
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = ROW # safest format for WordPress
expire_logs_days = 7
max_binlog_size = 100M
# Restrict binary logging to the WordPress database
binlog_do_db = helloadmin
# ── REPLICA SERVER (my.cnf) ──
[mysqld]
server-id = 2
relay_log = /var/log/mysql/mysql-relay-bin.log
read_only = ON # prevents accidental writes to replica
log_slave_updates = ON # allows chaining replicas
# Restart both MySQL instances after editing my.cnf
Create the replication user and start replication:
-- ── ON THE MASTER ──
-- Create a dedicated replication user (principle of least privilege)
CREATE USER 'replicator'@'REPLICA_IP' IDENTIFIED WITH mysql_native_password BY 'StrongPassword!';
GRANT REPLICATION SLAVE ON *.* TO 'replicator'@'REPLICA_IP';
FLUSH PRIVILEGES;
-- Take a consistent snapshot
FLUSH TABLES WITH READ LOCK;
SHOW MASTER STATUS;
-- Note: File = 'mysql-bin.000001', Position = 154
-- Export the DB while the lock is held:
-- mysqldump -u root helloadmin > helloadmin_snapshot.sql
UNLOCK TABLES;
-- ── ON THE REPLICA ──
-- Import the snapshot first:
-- mysql -u root helloadmin < helloadmin_snapshot.sql
CHANGE MASTER TO
MASTER_HOST = 'MASTER_IP',
MASTER_USER = 'replicator',
MASTER_PASSWORD = 'StrongPassword!',
MASTER_LOG_FILE = 'mysql-bin.000001',
MASTER_LOG_POS = 154;
START SLAVE;
-- Verify replication is running:
SHOW SLAVE STATUS\G
-- Look for: Slave_IO_Running: Yes, Slave_SQL_Running: Yes, Seconds_Behind_Master: 0
Point WordPress reads to the replica with a minimal db.php:
replica = new mysqli( DB_REPLICA_HOST, DB_USER, DB_PASSWORD, DB_NAME );
}
public function query( $query ) {
// Send SELECTs to the replica, everything else to master
if ( $this->replica && preg_match( '/^\s*SELECT\s/i', $query ) ) {
$this->dbh = $this->replica;
} else {
$this->dbh = $this->dbhmaster ?? $this->dbh;
}
return parent::query( $query );
}
}
// Override the global $wpdb with our subclass
$wpdb = new WP_DB_With_Replica( DB_USER, DB_PASSWORD, DB_NAME, DB_HOST );
NOTE: For production use, prefer the HyperDB drop-in (Automattic/hyperdb on GitHub) over a custom db.php — it handles connection failures, replica lag detection, and multiple replicas out of the box.