MariaDB replication copies data from one database server (the master) to one or more other servers (slaves) in near real-time. Every write that hits the master gets recorded in a binary log, shipped to the slave, and replayed there. The result is two or more servers holding identical data without any application-level changes.
In this guide, I will walk you through setting up master-slave replication between two MariaDB 11.x servers running Debian 13 (Trixie) or Debian 12 (Bookworm). We will configure the master, prepare the slave, start replication, verify it works, and cover failover and semi-synchronous replication as optional next steps.
Why Use MariaDB Replication
Replication is one of the first things I set up on any production MariaDB deployment. After running database infrastructure for over a decade, these are the use cases that come up again and again:
- Read scaling – Route SELECT queries to slaves and keep the master free for writes. A single slave can absorb a surprising amount of read traffic.
- Live backups – Run
mariadb-dumpor filesystem snapshots on the slave instead of the master. No locking, no performance hit on production. - Disaster recovery – If the master fails, promote the slave and have the application back online in minutes rather than hours.
- Reporting and analytics – Point heavy reporting queries at a slave so they never slow down the primary workload.
- Geographic distribution – Place a slave closer to users in another region to cut query latency for read-heavy applications.
Prerequisites
Before you begin, confirm the following:
- Two Debian 13 or Debian 12 servers, each with root or sudo access
- MariaDB 11.x installed on both servers (follow our Install MariaDB 11 on Debian guide if you have not done this yet)
- Both servers can reach each other over the network on port 3306
- At least 1 GB of RAM per server (2 GB+ recommended for production)
- System packages up to date on both machines
Throughout this guide I will use the following details. Replace them with your own values:
| Role | Hostname | IP Address |
|---|---|---|
| Master | db-master | 192.168.1.10 |
| Slave | db-slave | 192.168.1.11 |
Update both servers before proceeding:
sudo apt update && sudo apt upgrade -y
Confirm MariaDB is running on both servers:
sudo systemctl status mariadb
You should see active (running) in the output on both machines. If MariaDB is not running, start it with sudo systemctl start mariadb.
Step 1 – Configure the Master Server
All replication configuration in MariaDB lives in the server configuration file. On Debian, the recommended location for custom settings is a dedicated file under /etc/mysql/mariadb.conf.d/.
Open the main server configuration file on the master:
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
Find the [mysqld] section and add or modify the following directives:
[mysqld]
server-id = 1
log_bin = /var/log/mysql/mariadb-bin
binlog_format = ROW
binlog_do_db = myappdb
expire_logs_days = 7
max_binlog_size = 500M
bind-address = 0.0.0.0
Here is what each directive does:
server-id = 1– A unique integer that identifies this server in the replication topology. Every server must have a different ID.log_bin– Enables binary logging and sets the base path for the log files. Without this, replication cannot work.binlog_format = ROW– Row-based replication is the safest format. It logs the actual row changes rather than SQL statements, which avoids issues with non-deterministic queries.binlog_do_db = myappdb– Limits replication to a specific database. Remove this line if you want to replicate all databases.expire_logs_days = 7– Automatically purges binary logs older than 7 days so they do not fill up your disk.max_binlog_size = 500M– Rotates the binary log file once it reaches 500 MB.bind-address = 0.0.0.0– Makes MariaDB listen on all interfaces so the slave can connect. In production, restrict this to the internal network interface.
Save the file and restart MariaDB on the master:
sudo systemctl restart mariadb
Verify binary logging is active:
sudo mariadb -e "SHOW VARIABLES LIKE 'log_bin';"
Expected output:
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON |
+---------------+-------+
Also confirm the server ID:
sudo mariadb -e "SHOW VARIABLES LIKE 'server_id';"
The value should be 1.
Step 2 – Configure the Slave Server
On the slave server, open the same configuration file:
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
Add the following under the [mysqld] section:
[mysqld]
server-id = 2
relay-log = /var/log/mysql/mariadb-relay-bin
read_only = 1
bind-address = 0.0.0.0
Key points about the slave configuration:
server-id = 2– Must be different from the master. If you add more slaves later, give each one a unique ID (3, 4, 5, etc.).relay-log– The slave writes events received from the master into relay logs before applying them. This sets the base path.read_only = 1– Prevents accidental writes to the slave from non-root users. This is a safety net I always enable on slaves.
Save the file and restart MariaDB on the slave:
sudo systemctl restart mariadb
Verify the server ID on the slave:
sudo mariadb -e "SHOW VARIABLES LIKE 'server_id';"
The value should be 2.
Step 3 – Create a Replication User on the Master
The slave connects to the master using a dedicated MariaDB account. Never reuse an application account for replication – create a separate user with only the REPLICATION SLAVE privilege.
On the master, log in to MariaDB:
sudo mariadb
Create the replication user:
CREATE USER 'repl_user'@'192.168.1.11' IDENTIFIED BY 'StrongRepl!Pass42';
GRANT REPLICATION SLAVE ON *.* TO 'repl_user'@'192.168.1.11';
FLUSH PRIVILEGES;
Replace 192.168.1.11 with your slave’s actual IP address and use a strong password of your own. If you plan to add more slaves later, you can use a subnet wildcard like 'repl_user'@'192.168.1.%'.
Verify the user was created:
SELECT user, host FROM mysql.user WHERE user = 'repl_user';
You should see one row with the replication user and the slave’s IP.
Step 4 – Get the Binary Log Position from the Master
The slave needs to know exactly where to start reading the master’s binary log. If the master database already has data, you need to lock the tables, record the log position, dump the data, and then unlock.
On the master, lock the tables to get a consistent snapshot:
FLUSH TABLES WITH READ LOCK;
Now get the current binary log file and position:
SHOW MASTER STATUS;
You will see output like this:
+----------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+----------------------+----------+--------------+------------------+
| mariadb-bin.000001 | 663 | myappdb | |
+----------------------+----------+--------------+------------------+
Write down the File and Position values. You will need them in Step 6.
Export existing data (if any)
If your master database already has data, dump it now while the tables are still locked. Open a second terminal on the master and run:
sudo mariadb-dump --all-databases --master-data=2 --single-transaction > /tmp/master_dump.sql
Once the dump finishes, go back to the first terminal and unlock the tables:
UNLOCK TABLES;
Step 5 – Import the Data on the Slave
If you created a dump in the previous step, copy it to the slave and import it. Skip this step if the master database was empty.
Copy the dump file to the slave:
scp /tmp/master_dump.sql [email protected]:/tmp/
On the slave, import the dump:
sudo mariadb < /tmp/master_dump.sql
Verify the import:
sudo mariadb -e "SHOW DATABASES;"
You should see myappdb (and any other databases from the master) in the list.
Step 6 - Point the Slave to the Master
Now we tell the slave where the master is and where to start reading the binary log. On the slave, log in to MariaDB:
sudo mariadb
Stop any existing slave processes first:
STOP SLAVE;
Configure the replication connection using the file and position you noted in Step 4:
CHANGE MASTER TO
MASTER_HOST='192.168.1.10',
MASTER_USER='repl_user',
MASTER_PASSWORD='StrongRepl!Pass42',
MASTER_LOG_FILE='mariadb-bin.000001',
MASTER_LOG_POS=663;
Replace the values with your actual master IP, replication user credentials, log file name, and position.
Step 7 - Start Replication
Still on the slave, start the replication threads:
START SLAVE;
Check the replication status immediately:
SHOW SLAVE STATUS\G
Look for these key fields in the output:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
Last_IO_Error:
Last_SQL_Error:
The two critical checks are:
Slave_IO_Running: Yes- The I/O thread is connected to the master and pulling binary log events.Slave_SQL_Running: Yes- The SQL thread is applying the events to the slave database.
If both say Yes and Seconds_Behind_Master is 0, replication is running and fully caught up. If you see errors, check the Troubleshooting section at the end of this guide.
Step 8 - Test Replication
The best way to confirm replication works is to write data on the master and check the slave. On the master:
sudo mariadb
USE myappdb;
CREATE TABLE repl_test (
id INT AUTO_INCREMENT PRIMARY KEY,
message VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO repl_test (message) VALUES ('Replication is working');
SELECT * FROM repl_test;
Now on the slave, check if the table and data appeared:
sudo mariadb -e "SELECT * FROM myappdb.repl_test;"
You should see the same row with the message "Replication is working". If it is there, replication is confirmed and operational.
Clean up the test table when you are done:
sudo mariadb -e "DROP TABLE myappdb.repl_test;"
Step 9 - Promote a Slave to Master (Failover)
When the master goes down and you need to promote the slave, follow these steps. This is a manual failover process - practice it before you need it in production.
On the slave, stop the replication threads:
sudo mariadb -e "STOP SLAVE;"
Reset the slave configuration so it no longer tries to connect to the old master:
sudo mariadb -e "RESET SLAVE ALL;"
Disable read-only mode so the promoted server can accept writes:
sudo mariadb -e "SET GLOBAL read_only = 0;"
Make the change persistent by editing the configuration file on the promoted server:
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
Comment out or remove the read_only line, and add binary logging so this server can act as a master for future slaves:
[mysqld]
server-id = 2
log_bin = /var/log/mysql/mariadb-bin
binlog_format = ROW
# read_only = 1
Restart MariaDB:
sudo systemctl restart mariadb
Verify the promoted server is accepting writes:
sudo mariadb -e "SHOW VARIABLES LIKE 'read_only';"
The value should be OFF. Update your application connection strings to point to this server's IP address.
Step 10 - Enable Semi-Synchronous Replication (Optional)
Standard asynchronous replication has one weakness - the master does not wait for the slave to confirm it received the data. If the master crashes right after a commit, that transaction could be lost. Semi-synchronous replication fixes this by making the master wait for at least one slave to acknowledge each transaction before returning success to the client.
The trade-off is slightly higher write latency. In my experience, the latency increase is barely noticeable on a local network and well worth the data safety.
On the master, install and enable the semi-sync plugin:
sudo mariadb -e "INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';"
sudo mariadb -e "SET GLOBAL rpl_semi_sync_master_enabled = 1;"
On the slave, install and enable the slave-side plugin:
sudo mariadb -e "INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';"
sudo mariadb -e "SET GLOBAL rpl_semi_sync_slave_enabled = 1;"
Restart the slave I/O thread to activate semi-sync:
sudo mariadb -e "STOP SLAVE IO_THREAD; START SLAVE IO_THREAD;"
To make the setting persistent across restarts, add these lines to the configuration files.
On the master, add to /etc/mysql/mariadb.conf.d/50-server.cnf:
plugin_load_add = semisync_master
rpl_semi_sync_master_enabled = 1
rpl_semi_sync_master_timeout = 10000
On the slave, add to /etc/mysql/mariadb.conf.d/50-server.cnf:
plugin_load_add = semisync_slave
rpl_semi_sync_slave_enabled = 1
Verify semi-sync is active on the master:
sudo mariadb -e "SHOW STATUS LIKE 'Rpl_semi_sync_master_status';"
Expected output:
+-----------------------------+-------+
| Variable_name | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_status | ON |
+-----------------------------+-------+
Troubleshooting Common Replication Issues
Slave_IO_Running is No
This means the slave cannot connect to the master. Check these items:
- Verify the master is listening on port 3306:
sudo ss -tlnp | grep 3306 - Test network connectivity from the slave:
mariadb -h 192.168.1.10 -u repl_user -p - Check that the firewall allows port 3306. On Debian with ufw:
sudo ufw allow from 192.168.1.11 to any port 3306 - Confirm the replication user credentials are correct and the host restriction matches the slave's IP
- Look at the
Last_IO_Errorfield inSHOW SLAVE STATUS\Gfor the specific error message
Slave_SQL_Running is No
The SQL thread stopped because it hit an error while applying an event. Common causes:
- A duplicate key error from data that already exists on the slave
- A missing table or database on the slave
- Schema differences between master and slave
Check the exact error:
sudo mariadb -e "SHOW SLAVE STATUS\G" | grep -E "Last_SQL_Error|Last_SQL_Errno"
If you need to skip a single problematic event (use with caution):
sudo mariadb -e "STOP SLAVE; SET GLOBAL sql_slave_skip_counter = 1; START SLAVE;"
Seconds_Behind_Master keeps increasing
The slave is falling behind. This usually means the slave hardware cannot keep up with the master's write rate. Possible fixes:
- Check slave disk I/O with
iostat -x 1- if utilization is near 100%, the disk is the bottleneck - Enable parallel replication on the slave to apply events from multiple threads:
SET GLOBAL slave_parallel_threads = 4; - Move to faster storage (SSD or NVMe) on the slave
- Check for long-running queries on the slave that might be blocking the SQL thread
Server ID conflicts
If you see errors about duplicate server IDs, verify that every server in the replication topology has a unique server-id value:
sudo mariadb -e "SHOW VARIABLES LIKE 'server_id';"
Resynchronizing a broken slave
When a slave is too far out of sync to recover, the cleanest approach is to rebuild it from scratch. On the master:
sudo mariadb-dump --all-databases --master-data=1 --single-transaction > /tmp/full_dump.sql
Copy to the slave, import it, and the --master-data=1 flag will automatically include the CHANGE MASTER TO statement with the correct log file and position. Then just run START SLAVE; on the slave.
Summary
You now have a working MariaDB master-slave replication setup on Debian 13/12. The master logs every change to its binary log, the slave pulls those changes and applies them, and your data stays in sync across both servers. We also covered how to promote a slave during failover and how to enable semi-synchronous replication for stronger data safety guarantees.
A few operational tips from production experience:
- Monitor
Seconds_Behind_Masterwith your monitoring tool of choice. Alert if it stays above zero for more than a few minutes. - Test your failover procedure regularly. A failover plan you have never practiced is not a plan.
- Keep MariaDB versions identical on master and slave. Mismatched versions are a common source of replication breakage after upgrades.
- Consider GTID-based replication (
gtid_slave_pos) for more complex topologies with multiple slaves or cascading replication. It simplifies slave reconfiguration significantly.
































































