← Start of tutorial

WIUNIX ACADEMY

Install MariaDB primary-replica replication with GTID and TLS

Page 2 of 4. Review the earlier steps and prerequisites before continuing.

YOUR SYSTEM. YOUR STEPS.

Where are you working?

Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.

Ubuntu 26.10Reviewed against official documentation ·

USE YOUR OWN VALUES

Configure this tutorial

Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.

Edit 3 tutorial values
09

LET’S BUILD

Open the replica configuration

Create the selected OS include on the replica; use the replica contents in the following panel, not the primary contents.

Bash

Replica

sudo nano /etc/mariadb/mariadb.conf.d/90-academy-replication.cnf
How do I know it worked?

Replica include opened at the selected OS path.

10

LET’S BUILD

Keep the replica read-only

Replica file contents. read_only blocks ordinary writers but not replication and privileged administrators (READ_ONLY ADMIN/SUPER). Never give application accounts those bypass privileges, and do not point application writes here. MariaDB 10.11/11.8 semantics are used; no MySQL-only super_read_only assumption. Binary logging plus log_slave_updates preserves the applied stream for a separately planned future topology; it does not promote this node.

INI · /etc/mariadb/mariadb.conf.d/90-academy-replication.cnf

File contents; save in named file, not a shell.

[mariadb]
server_id=102
bind_address=127.0.0.1
log_bin=/var/lib/mariadb/replica-bin
relay_log=/var/lib/mariadb/replica-relay
binlog_format=ROW
gtid_domain_id=0
gtid_strict_mode=ON
log_slave_updates=ON
sync_binlog=1
innodb_flush_log_at_trx_commit=1
binlog_expire_logs_seconds=604800
read_only=ON
How do I know it worked?

Replica file saved with102 identity, loopback binding and persistent read_only ON.

11

LET’S BUILD

Restart and inspect the empty replica

Replica only. Restart is disruptive; the lab has no application connection. Confirm the identity, read-only flag and loopback binding. There must be no existing replication channel or application schema. A pre-existing channel means stop; do not reset it.

Bash

Replica

(
set -euo pipefail
sudo mariadbd --verbose --help >/dev/null
sudo systemctl restart mariadb
systemctl is-active mariadb
sudo mariadb -e 'SELECT VERSION(),@@server_id,@@read_only,@@gtid_domain_id; SHOW DATABASES; SHOW SLAVE STATUS\G'
sudo ss -lntp 'sport = :3306'
)
sudo mariadb -e "SELECT @@datadir;"
How do I know it worked?

102, read_only1, domain 0; empty replication status; only system schemas and loopback3306.

12

LET’S BUILD

Open a history-free primary SQL session

Primary terminal. The next SQL panels run INSIDE this interactive session, not at a Bash prompt. MYSQL_HISTFILE disables client history. Retrieve a unique long random replication secret from your secret manager and substitute the password placeholder inside the SQL session; never put it in shell arguments, shell history, screenshots or tickets. Do not use the literal placeholder. Use a protected terminal and review any session recording/audit policy; replication metadata stores a recoverable credential and belongs in protected backups. Use a64-character random hexadecimal secret (0-9/a-f only) so its SQL literal needs no quote escaping.

Bash

Primary terminal

sudo env MYSQL_HISTFILE=/dev/null mariadb
How do I know it worked?

Local primary MariaDB administrator prompt is open with client history disabled.

13

LET’S BUILD

Create only a restricted replication identity

Inside the primary SQL session. Restrict the account to the replica’s real source IP (no NAT mismatch, no wildcard) and require TLS. Only REPLICATION SLAVE is granted, no application/admin privileges. SSL is required by the account; server-name verification is explicitly required on the replica. No FLUSH PRIVILEGES is necessary after CREATE USER/GRANT.

SQL

Primary SQL session

CREATE USER 'academy_repl'@'10.20.0.11' IDENTIFIED BY 'REPLACE_WITH_UNIQUE_SECRET' REQUIRE SSL;
GRANT REPLICATION SLAVE ON *.* TO 'academy_repl'@'10.20.0.11';
How do I know it worked?

A new host-restricted account with only replication privilege and REQUIRE SSL; no secret printed.

14

LET’S BUILD

Create the small InnoDB dataset and pause writes

Primary SQL session. This dedicated lab has no app, jobs or other privileged writer. Create the only user schema and set read_only before the snapshot; leave it on until row verification. read_only does not fence privileged users, so coordinate the maintenance window and do not issue any further primary writes until the later release step. Never apply this sequence to an existing production database.

SQL

Primary SQL session

CREATE DATABASE academy_lab CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE TABLE academy_lab.replication_probe (id INT PRIMARY KEY, note VARCHAR(80) NOT NULL) ENGINE=InnoDB;
INSERT INTO academy_lab.replication_probe VALUES (1,'consistent-seed');
SET GLOBAL read_only=ON;
SELECT @@read_only;
SELECT * FROM academy_lab.replication_probe;
EXIT;
How do I know it worked?

One InnoDB row, read_only1; no writers remain; return to the shell.

15

LET’S BUILD

Take a consistent seed with its own GTID

Primary Bash. This creates a NEW protected seed directory and refuses a repeat instead of overwriting a previous seed. --single-transaction provides an InnoDB snapshot; --master-data=2 and --gtid record its exact replication position as comments. The brief initial global read lock can wait for long transactions. Do not run concurrent DDL, nontransactional writes, account changes or privileged writes. This is a seed for the dedicated single user schema, not a full-instance disaster-recovery backup. Do not take a later live GTID and combine it with this snapshot.

Bash

Primary

sudo bash -eu <<'SEED'
umask 077
mkdir /root/academy-seed
mariadb-dump --single-transaction --quick --master-data=2 --gtid   --routines --events --triggers --skip-add-drop-table   --databases academy_lab > /root/academy-seed/seed.sql
test -s /root/academy-seed/seed.sql
grep -E "^-- SET GLOBAL gtid_slave_pos='[0-9,-]+';" /root/academy-seed/seed.sql
cd /root/academy-seed
sha256sum seed.sql > SHA256SUMS
sha256sum -c SHA256SUMS
SEED
How do I know it worked?

Successful dump and SHA256 read-back; exactly one commented snapshot GTID. On error retain files, stop, diagnose; do not import a partial seed.

16

LET’S BUILD

Create a protected receiving directory

Replica, logged in as dbadmin. The directory must not already exist. Keep the data transfer on existing authenticated SSH with a verified host key; do not disable host-key verification.

Bash

Replica shell as dbadmin

( umask 077; mkdir "$HOME/academy-seed" )
How do I know it worked?

New dbadmin-owned receiving directory exists with0700 mode.

17

LET’S BUILD

Transfer both files over authenticated SSH

Primary Bash, logged in as the same dbadmin administrator. Each remote write refuses to overwrite an existing file. pipefail propagates local read and SSH failures. Passwordless sudo for these specific reads or a prior local sudo authentication is required; no secrets are echoed. Verify SSH host fingerprints by your existing trusted channel before this operation.

Bash

Primary shell as dbadmin

(
set -euo pipefail
sudo -v
sudo cat /root/academy-seed/seed.sql | ssh -l dbadmin 10.20.0.11 'umask 077; set -C; cat > "$HOME/academy-seed/seed.sql"'
sudo cat /root/academy-seed/SHA256SUMS | ssh -l dbadmin 10.20.0.11 'umask 077; set -C; cat > "$HOME/academy-seed/SHA256SUMS"'
)
How do I know it worked?

Both SSH commands succeed; original protected primary seed is retained.

18

LET’S BUILD

Verify and restore only into the fresh replica

Replica Bash as dbadmin. This guard refuses any pre-existing academy_lab schema, any configured replication channel, any user table in other schemas or any applied GTID. It does not erase them. Verify checksum BEFORE import. Restore runs with session binary logging disabled so local seed writes do not create a competing domain 0 history; read_only stays ON because privileged bootstrap bypass is intentional here. Import errors stop immediately and leave the partial state for diagnosis; do not force or rerun on top of it.

Bash

Replica

(
set -euo pipefail
cd "$HOME/academy-seed"
sha256sum -c SHA256SUMS
test "$(sudo mariadb -NBe "SELECT COUNT(*) FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='academy_lab';")" = 0
test "$(sudo mariadb -NBe "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA NOT IN ('mysql','information_schema','performance_schema','sys');")" = 0
channels=$(sudo mariadb -NBe 'SHOW SLAVE STATUS;')
test -z "$channels"
applied=$(sudo mariadb -NBe 'SELECT @@GLOBAL.gtid_slave_pos;')
test -z "$applied"
{ printf 'SET SESSION sql_log_bin=0;\n'; cat seed.sql; } | sudo mariadb
sudo mariadb -e 'SELECT @@read_only; SELECT * FROM academy_lab.replication_probe;'
)
How do I know it worked?

Checksum OK; fresh-state checks pass; restored row1 consistent-seed and read_only1.

19

LET’S BUILD

Set only the GTID embedded in this seed

Replica Bash. Extract exactly one commented GTID line, reject a malformed/domain-mismatched position and send only that reviewed statement to the local administrator connection. It must be domain 0 with primary server_id101 and a positive sequence. Do not use current_pos, invent a GTID or copy the primary’s current position after the backup. This snapshot has only one GTID domain; multi-source histories need a different plan.

Bash

Replica

(
set -euo pipefail
cd "$HOME/academy-seed"
sha256sum -c SHA256SUMS
position=$(sed -n "s/^-- SET GLOBAL gtid_slave_pos='\(0-101-[1-9][0-9]*\)';$/\1/p" seed.sql)
if [[ ! "$position" =~ ^0-101-[1-9][0-9]*$ ]]; then
  echo 'Missing, multiple or unexpected snapshot GTID; STOP' >&2; exit 1
fi
printf "SET GLOBAL gtid_slave_pos='%s';\n" "$position" | sudo mariadb
sudo mariadb -NBe 'SELECT @@GLOBAL.gtid_slave_pos;'
)
How do I know it worked?

Replica gtid_slave_pos equals the single GTID from its verified seed.

Explore all tutorials ↗