Community-developed fork of MySQL providing a drop-in replacement with additional storage engines, performance improvements, and Galera-based multi-master clustering.
Addresses below are RFC 5737 documentation ranges or placeholders - swap in your own.
Table of Contents#
- Overview
- Setup
- Installation
- Security Hardening
- Performance Tuning
- SSL/TLS Configuration
- Galera Cluster Setup
- Backup Strategies
- Replication
- Database Management
- Monitoring
- Troubleshooting
- See Also
- Sources
1. Overview#
MariaDB is a relational database management system forked from MySQL by its original developers. It maintains protocol and API compatibility with MySQL while adding features such as:
- Galera Cluster for synchronous multi-master replication
- Aria storage engine as a crash-safe replacement for MyISAM
- ColumnStore for analytical workloads
- Temporal tables and Oracle compatibility mode
- Thread pool for improved connection handling under load
MariaDB follows its own versioning (10.x, 11.x) but remains compatible with MySQL client libraries and most MySQL tools.
2. Setup#
2.1 Environment Variables#
Define variables on each node before proceeding. Replace placeholders with real values:
export DB_PWD="<mariadb-root-password>"
export DB_REPLICA=$(openssl rand -base64 24)
export INIT_PASS=$(openssl rand -base64 24)
export CURRENT_NODE="node01"
export CURRENT_IP="192.0.2.111"
export IP_NODE01="192.0.2.111"
export IP_NODE02="192.0.2.112"
export IP_NODE03="192.0.2.113"2.2 Storage Preparation#
Dedicate a volume for database files to isolate I/O:
# LVM setup
pvcreate /dev/sdb
vgcreate database /dev/sdb
lvcreate -l 100%FREE -n data database
mkfs.xfs -f -i size=512 /dev/mapper/database-data
# Persistent mounts
cat >> /etc/fstab <<'EOF'
# MariaDB data volume
/dev/mapper/database-data /database xfs defaults 1 2
/database/mysql /var/lib/mysql none bind 0 0
EOF
mkdir -p /database/mysql /var/lib/mysql
mount -a3. Installation#
3.1 RHEL/Rocky/Alma#
dnf install -y MariaDB-server MariaDB-client MariaDB-backup galera-4
firewall-cmd --permanent --add-service=mysql
firewall-cmd --permanent --add-port={4444,4567,4568}/tcp
firewall-cmd --reload
systemctl enable --now mariadb3.2 Debian/Ubuntu#
apt install -y mariadb-server mariadb-client mariadb-backup galera-4
systemctl enable --now mariadb3.3 Initial Secure Installation#
mariadb-secure-installationThis removes anonymous users, disables remote root login, removes the test database, and reloads privilege tables.
4. Security Hardening#
4.1 Beyond mariadb-secure-installation#
-- Restrict root to localhost with strong password
ALTER USER 'root'@'localhost' IDENTIFIED BY '<strong-password>';
-- Remove any wildcard host users
SELECT User, Host FROM mysql.user WHERE Host = '%';
DROP USER IF EXISTS ''@'%';
-- Disable file loading (prevents LOAD DATA LOCAL INFILE attacks)
SET GLOBAL local_infile = 0;
-- Audit installed plugins
SELECT plugin_name, plugin_status FROM information_schema.plugins
WHERE plugin_status = 'ACTIVE';4.2 my.cnf Security Settings#
Add to /etc/my.cnf.d/security.cnf:
[mysqld]
# Disable symbolic links
symbolic-links=0
# Disable local infile
local-infile=0
# Require secure transport for all connections
# (enable after configuring SSL)
# require_secure_transport=ON
# Limit connection errors before blocking host
max_connect_errors=10
# Disable LOAD DATA LOCAL
local-infile=0
# Log all connections and queries (for audit; disable in high-throughput production)
# general_log=1
# general_log_file=/var/log/mysql/general.log
# Enable error log
log_error=/var/log/mysql/error.log4.3 Firewall and Network#
# Only allow database traffic from application subnets
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.0.2.0/24" port port="3306" protocol="tcp" accept'
firewall-cmd --reload4.4 Audit Plugin#
-- Install the audit plugin for compliance logging
INSTALL SONAME 'server_audit';
SET GLOBAL server_audit_logging=ON;
SET GLOBAL server_audit_events='CONNECT,QUERY_DDL,QUERY_DML';
SET GLOBAL server_audit_file_path='/var/log/mysql/audit.log';
SET GLOBAL server_audit_file_rotate_size=10000000;
SET GLOBAL server_audit_file_rotations=10;5. Performance Tuning#
5.1 InnoDB Settings#
Create /etc/my.cnf.d/performance.cnf:
[mysqld]
# InnoDB buffer pool - set to ~70-80% of available RAM
innodb_buffer_pool_size=4G
innodb_buffer_pool_instances=4
# Log file size - larger = better write performance, longer recovery
innodb_log_file_size=1G
innodb_log_buffer_size=64M
# Flush method - O_DIRECT avoids double buffering
innodb_flush_method=O_DIRECT
# Flush at commit: 1=safe, 2=faster (flush every second)
innodb_flush_log_at_trx_commit=1
# I/O capacity
innodb_io_capacity=2000
innodb_io_capacity_max=4000
# File per table for easier management
innodb_file_per_table=ON
# Thread concurrency (0=automatic)
innodb_thread_concurrency=0
# Read-ahead
innodb_read_ahead_threshold=565.2 Connection and Thread Settings#
[mysqld]
# Connection limits
max_connections=500
max_user_connections=450
# Thread pool (MariaDB-specific)
thread_handling=pool-of-threads
thread_pool_size=16
thread_pool_max_threads=1000
# Temporary tables
tmp_table_size=256M
max_heap_table_size=256M
# Query cache (disable for write-heavy workloads)
query_cache_type=0
query_cache_size=0
# Sort and join buffers
sort_buffer_size=4M
join_buffer_size=4M
read_buffer_size=2M
read_rnd_buffer_size=8M5.3 Slow Query Log#
[mysqld]
slow_query_log=1
slow_query_log_file=/var/log/mysql/slow.log
long_query_time=1
log_queries_not_using_indexes=16. SSL/TLS Configuration#
6.1 Generate Certificates#
mkdir -p /etc/mysql/ssl && cd /etc/mysql/ssl
# CA
openssl genrsa -out ca-key.pem 4096
openssl req -new -x509 -days 3650 -key ca-key.pem -out ca.pem \
-subj "/CN=MariaDB CA"
# Server certificate
openssl genrsa -out server-key.pem 4096
openssl req -new -key server-key.pem -out server-req.pem \
-subj "/CN=$(hostname)"
openssl x509 -req -days 3650 -in server-req.pem \
-CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem
# Client certificate
openssl genrsa -out client-key.pem 4096
openssl req -new -key client-key.pem -out client-req.pem \
-subj "/CN=MariaDB Client"
openssl x509 -req -days 3650 -in client-req.pem \
-CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem
chown mysql:mysql /etc/mysql/ssl/*
chmod 600 /etc/mysql/ssl/*-key.pem6.2 Server Configuration#
[mysqld]
ssl_ca=/etc/mysql/ssl/ca.pem
ssl_cert=/etc/mysql/ssl/server-cert.pem
ssl_key=/etc/mysql/ssl/server-key.pem
tls_version=TLSv1.2,TLSv1.3
# Enforce TLS for all remote connections
require_secure_transport=ON6.3 Client Configuration#
[client]
ssl_ca=/etc/mysql/ssl/ca.pem
ssl_cert=/etc/mysql/ssl/client-cert.pem
ssl_key=/etc/mysql/ssl/client-key.pem6.4 Verify SSL#
SHOW VARIABLES LIKE '%ssl%';
-- Check current connection
STATUS;
-- Look for "SSL: Cipher in use is TLS_AES_256_GCM_SHA384"7. Galera Cluster Setup#
7.1 Configuration#
Create /etc/my.cnf.d/galera.cnf on each node:
[mariadb]
binlog_format=ROW
default-storage-engine=innodb
innodb_autoinc_lock_mode=2
bind-address=0.0.0.0
# Galera provider
wsrep_on=ON
wsrep_provider=/usr/lib64/galera-4/libgalera_smm.so
# Cluster identity
wsrep_cluster_name="<cluster-name>"
wsrep_cluster_address="gcomm://<ip-node01>,<ip-node02>,<ip-node03>"
# SST method - mariabackup is non-blocking (replaces deprecated rsync)
wsrep_sst_method=mariabackup
wsrep_sst_auth="mariabackup:<sst-password>"
# Node identity
wsrep_node_address="<current-ip>"
wsrep_node_name="<current-node>"
# Parallel replication
wsrep_slave_threads=4
# Flow control tuning
wsrep_provider_options="gcache.size=1G; gcs.fc_limit=256; gcs.fc_factor=0.8"7.2 Create SST User#
Before bootstrapping, create the SST user on the first node:
CREATE USER 'mariabackup'@'localhost' IDENTIFIED BY '<sst-password>';
GRANT RELOAD, PROCESS, LOCK TABLES, BINLOG MONITOR, REPLICATION CLIENT ON *.* TO 'mariabackup'@'localhost';
FLUSH PRIVILEGES;7.3 Bootstrap the Cluster#
# Stop MariaDB on all nodes first
systemctl stop mariadb
# On the FIRST node only
galera_new_cluster
# Verify
mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_cluster_size'"
# Start remaining nodes one at a time
# On node02:
systemctl start mariadb
mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_cluster_size'"
# On node03:
systemctl start mariadb
mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_cluster_size'"7.4 Galera Status Monitoring#
-- Key status variables
SHOW STATUS LIKE 'wsrep_%';
-- Most important variables
SHOW STATUS WHERE Variable_name IN (
'wsrep_cluster_size',
'wsrep_cluster_status',
'wsrep_connected',
'wsrep_ready',
'wsrep_local_state_comment',
'wsrep_flow_control_paused',
'wsrep_local_recv_queue_avg',
'wsrep_local_send_queue_avg'
);8. Backup Strategies#
8.1 Logical Backup (mysqldump)#
# Full backup with routines and triggers
mysqldump -u root -p --all-databases --routines --triggers \
--single-transaction --quick > /backup/full-$(date +%Y%m%d).sql
# Single database
mysqldump -u root -p --single-transaction <database> > /backup/<database>.sql
# Restore
mysql -u root -p < /backup/full-20260322.sql
mysql -u root -p <database> < /backup/<database>.sql8.2 Physical Backup (mariabackup)#
Preferred for large databases; non-blocking, supports incremental:
# Full backup
mariabackup --backup --target-dir=/backup/full \
--user=mariabackup --password=<sst-password>
# Prepare the backup (apply redo log)
mariabackup --prepare --target-dir=/backup/full
# Incremental backup (based on last full)
mariabackup --backup --target-dir=/backup/inc1 \
--incremental-basedir=/backup/full \
--user=mariabackup --password=<sst-password>
# Prepare incremental
mariabackup --prepare --target-dir=/backup/full \
--incremental-dir=/backup/inc18.3 Restore from mariabackup#
systemctl stop mariadb
rm -rf /var/lib/mysql/*
mariabackup --copy-back --target-dir=/backup/full
chown -R mysql:mysql /var/lib/mysql
systemctl start mariadb8.4 Binary Log Backup (Point-in-Time Recovery)#
[mysqld]
log_bin=/var/log/mysql/mariadb-bin
binlog_format=ROW
expire_logs_days=14
max_binlog_size=256M# List binary logs
mysqlbinlog --list /var/log/mysql/mariadb-bin.index
# Point-in-time recovery
mysqlbinlog --start-datetime="2026-03-22 10:00:00" \
--stop-datetime="2026-03-22 11:00:00" \
/var/log/mysql/mariadb-bin.000042 | mysql -u root -p8.5 Automated Backup Script#
#!/bin/bash
# /usr/local/bin/mariadb-backup.sh
BACKUP_DIR="/backup/mariadb"
RETENTION_DAYS=14
DATE=$(date +%Y%m%d-%H%M%S)
mkdir -p "${BACKUP_DIR}"
# Full physical backup
mariabackup --backup --target-dir="${BACKUP_DIR}/${DATE}" \
--user=mariabackup --password="<sst-password>"
mariabackup --prepare --target-dir="${BACKUP_DIR}/${DATE}"
# Compress
tar -czf "${BACKUP_DIR}/${DATE}.tar.gz" -C "${BACKUP_DIR}" "${DATE}"
rm -rf "${BACKUP_DIR}/${DATE}"
# Retention
find "${BACKUP_DIR}" -name "*.tar.gz" -mtime +${RETENTION_DAYS} -delete9. Replication#
9.1 Standard Async Replication#
On the primary:
-- Enable binary logging (in my.cnf: log_bin, server_id)
CREATE USER 'replication'@'<replica-ip>' IDENTIFIED BY '<repl-password>';
GRANT REPLICATION SLAVE ON *.* TO 'replication'@'<replica-ip>';
FLUSH PRIVILEGES;
SHOW MASTER STATUS;
-- Note File and PositionOn the replica:
CHANGE MASTER TO
MASTER_HOST='<primary-ip>',
MASTER_USER='replication',
MASTER_PASSWORD='<repl-password>',
MASTER_LOG_FILE='<binlog_file>',
MASTER_LOG_POS=<position>;
START SLAVE;
SHOW SLAVE STATUS\G9.2 Monitoring Replication Lag#
-- On replica
SHOW SLAVE STATUS\G
-- Key fields:
-- Seconds_Behind_Master
-- Slave_IO_Running
-- Slave_SQL_Running
-- Last_Error
-- Galera-specific lag
SHOW STATUS LIKE 'wsrep_local_recv_queue_avg';10. Database Management#
10.1 Creating Users and Databases#
CREATE DATABASE <database_name> CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER '<username>'@'<host>' IDENTIFIED BY '<password>';
GRANT ALL PRIVILEGES ON <database_name>.* TO '<username>'@'<host>';
FLUSH PRIVILEGES;10.2 Exporting and Importing#
# Export
mysqldump -u <username> -p <database_name> > <database_name>.sql
# Import
mysql -u <username> -p <database_name> < <database_name>.sql10.3 Permission Management#
-- Grant specific privileges
GRANT SELECT, INSERT, UPDATE ON <database>.<table> TO '<user>'@'<host>';
-- Revoke privileges
REVOKE DELETE ON <database>.<table> FROM '<user>'@'<host>';
-- View grants
SHOW GRANTS FOR '<user>'@'<host>';
-- List all users
SELECT User, Host, plugin FROM mysql.user;10.4 Common Commands#
| Command | Description |
|---|---|
SHOW DATABASES; | List all databases |
USE <database>; | Switch database |
SHOW TABLES; | List tables in current database |
DESCRIBE <table>; | Show table structure |
SHOW PROCESSLIST; | Show active connections |
SHOW ENGINE INNODB STATUS\G | InnoDB internals |
SHOW GLOBAL STATUS; | Server status counters |
SHOW GLOBAL VARIABLES; | Server configuration |
11. Monitoring#
11.1 Key Health Queries#
-- Active connections
SHOW STATUS LIKE 'Threads_connected';
-- Buffer pool hit ratio (should be > 99%)
SHOW STATUS LIKE 'Innodb_buffer_pool_read%';
-- Hit ratio = 1 - (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)
-- Table locks
SHOW STATUS LIKE 'Table_locks%';
-- Slow queries
SHOW STATUS LIKE 'Slow_queries';11.2 Galera-Specific Monitoring#
-- Cluster health at a glance
SELECT VARIABLE_NAME, VARIABLE_VALUE
FROM information_schema.GLOBAL_STATUS
WHERE VARIABLE_NAME IN (
'wsrep_cluster_size',
'wsrep_cluster_status',
'wsrep_ready',
'wsrep_connected',
'wsrep_local_state_comment',
'wsrep_flow_control_paused',
'wsrep_cert_deps_distance'
);12. Troubleshooting#
| Issue | Cause | Solution |
|---|---|---|
Access denied for user | Wrong credentials or missing GRANT | Verify user exists with SELECT User,Host FROM mysql.user; re-grant privileges |
Galera node stuck in Joining | SST transfer failed or timed out | Check donor node logs; ensure mariabackup user exists; verify network/firewall |
wsrep_cluster_status = non-Primary | Node lost quorum (majority unreachable) | If safe: SET GLOBAL wsrep_provider_options='pc.bootstrap=YES' on one node |
| Replication lag growing | Slow replica I/O or single-threaded apply | Enable parallel replication (wsrep_slave_threads); check disk I/O on replica |
Too many connections | Connection pool exhaustion | Increase max_connections; use connection pooling (ProxySQL, MaxScale) |
| InnoDB buffer pool too small | Frequent disk reads, low hit ratio | Increase innodb_buffer_pool_size to 70-80% of RAM |
| Binary log filling disk | expire_logs_days not set or too high | Set expire_logs_days=7; run PURGE BINARY LOGS BEFORE NOW() - INTERVAL 7 DAY |
Galera flow_control_paused > 0.1 | Slowest node throttling the cluster | Tune gcs.fc_limit; investigate slow node (disk, CPU, network) |