Primary-replica (historically called master-slave) replication on MariaDB gives you an asynchronous copy of a database you can use for read scaling, analytics queries, hot standby, or rolling maintenance. The setup is three moving parts: bin-log format, a replication user, and a CHANGE MASTER TO call on the replica. This guide walks through the full setup on Ubuntu 24.04 LTS, then shows the equivalent on Debian 13. For a broader sysadmin workflow, our systemctl reference and Rocky 10 post-install tips are good companions.
Tested April 2026 on Ubuntu 24.04.4 LTS with MariaDB 10.11.14 (the version shipping in noble main)
Step 1: Install MariaDB on both hosts
Run this on both the primary and the replica. Ubuntu 24.04 ships MariaDB 10.11 in the main repository, which is the current LTS of the 10.x series:
sudo apt update
sudo apt install -y mariadb-server mariadb-client
sudo systemctl enable --now mariadb
mariadb --version
Check the version so you can confirm both nodes match:
mariadb Ver 15.1 Distrib 10.11.14-MariaDB, for debian-linux-gnu (x86_64) using EditLine wrapper
Running mixed versions in a replication pair is possible but error-prone. Stick to identical minor versions when you can.
Step 2: Configure the primary for binary logging
Binary logs are the replication stream. Without them, replicas have nothing to pull. Create a drop-in config file under /etc/mysql/mariadb.conf.d/ that enables the bin log, sets the format to ROW (safer than STATEMENT), assigns a unique server ID, and binds MariaDB to all interfaces so the replica can connect:
sudo vi /etc/mysql/mariadb.conf.d/99-replication-primary.cnf
Paste in the primary-side replication config:
[mysqld]
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_format = ROW
bind-address = 0.0.0.0
Create the log directory and set the right owner before restarting, otherwise MariaDB aborts with “File ‘/var/log/mysql/mysql-bin.index’ not found”:
sudo mkdir -p /var/log/mysql
sudo chown mysql:mysql /var/log/mysql
sudo systemctl restart mariadb
Confirm the binary log is active. SHOW MASTER STATUS returns the current log filename and byte offset (position). Write these numbers down because the replica needs them in step 4:
sudo mariadb -e "SHOW MASTER STATUS;"
A healthy bin-log setup returns the current log file and byte position:
File Position Binlog_Do_DB Binlog_Ignore_DB
mysql-bin.000001 328
If the command returns an empty result, the bin log isn’t enabled yet. Re-check the drop-in config and the /var/log/mysql ownership.
Step 3: Create a replication user
The replica connects to the primary as a dedicated user with only the REPLICATION SLAVE privilege. Do this on the primary:
sudo mariadb -e "CREATE USER 'repl'@'%' IDENTIFIED BY 'replpass';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;"
Verify the user exists and has the right host pattern:
sudo mariadb -e "SELECT host, user FROM mysql.user WHERE user='repl';"
The replication user shows up with the wildcard host pattern:
Host User
% repl
For production, scope the host from % down to the replica’s IP so any other box on the network can’t use the same credentials to read your bin log.
Step 4: Take a consistent snapshot of the primary
The replica needs to start from a point in time that matches a specific binary log position. mariadb-dump with --master-data=2 --single-transaction writes that position into a comment at the top of the dump, and holds a consistent read view for the duration of the export so the data is internally consistent:
sudo mariadb-dump \
--all-databases \
--master-data=2 \
--single-transaction \
--flush-logs \
> /tmp/primary-dump.sql
Inspect the head of the dump to find the log file + position the replica should start from:
grep -m1 'CHANGE MASTER' /tmp/primary-dump.sql
Copy the dump to the replica over SSH or your own channel:
scp /tmp/primary-dump.sql replica-host:/tmp/
Step 5: Configure the replica
On the replica, drop in a similar config file with a unique server-id (must be different from the primary) and declare the relay log path:
sudo vi /etc/mysql/mariadb.conf.d/99-replication-replica.cnf
Write in the replica-side config:
[mysqld]
server-id = 2
relay_log = /var/log/mysql/mysql-relay-bin.log
read_only = 1
The read_only = 1 setting stops accidental writes on the replica. Users with SUPER can still write, but ordinary application accounts will get an error if they try to modify data.
sudo mkdir -p /var/log/mysql
sudo chown mysql:mysql /var/log/mysql
sudo systemctl restart mariadb
Import the dump to seed the replica’s data:
sudo mariadb < /tmp/primary-dump.sql
Step 6: Point the replica at the primary
The CHANGE MASTER TO call tells the replica where to pull the bin log from, what user to connect as, and which log position to start from. Replace the IP with the primary’s address and the MASTER_LOG_FILE + MASTER_LOG_POS with the values from SHOW MASTER STATUS or from the dump header:
sudo mariadb -e "CHANGE MASTER TO
MASTER_HOST='10.0.1.50',
MASTER_USER='repl',
MASTER_PASSWORD='replpass',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=328;"
sudo mariadb -e "START SLAVE;"
Check the replication state:
sudo mariadb -e "SHOW SLAVE STATUS\G" | grep -E 'Slave_IO_Running|Slave_SQL_Running|Seconds_Behind|Last_Error'
You want both Slave_IO_Running and Slave_SQL_Running set to Yes, Seconds_Behind_Master at 0 or a small positive number, and Last_Error empty. Anything else means the connection isn’t fully healthy and you need to read the last error message to find out what went wrong.
Step 7: Verify end-to-end
On the primary, create a test database and insert a row:
sudo mariadb -e "CREATE DATABASE repltest;
USE repltest;
CREATE TABLE t1(id INT AUTO_INCREMENT PRIMARY KEY, note VARCHAR(100));
INSERT INTO t1(note) VALUES ('replication works');"
On the replica (a second or two later), check that the database and row replicated over:
sudo mariadb -e "SELECT * FROM repltest.t1;"
The row you inserted on the primary is now visible on the replica:
id note
1 replication works
If the row doesn’t appear on the replica within a few seconds, check SHOW SLAVE STATUS\G again and look at the Last_SQL_Error and Last_IO_Error fields.
Common failure modes
Error: “Got fatal error 1236 from master when reading data from binary log”
The replica is asking for a log file or position the primary no longer has. Either the primary rotated past the log the replica was following, or the CHANGE MASTER TO call had the wrong position. Re-run the snapshot from Step 4, import it on the replica, and re-point to the new file/position.
Connection refused on port 3306
The primary is bound to localhost only. Re-check the bind-address = 0.0.0.0 line in the primary’s config and restart MariaDB. Also confirm the firewall lets the replica reach port 3306. Our UFW reference shows the syntax for scoping port 3306 to a specific source IP.
Wrap up
Primary-replica replication is the simplest building block for read scaling and hot standby. For something more sophisticated (multi-master, automatic failover, synchronous commits), look at MariaDB Galera Cluster, which our Galera + HAProxy guide walks through. For database monitoring, pair this with the Prometheus MySQL exporter so you can watch replication lag and the bin log position from Grafana. For backups (which you still need even with replication), see our MySQL backup to S3 guide.