Configure a production-ready MariaDB Galera cluster with three nodes for multi-master replication, automatic failover, and SSL encryption. Includes HAProxy load balancing and comprehensive monitoring for high availability database operations.
Prerequisites
- 3 servers with at least 2GB RAM each
- Root or sudo access
- Network connectivity between nodes
- Basic understanding of SQL and database administration
What this solves
MariaDB Galera Cluster provides synchronous multi-master replication where all nodes can handle both reads and writes simultaneously. This eliminates single points of failure and enables automatic failover when nodes become unavailable. You need this when your application requires high availability database operations with zero downtime and consistent data across multiple geographic locations.
Step-by-step installation
Update system packages and install dependencies
Start by updating your package manager and installing required dependencies for MariaDB Galera cluster.
sudo apt update && sudo apt upgrade -y
sudo apt install -y software-properties-common dirmngr apt-transport-https lsb-release ca-certificates
Add MariaDB repository
Add the official MariaDB repository to get the latest 11.6 version with Galera wsrep support.
curl -o /tmp/mariadb_repo_setup https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
echo "367ad0a16085c59d9a78c9d1ba3c8e9fac4a7b9096a93bbbc626bffbdcb3e04c /tmp/mariadb_repo_setup" | sha256sum -c -
sudo bash /tmp/mariadb_repo_setup --mariadb-server-version="mariadb-11.6"
Install MariaDB Galera cluster packages
Install MariaDB server with Galera wsrep provider and clustering tools.
sudo apt update
sudo apt install -y mariadb-server galera-4 mariadb-client mariadb-backup socat rsync
Stop MariaDB service before cluster configuration
Stop the default MariaDB service to prepare for cluster initialization.
sudo systemctl stop mariadb
sudo systemctl disable mariadb
Configure Galera cluster settings
Create the Galera cluster configuration with wsrep provider settings and node addresses. Replace IP addresses with your actual node IPs.
[galera]
Mandatory settings
wsrep_on=ON
wsrep_cluster_name="production_cluster"
wsrep_cluster_address="gcomm://203.0.113.10,203.0.113.11,203.0.113.12"
wsrep_node_address="203.0.113.10"
wsrep_node_name="galera-node-1"
wsrep_provider=/usr/lib/galera/libgalera_smm.so
Replication settings
wsrep_sst_method=rsync
wsrep_slave_threads=4
wsrep_certify_nonPK=1
wsrep_max_ws_rows=131072
wsrep_max_ws_size=1073741824
wsrep_debug=0
wsrep_convert_LOCK_to_trx=0
wsrep_retry_autocommit=1
wsrep_auto_increment_control=1
wsrep_drupal_282555_workaround=0
wsrep_causal_reads=0
wsrep_sync_wait=0
Binary logging
log-bin=mysql-bin
binlog_format=ROW
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
innodb_locks_unsafe_for_binlog=1
Network and security
bind-address=0.0.0.0
max_connections=500
max_allowed_packet=64M
Performance optimization
innodb_buffer_pool_size=1G
innodb_log_file_size=256M
innodb_flush_log_at_trx_commit=2
innodb_file_per_table=1
Configure SSL encryption for cluster communication
Generate SSL certificates and configure encrypted communication between cluster nodes.
sudo mkdir -p /etc/mysql/ssl
sudo openssl genrsa -out /etc/mysql/ssl/ca-key.pem 4096
sudo openssl req -new -x509 -nodes -days 365000 -key /etc/mysql/ssl/ca-key.pem -out /etc/mysql/ssl/ca-cert.pem -subj "/CN=galera-ca"
sudo openssl req -newkey rsa:4096 -days 365000 -nodes -keyout /etc/mysql/ssl/server-key.pem -out /etc/mysql/ssl/server-req.pem -subj "/CN=galera-server"
sudo openssl x509 -req -in /etc/mysql/ssl/server-req.pem -days 365000 -CA /etc/mysql/ssl/ca-cert.pem -CAkey /etc/mysql/ssl/ca-key.pem -set_serial 01 -out /etc/mysql/ssl/server-cert.pem
sudo chown -R mysql:mysql /etc/mysql/ssl
sudo chmod 600 /etc/mysql/ssl/*-key.pem
Add SSL configuration to Galera
Configure SSL settings for wsrep provider to encrypt cluster communication.
[galera]
SSL configuration for cluster communication
wsrep_provider_options="socket.ssl_key=/etc/mysql/ssl/server-key.pem;socket.ssl_cert=/etc/mysql/ssl/server-cert.pem;socket.ssl_ca=/etc/mysql/ssl/ca-cert.pem;socket.ssl_cipher=AES128-SHA256"
SSL for client connections
ssl-ca=/etc/mysql/ssl/ca-cert.pem
ssl-cert=/etc/mysql/ssl/server-cert.pem
ssl-key=/etc/mysql/ssl/server-key.pem
require_secure_transport=ON
Initialize the first cluster node
Bootstrap the cluster by starting the first node with the new cluster flag.
sudo galera_new_cluster
sudo systemctl status mariadb
Secure MariaDB installation
Run the security script to set root password and remove default accounts.
sudo mysql_secure_installation
Create cluster monitoring user
Create a dedicated user for cluster monitoring and health checks.
sudo mysql -u root -p
CREATE USER 'clustercheck'@'localhost' IDENTIFIED BY 'clustercheck_password';
GRANT PROCESS ON . TO 'clustercheck'@'localhost';
CREATE USER 'galera_backup'@'localhost' IDENTIFIED BY 'backup_password';
GRANT RELOAD, LOCK TABLES, PROCESS, REPLICATION CLIENT ON . TO 'galera_backup'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Configure remaining cluster nodes
On nodes 2 and 3, use the same Galera configuration but update node-specific settings.
[galera]
Same configuration as node 1, but change:
wsrep_node_address="203.0.113.11"
wsrep_node_name="galera-node-2"
[galera]
Same configuration as node 1, but change:
wsrep_node_address="203.0.113.12"
wsrep_node_name="galera-node-3"
Start remaining cluster nodes
Copy SSL certificates to other nodes and start MariaDB to join the cluster.
# Copy SSL certificates from node 1
sudo scp -r root@203.0.113.10:/etc/mysql/ssl /etc/mysql/
sudo chown -R mysql:mysql /etc/mysql/ssl
sudo chmod 600 /etc/mysql/ssl/*-key.pem
Start MariaDB to join cluster
sudo systemctl start mariadb
sudo systemctl enable mariadb
Install and configure HAProxy for load balancing
Set up HAProxy to distribute database connections across cluster nodes with health checks.
sudo apt install -y haproxy
Configure HAProxy for Galera cluster
Create HAProxy configuration with MySQL health checks and load balancing algorithms optimized for database traffic.
global
daemon
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
ssl-default-bind-options ssl-min-ver TLSv1.2
defaults
mode tcp
timeout connect 10s
timeout client 1m
timeout server 1m
option dontlognull
option redispatch
retries 3
maxconn 2000
MySQL read/write cluster
frontend mysql_frontend
bind *:3306
mode tcp
default_backend mysql_cluster_backend
backend mysql_cluster_backend
mode tcp
balance leastconn
option mysql-check user clustercheck
server galera-node-1 203.0.113.10:3306 check weight 1 maxconn 300
server galera-node-2 203.0.113.11:3306 check weight 1 maxconn 300
server galera-node-3 203.0.113.12:3306 check weight 1 maxconn 300
Read-only cluster for reporting
frontend mysql_readonly_frontend
bind *:3307
mode tcp
default_backend mysql_readonly_backend
backend mysql_readonly_backend
mode tcp
balance roundrobin
option mysql-check user clustercheck
server galera-node-1-ro 203.0.113.10:3306 check weight 1 maxconn 300
server galera-node-2-ro 203.0.113.11:3306 check weight 1 maxconn 300
server galera-node-3-ro 203.0.113.12:3306 check weight 1 maxconn 300
Stats interface
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 30s
stats admin if TRUE
Enable and start HAProxy
Start HAProxy service and configure it to start automatically on boot.
sudo systemctl enable --now haproxy
sudo systemctl status haproxy
Configure firewall rules
Open necessary ports for Galera cluster communication and HAProxy load balancing.
sudo ufw allow 3306/tcp comment 'MySQL/MariaDB'
sudo ufw allow 4444/tcp comment 'Galera SST'
sudo ufw allow 4567/tcp comment 'Galera replication'
sudo ufw allow 4568/tcp comment 'Galera IST'
sudo ufw allow 8404/tcp comment 'HAProxy stats'
sudo ufw reload
Verify your setup
Check cluster status and verify all nodes are synchronized and healthy.
# Check cluster size and status
sudo mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_%';"
Verify all nodes are connected
sudo mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
sudo mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_ready';"
sudo mysql -u root -p -e "SHOW STATUS LIKE 'wsrep_connected';"
Test replication
sudo mysql -u root -p -e "CREATE DATABASE test_replication;"
sudo mysql -u root -p -e "SHOW DATABASES;"
Check HAProxy status
curl http://localhost:8404/stats
sudo systemctl status haproxy
Monitor cluster health and performance
Set up monitoring queries to track cluster performance and detect issues early.
# Create monitoring script
sudo tee /usr/local/bin/galera-monitor.sh > /dev/null << 'EOF'
#!/bin/bash
echo "=== Galera Cluster Status ==="
mysql -u clustercheck -pclustercheck_password -e "SHOW STATUS LIKE 'wsrep_cluster_size';"
mysql -u clustercheck -pclustercheck_password -e "SHOW STATUS LIKE 'wsrep_local_state_comment';"
mysql -u clustercheck -pclustercheck_password -e "SHOW STATUS LIKE 'wsrep_flow_control_paused';"
mysql -u clustercheck -pclustercheck_password -e "SHOW STATUS LIKE 'wsrep_cert_deps_distance';"
echo "=== Node Status ==="
mysql -u clustercheck -pclustercheck_password -e "SHOW VARIABLES LIKE 'wsrep_node_name';"
mysql -u clustercheck -pclustercheck_password -e "SHOW STATUS LIKE 'wsrep_local_index';"
EOF
sudo chmod +x /usr/local/bin/galera-monitor.sh
sudo /usr/local/bin/galera-monitor.sh
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| wsrep_ready shows OFF | Node not synchronized with cluster | Check network connectivity and restart MariaDB |
| Cluster size shows less than 3 | Node communication failure | Verify firewall rules and cluster addresses in config |
| SSL connection errors | Certificate mismatch or permissions | Copy certificates correctly and check ownership with ls -la /etc/mysql/ssl/ |
| Split-brain scenario | Network partition between nodes | Stop all nodes, bootstrap from most recent node with galera_new_cluster |
| HAProxy health checks failing | clustercheck user missing or wrong password | Recreate monitoring user and update HAProxy config |
| High wsrep_flow_control_paused | Slow node affecting cluster performance | Check disk I/O and memory on all nodes, consider MariaDB performance optimization |
chown mysql:mysql and appropriate chmod values.Next steps
- Configure MariaDB master-slave replication with SSL encryption for hybrid replication scenarios
- Implement HAProxy SSL termination with Let's Encrypt to secure client connections
- Set up automated MariaDB Galera backup strategies for disaster recovery
- Monitor MariaDB Galera cluster with Prometheus and Grafana for comprehensive observability
- Configure advanced load balancing with ProxySQL for query routing and connection pooling
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default values
NODE_ADDRESS=""
NODE_NAME=""
CLUSTER_ADDRESSES=""
ROOT_PASSWORD=""
# Usage message
usage() {
cat << EOF
Usage: $0 --node-address=IP --node-name=NAME --cluster-addresses=IP1,IP2,IP3 --root-password=PASS
--node-address This node's IP address
--node-name This node's name (e.g., galera-node-1)
--cluster-addresses Comma-separated list of all cluster node IPs
--root-password MariaDB root password
EOF
exit 1
}
# Parse arguments
for arg in "$@"; do
case $arg in
--node-address=*) NODE_ADDRESS="${arg#*=}" ;;
--node-name=*) NODE_NAME="${arg#*=}" ;;
--cluster-addresses=*) CLUSTER_ADDRESSES="${arg#*=}" ;;
--root-password=*) ROOT_PASSWORD="${arg#*=}" ;;
*) usage ;;
esac
done
[[ -z "$NODE_ADDRESS" || -z "$NODE_NAME" || -z "$CLUSTER_ADDRESSES" || -z "$ROOT_PASSWORD" ]] && usage
# Error handling
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Cleaning up...${NC}"
systemctl stop mariadb 2>/dev/null || true
systemctl disable mariadb 2>/dev/null || true
}
trap cleanup ERR
# Check prerequisites
echo -e "${YELLOW}[0/8] Checking prerequisites...${NC}"
[[ $EUID -eq 0 ]] || { echo -e "${RED}This script must be run as root${NC}"; exit 1; }
# Detect distro
if [[ ! -f /etc/os-release ]]; then
echo -e "${RED}Cannot detect OS distribution${NC}"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update -y"
PKG_INSTALL="apt install -y"
FIREWALL_CMD="ufw"
GALERA_LIB="/usr/lib/galera/libgalera_smm.so"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
FIREWALL_CMD="firewall-cmd"
GALERA_LIB="/usr/lib64/galera-4/libgalera_smm.so"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
FIREWALL_CMD="firewall-cmd"
GALERA_LIB="/usr/lib64/galera/libgalera_smm.so"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
FIREWALL_CMD="firewall-cmd"
GALERA_LIB="/usr/lib64/galera-4/libgalera_smm.so"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
# Step 1: Update system
echo -e "${GREEN}[1/8] Updating system packages...${NC}"
$PKG_UPDATE
# Install base dependencies
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL software-properties-common dirmngr apt-transport-https lsb-release ca-certificates wget curl
else
$PKG_INSTALL wget curl epel-release which
fi
# Step 2: Add MariaDB repository
echo -e "${GREEN}[2/8] Adding MariaDB repository...${NC}"
curl -o /tmp/mariadb_repo_setup https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
echo "367ad0a16085c59d9a78c9d1ba3c8e9fac4a7b9096a93bbbc626bffbdcb3e04c /tmp/mariadb_repo_setup" | sha256sum -c -
bash /tmp/mariadb_repo_setup --mariadb-server-version="mariadb-11.6"
# Step 3: Install MariaDB Galera packages
echo -e "${GREEN}[3/8] Installing MariaDB Galera packages...${NC}"
if [[ "$PKG_MGR" == "apt" ]]; then
apt update
$PKG_INSTALL mariadb-server galera-4 mariadb-client mariadb-backup socat rsync
else
$PKG_INSTALL MariaDB-server MariaDB-client galera-4 MariaDB-backup socat rsync
fi
# Step 4: Stop and configure service
echo -e "${GREEN}[4/8] Stopping MariaDB service...${NC}"
systemctl stop mariadb || true
systemctl disable mariadb
# Step 5: Configure Galera cluster
echo -e "${GREEN}[5/8] Configuring Galera cluster...${NC}"
mkdir -p /etc/mysql/conf.d
# Convert comma-separated addresses to gcomm format
GCOMM_ADDRESSES=$(echo "$CLUSTER_ADDRESSES" | sed 's/,/,/g')
cat > /etc/mysql/conf.d/galera.cnf << EOF
[galera]
# Mandatory settings
wsrep_on=ON
wsrep_cluster_name="production_cluster"
wsrep_cluster_address="gcomm://$GCOMM_ADDRESSES"
wsrep_node_address="$NODE_ADDRESS"
wsrep_node_name="$NODE_NAME"
wsrep_provider=$GALERA_LIB
# Replication settings
wsrep_sst_method=rsync
wsrep_slave_threads=4
wsrep_certify_nonPK=1
wsrep_max_ws_rows=131072
wsrep_max_ws_size=1073741824
wsrep_retry_autocommit=1
wsrep_auto_increment_control=1
# Binary logging
log-bin=mysql-bin
binlog_format=ROW
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
# Network and security
bind-address=0.0.0.0
max_connections=500
max_allowed_packet=64M
# Performance optimization
innodb_buffer_pool_size=1G
innodb_log_file_size=256M
innodb_flush_log_at_trx_commit=2
innodb_file_per_table=1
EOF
chown mysql:mysql /etc/mysql/conf.d/galera.cnf
chmod 644 /etc/mysql/conf.d/galera.cnf
# Step 6: Configure SSL
echo -e "${GREEN}[6/8] Configuring SSL encryption...${NC}"
mkdir -p /etc/mysql/ssl
openssl genrsa -out /etc/mysql/ssl/ca-key.pem 4096
openssl req -new -x509 -nodes -days 365000 -key /etc/mysql/ssl/ca-key.pem -out /etc/mysql/ssl/ca-cert.pem -subj "/CN=galera-ca"
openssl req -newkey rsa:4096 -days 365000 -nodes -keyout /etc/mysql/ssl/server-key.pem -out /etc/mysql/ssl/server-req.pem -subj "/CN=galera-server"
openssl x509 -req -in /etc/mysql/ssl/server-req.pem -days 365000 -CA /etc/mysql/ssl/ca-cert.pem -CAkey /etc/mysql/ssl/ca-key.pem -set_serial 01 -out /etc/mysql/ssl/server-cert.pem
chown -R mysql:mysql /etc/mysql/ssl
chmod 750 /etc/mysql/ssl
chmod 640 /etc/mysql/ssl/*
# Step 7: Configure firewall
echo -e "${GREEN}[7/8] Configuring firewall...${NC}"
if [[ "$FIREWALL_CMD" == "ufw" ]]; then
ufw allow 3306/tcp # MariaDB
ufw allow 4444/tcp # SST
ufw allow 4567/tcp # Galera
ufw allow 4568/tcp # IST
elif [[ "$FIREWALL_CMD" == "firewall-cmd" ]] && systemctl is-active --quiet firewalld; then
firewall-cmd --permanent --add-port=3306/tcp
firewall-cmd --permanent --add-port=4444/tcp
firewall-cmd --permanent --add-port=4567/tcp
firewall-cmd --permanent --add-port=4568/tcp
firewall-cmd --reload
fi
# Configure SELinux if present
if command -v getenforce >/dev/null 2>&1 && [[ "$(getenforce)" != "Disabled" ]]; then
setsebool -P mysql_connect_any 1
semanage port -a -t mysqld_port_t -p tcp 4444 2>/dev/null || true
semanage port -a -t mysqld_port_t -p tcp 4567 2>/dev/null || true
semanage port -a -t mysqld_port_t -p tcp 4568 2>/dev/null || true
fi
# Step 8: Initialize cluster
echo -e "${GREEN}[8/8] Initializing cluster...${NC}"
echo -e "${YELLOW}Starting bootstrap on first node (if this is node 1)...${NC}"
# Start with bootstrap for first node setup
galera_new_cluster || {
echo -e "${YELLOW}Bootstrap failed, trying regular start...${NC}"
systemctl start mariadb
}
# Secure installation
mysql -e "SET PASSWORD FOR 'root'@'localhost' = PASSWORD('$ROOT_PASSWORD');"
mysql -e "DELETE FROM mysql.user WHERE User='';"
mysql -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');"
mysql -e "DROP DATABASE IF EXISTS test;"
mysql -e "DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';"
mysql -e "FLUSH PRIVILEGES;"
systemctl enable mariadb
# Verification
echo -e "${GREEN}Installation completed successfully!${NC}"
echo -e "${YELLOW}Verifying cluster status...${NC}"
if mysql -uroot -p"$ROOT_PASSWORD" -e "SHOW STATUS LIKE 'wsrep_cluster_size';" 2>/dev/null; then
echo -e "${GREEN}✓ MariaDB Galera cluster is running${NC}"
echo -e "${YELLOW}Cluster status:${NC}"
mysql -uroot -p"$ROOT_PASSWORD" -e "SHOW STATUS LIKE 'wsrep_%';" | grep -E "(cluster_size|ready|connected)"
else
echo -e "${RED}✗ Failed to verify cluster status${NC}"
exit 1
fi
echo -e "${GREEN}MariaDB Galera Cluster installation completed!${NC}"
echo -e "${YELLOW}Next steps:${NC}"
echo "1. Install and configure this script on other cluster nodes"
echo "2. For additional nodes, use: systemctl start mariadb (not galera_new_cluster)"
echo "3. Monitor cluster with: mysql -uroot -p'$ROOT_PASSWORD' -e \"SHOW STATUS LIKE 'wsrep_cluster_size';\""
Review the script before running. Execute with: bash install.sh