How7o
  • Home
  • Tools
  • Prank Screens
  • Learn
  • Blog
  • Contact
Reading: How to Set Up MySQL Primary-Replica Replication on Ubuntu (Production Guide)
Share
How7oHow7o
Font ResizerAa
  • OS
Search
  • Home
  • Tools
  • Prank Screens
  • Learn
  • Blog
  • Contact
Follow US
© 2024–2026 How7o. All rights reserved.
How7o > Free Laravel, PHP, WordPress & Server Tutorials > Server Management > How to Set Up MySQL Primary-Replica Replication on Ubuntu (Production Guide)
Server Management

How to Set Up MySQL Primary-Replica Replication on Ubuntu (Production Guide)

how7o
By how7o
Last updated: May 24, 2026
10 Min Read
MySQL primary and replica database cylinders connected by replication arrows
SHARE

MySQL primary-replica replication moves you from a single point of failure to a hot standby with current data — same database, second server, applied in near real-time. Here’s the production-grade GTID-based setup on Ubuntu 24.04 (works the same on 22.04 and 26.04), the modern way that’s existed since MySQL 8.0 (replacing the old log-file-position approach). Two VPS, one replication user, one snapshot transfer, and a verification step that catches the most common silent failure.

Contents
  • TL;DR
  • Why replicate (and what you get)
  • Prerequisites
  • Step 1 — Configure the primary
  • Step 2 — Create the replication user
  • Step 3 — Snapshot the primary’s data
  • Step 4 — Configure the replica
  • Step 5 — Point the replica at the primary and start
  • Step 6 — Verify replication is working
  • Troubleshooting
    • "Replica_IO_Running: No"
    • "Seconds_Behind_Source keeps growing"
    • "Replication broke mid-flight with a duplicate-key error"
  • Related guides

TL;DR

Two servers running MySQL 8.0+ on Ubuntu. Primary: enable binary logging + GTID, create a replication user, dump the data with --source-data. Replica: load the dump, point at the primary with CHANGE REPLICATION SOURCE TO …, START REPLICA, and watch SHOW REPLICA STATUS until Seconds_Behind_Source stabilises near zero. The hard parts are network config (replica needs port 3306 access to primary) and the order of operations during the dump.

Why replicate (and what you get)

  • Hot standby for failover. If the primary dies, you promote the replica and your application keeps running. Recovery time becomes minutes instead of hours-to-restore-from-backup.
  • Read scaling. Point read-only queries at the replica, write queries at the primary. Doubles read throughput on most web workloads.
  • Backup without locking. Run mysqldump against the replica without affecting the primary. You can also stop replication on the replica briefly, take a filesystem-level snapshot, and resume — true zero-impact backups.
  • Geographic distribution. Replicas in other regions reduce latency for read-heavy apps with global users.

Prerequisites

  • Two Ubuntu servers with MySQL 8.0+ installed (matching minor versions; mixing 8.0 and 8.4 works but isn’t recommended).
  • Network connectivity from the replica to the primary on port 3306 (open in the firewall, restricted to the replica’s IP).
  • Root SQL access on the primary, root SQL access on the replica.
  • About 30 minutes plus however long it takes to transfer a snapshot of the primary’s data.

Step 1 — Configure the primary

Edit /etc/mysql/mysql.conf.d/mysqld.cnf on the primary server:

[mysqld]
server-id              = 1
log-bin                = mysql-bin
binlog_format          = ROW
gtid_mode              = ON
enforce_gtid_consistency = ON
bind-address           = 0.0.0.0

bind-address = 0.0.0.0 opens MySQL to the network — restrict access via firewall (ufw allow from REPLICA_IP to any port 3306) so only the replica can connect. Restart:

sudo systemctl restart mysql

Step 2 — Create the replication user

On the primary’s MySQL shell:

CREATE USER 'repl'@'REPLICA_IP' IDENTIFIED WITH 'caching_sha2_password' BY 'long-random-replication-password';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repl'@'REPLICA_IP';
FLUSH PRIVILEGES;

Replace REPLICA_IP with the replica’s actual IP. The caching_sha2_password auth plugin is the MySQL 8.0+ default — using it avoids the legacy mysql_native_password warnings.

MySQL replication topology — primary writes binlog, replica reads via IO+SQL threads, GTID tracks position

Step 3 — Snapshot the primary’s data

From the primary‘s shell (not MySQL — the OS shell):

mysqldump -u root -p \
  --all-databases \
  --single-transaction \
  --source-data=2 \
  --triggers --routines --events \
  --set-gtid-purged=ON \
  > /tmp/primary-snapshot.sql

Key flags:

  • --single-transaction — consistent snapshot of InnoDB tables without locking.
  • --source-data=2 — embeds a commented CHANGE REPLICATION SOURCE line with the GTID position. The replica needs this.
  • --set-gtid-purged=ON — writes the GTID set into the dump so the replica skips events already applied on the primary before replication started.

Transfer the dump to the replica:

scp /tmp/primary-snapshot.sql user@REPLICA_IP:/tmp/

Step 4 — Configure the replica

Edit /etc/mysql/mysql.conf.d/mysqld.cnf on the replica:

[mysqld]
server-id              = 2
relay-log              = relay-log
gtid_mode              = ON
enforce_gtid_consistency = ON
read_only              = ON
super_read_only        = ON

read_only + super_read_only prevent application writes from accidentally hitting the replica and breaking replication. Restart the replica:

sudo systemctl restart mysql

Load the snapshot:

mysql -u root -p 

Step 5 — Point the replica at the primary and start

In the replica's MySQL shell:

CHANGE REPLICATION SOURCE TO
  SOURCE_HOST     = 'PRIMARY_IP',
  SOURCE_PORT     = 3306,
  SOURCE_USER     = 'repl',
  SOURCE_PASSWORD = 'long-random-replication-password',
  SOURCE_AUTO_POSITION = 1;

START REPLICA;

SOURCE_AUTO_POSITION = 1 tells the replica to use GTID for tracking position — no need to specify a binlog file or position manually.

Step 6 — Verify replication is working

SHOW REPLICA STATUS\G

Look for these three values:

  • Replica_IO_Running: Yes — replica is reading binlog from primary
  • Replica_SQL_Running: Yes — replica is applying events to its own data
  • Seconds_Behind_Source: 0 (or a small number that's not growing)

Test it: create a table on the primary, watch it appear on the replica within a second:

-- on primary
CREATE DATABASE repl_test;
USE repl_test;
CREATE TABLE ping (id INT);
INSERT INTO ping VALUES (1);

-- on replica
USE repl_test;
SELECT * FROM ping;
-- expect: id=1

Troubleshooting

"Replica_IO_Running: No"

Network or credential issue. Check Last_IO_Error in SHOW REPLICA STATUS: it's usually "Access denied" (wrong password or user not granted from replica's IP) or "Can't connect to MySQL server" (firewall blocking 3306, or bind-address not changed). Fix and run STOP REPLICA; START REPLICA;.

"Seconds_Behind_Source keeps growing"

Replica can't keep up with the primary's write rate. Causes: replica has slower disk (single-thread SQL apply is I/O-bound), replica is under-provisioned RAM (buffer pool too small), or a long-running query is blocking SQL thread. Use SHOW PROCESSLIST on the replica to identify the bottleneck. For sustained write-heavy workloads, enable parallel replication: SET GLOBAL replica_parallel_workers = 4;.

"Replication broke mid-flight with a duplicate-key error"

Means something wrote to the replica directly, bypassing replication. read_only doesn't apply to root (you need super_read_only). The fix depends on damage scope — usually delete the conflicting row on the replica and START REPLICA;. For widespread divergence, re-snapshot from the primary.

Should I use replication or just take daily backups?

Both, for different reasons. Backups protect against logical corruption (bad query, dropped table, ransomware) — replication doesn't, because replicas faithfully replicate the destructive change. Replication protects against hardware failure and gives you read scaling and fast failover. The right answer for production is daily logical backups (mariadb-dump or mysqldump) plus a replica for failover.

Is GTID-based replication compatible with old binlog-position-based setups?

Yes, but the transition has to be deliberate. To convert a non-GTID setup to GTID, both servers need gtid_mode = ON and enforce_gtid_consistency = ON, applied in stages. For brand-new replication setups, start with GTID — it's the modern default and makes failover much simpler.

What happens if the primary's disk fills up?

Writes fail on the primary, replication catches up to the last applied event, and stops there. When you free space, the primary resumes and the replica catches up. The binlog itself fills disk over time — set binlog_expire_logs_seconds = 604800 (7 days) or use PURGE BINARY LOGS BEFORE NOW() - INTERVAL 7 DAY; on a cron job.

Can I write to the replica?

You can but you shouldn't. read_only = ON blocks regular users; super_read_only = ON also blocks root. The only acceptable writes on a replica are administrative ones (creating new replication users for further fan-out replicas, for example). Application writes belong on the primary.

How do I promote the replica to primary during a failover?

On the replica: STOP REPLICA; RESET REPLICA ALL; to disconnect from the dead primary. Set read_only = OFF; super_read_only = OFF; to allow writes. Point your application's database connection at the new primary's address. When you rebuild the old primary, configure it as a replica of the new one to rebuild redundancy.

How much overhead does replication add?

On the primary: roughly 5-10% CPU during high write loads (cost of writing the binary log). On the replica: similar to a normal MySQL server. Bandwidth: the binlog stream is proportional to write rate — a server doing 100 writes/sec with small rows generates maybe 1-2 MB/sec of binlog traffic.

Related guides

  • Install MySQL on Ubuntu — needed on both servers before this guide applies.
  • Install MariaDB on Ubuntu — the same replication concept works with MariaDB; the commands are identical.
  • Disable MySQL binary logging — for the standalone server case, opposite of what this guide does.
  • MySQL binary log cleanup — manage the disk usage that replication's binlogs create.

The full replication reference, including parallel-replica tuning and group-replication, lives in the MySQL Reference Manual replication chapter.

TAGGED:configurationmysqlperformanceUbuntu

Sign Up For Daily Newsletter

Be keep up! Get the latest breaking news delivered straight to your inbox.
[mc4wp_form]
By signing up, you agree to our Terms of Use and acknowledge the data practices in our Privacy Policy. You may unsubscribe at any time.
Share This Article
Facebook Copy Link Print
Previous Article MariaDB 11.4 LTS database cylinder being installed on Ubuntu How to Install MariaDB 11.4 LTS on Ubuntu 24.04 (Fresh Install, Secured)
Next Article GitHub Actions workflow deploying Laravel to a VPS, zero-downtime symlink swap How to Deploy a Laravel App to a VPS with GitHub Actions (Zero-Downtime, No Forge)
Leave a Comment

Leave a Reply Cancel reply

You must be logged in to post a comment.

FacebookLike
XFollow
PinterestPin
InstagramFollow
Most Popular
Bun runtime — faster JS toolkit replacing npm in Laravel projects
How to Install Bun Runtime on Ubuntu (And Use It in a Laravel Project)
May 24, 2026
Tailscale mesh — peer-to-peer connections between devices, coordination server
How to Install Tailscale on Ubuntu (Zero-Config Mesh VPN for Self-Hosters)
May 24, 2026
Caddy server — automatic HTTPS, 3-line Caddyfile vs 25-line nginx config
How to Install Caddy Server on Ubuntu (Automatic HTTPS, Drop-in nginx Alternative)
May 24, 2026
Cloudflare Tunnel — outbound-only connection from server, no inbound port forward
How to Install Cloudflare Tunnel on Ubuntu (Expose Local Services, No Port Forwarding)
May 24, 2026
WireGuard encrypted tunnel between server and clients with lock icons
How to Set Up WireGuard VPN on Ubuntu (Server, Linux Client, and iOS)
May 24, 2026

You Might Also Like

Fix XAMPP MySQL shutdown unexpectedly error
Server Management

How to Fix “Error: MySQL Shutdown Unexpectedly” in XAMPP

5 Min Read
Laravel Eloquent count rows — Post::count query snippet with aggregate bar chart icon
Web Development

How to Count Rows in Laravel Eloquent Efficiently

6 Min Read
List network devices on Linux — ip a and nmcli output side by side
Server Management

How to List Network Devices on Linux

5 Min Read
How to temporarily disable Imunify360 service for testing (cPanel/WHM)
Server Management

How to Temporarily Disable Imunify360 Service (Safe Testing + Fix 503)

5 Min Read
How7o

We provide tips, tricks, and advice for improving websites and doing better search.

Tools

  • Age Calculator
  • Word Counter
  • Image Upscaler
  • Password Generator
  • QR Code Generator
  • See all tools→

Pranks

  • Fake Blue Screen Prank
  • Hacker Typer
  • Fake iMessage Generator
  • Windows XP Crash Prank
  • Windows 11 Update Prank
  • See all prank screens →

Company

  • About Us
  • Blog
  • Contact
  • Privacy Policy
  • Terms of Service
  • Sitemap
© 2024–2026 How7o. All rights reserved.
Welcome Back!

Sign in to your account

Username or Email Address
Password

Lost your password?