wsrep.cnf Configuration Deep Dive: Loading Order, Parameter Domains, and Validation
This deep dive builds on the node lifecycle model described in Galera Cluster Setup & Node Management, and solves one specific operational problem: how to turn wsrep.cnf from a hand-edited file that silently drifts into a version-controlled, validated artifact that produces deterministic cluster behavior on every node. In a synchronous multi-master group, a single mistyped provider option — a missing gcomm:// prefix, an undersized GCache, an over-aggressive eviction timeout — does not fail loudly at edit time. It fails hours later as a certification storm, a full State Snapshot Transfer that desyncs a donor, or a node that quietly starts standalone and accepts writes no peer will ever see. This page is the configuration reference for database administrators, DevOps engineers, and platform teams who need to reason about load precedence, tune the parameters that actually move cluster stability, and gate every change through validation before a service restart.
Concept: How MariaDB Assembles Galera Configuration
wsrep.cnf is not a file MariaDB reads on its own. The server parses a single configuration tree rooted at my.cnf (or mariadb.cnf), and wsrep.cnf is pulled into that tree by an !include or !includedir directive. Every Galera directive ultimately lands in the [mysqld] (or [galera]) section of that merged tree, and the value that wins is the last one parsed. Understanding that ordering is the difference between a predictable rollout and a configuration that behaves differently depending on which package installed which drop-in.
The effective configuration is layered, lowest precedence first:
- Base server defaults — distribution drop-ins such as
/etc/mysql/mariadb.conf.d/50-server.cnf. - Galera-specific overrides — your
/etc/mysql/wsrep.cnf(or a60-galera.cnfdrop-in), included after the base file. - Runtime injection — systemd
EnvironmentFile,MYSQLD_OPTS, container entrypoint arguments, and explicit command-line flags, which override everything parsed from disk.
[mysqld] tree before the provider ever loads. Because the last value parsed for a key wins, a stray runtime override silently beats a correct wsrep.cnf — so own exactly one layer per parameter.Because later layers overwrite earlier keys silently, the failure mode is not a parse error — it is a value you did not expect. A wsrep_cluster_address set correctly in wsrep.cnf but overridden by a leftover MYSQLD_OPTS in a systemd drop-in will send the node to the wrong group with no warning in the log. The rule that keeps this manageable: own exactly one layer per parameter. Put Galera directives in one drop-in, keep runtime injection reserved for values that genuinely vary per boot (a bootstrap flag, a templated node address), and never split ownership of a single key across layers. The provider options string in particular is a single semicolon-delimited value, so a second declaration replaces the whole string rather than merging into it — a subtlety covered in depth in configuring wsrep_provider_options for low latency.
Prerequisites & Environment Requirements
Configuration changes to a live group are unforgiving because several provider options are read only at provider load and must match across every member. Validate the following before you touch wsrep.cnf:
Software versions
- MariaDB 10.6 LTS or later (11.4 LTS recommended for new builds), with the Galera 4 provider
libgalera_smm.sofrom the MariaDB server package. mariabackupinstalled on every node — it is the executable the default SST method invokes; a missing binary turns the first join into a hard failure.- Identical major/minor MariaDB versions across the group. Provider option names and defaults can shift between releases, so a value that is valid on one node may be rejected on another.
Network ports — the provider binds these, so a config change that moves a listen address must be matched by firewall state:
| Port | Protocol | Purpose |
|---|---|---|
| 3306 | TCP | Client / SQL traffic |
| 4567 | TCP + UDP | Group communication (gcomm) and write-set replication |
| 4568 | TCP | Incremental State Transfer (IST) |
| 4444 | TCP | State Snapshot Transfer (SST) |
Locking these ports to known peers is covered in Network Security & Firewall Rules for Galera; a provider option such as ist.recv_addr is inert if the corresponding port is closed.
System settings
- A dedicated SST account replicated across the group, referenced by
wsrep_sst_auth, and injected from a secrets manager rather than committed in plaintext. - Write access to a single, version-controlled drop-in directory so every node renders the same template.
- Node hardware sized so
wsrep_slave_threadsand GCache values are realistic for the machine — capacity planning lives in Galera Cluster Hardware Requirements.
Step-by-Step: Author, Validate, and Roll Out a Change
The workflow is: render the file from a template, parse it, dry-run it against the MariaDB binary, then apply it with a rolling restart so the group never loses quorum.
Step 1 — Establish the base identity block
These directives define the consensus namespace and node addressing. An incorrect value here does not degrade performance — it prevents quorum entirely or forces the node standalone. Keep them in one drop-in, one owner:
[mysqld]
# --- Cluster identity & topology ---
wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_cluster_name="prod-galera-primary"
wsrep_cluster_address="gcomm://10.0.1.10,10.0.1.11,10.0.1.12"
wsrep_node_name="db-node-01"
wsrep_node_address="10.0.1.10"
The gcomm:// prefix on wsrep_cluster_address is mandatory — omit it and the provider refuses to replicate. wsrep_node_name must be deterministic and map directly to your inventory (Ansible hostname, Kubernetes pod label, or cloud instance ID) so telemetry and log lines are attributable. Only the very first node of a brand-new group uses wsrep_cluster_address="gcomm://" to bootstrap; the sequencing that prevents a second bootstrap and a split group is detailed in Bootstrapping Your First Galera Cluster.
Step 2 — Add the state-transfer block
State transfer decides how a fresh or lagging node synchronizes. The method and the GCache size together determine whether a rejoin is a fast Incremental State Transfer or a full snapshot:
[mysqld]
# --- State transfer & synchronization ---
wsrep_sst_method=mariabackup
wsrep_sst_auth="sstuser:SecurePassphrase!"
wsrep_provider_options="gcache.size=2G; gcache.page_size=256M; ist.recv_addr=10.0.1.10:4568"
mariabackup is the production standard: it streams physical InnoDB pages without a full table lock, so the donor stays writable. gcache.size must exceed the maximum write volume expected during a node outage — if the joiner’s required sequence number has already aged out of the ring buffer, the provider falls back to a full SST with the I/O and network cost that implies. The complete SST/IST decision model, including method benchmarks, is documented in Initial Data Synchronization Methods, with large-dataset tuning in Choosing the Right SST Method for Large Datasets.
Step 3 — Add flow-control and apply tuning
Galera uses certification-based replication: every node validates each write-set independently, and when the apply queue on a slow node grows, flow control throttles the whole group to protect consistency. These knobs set where that throttling engages:
[mysqld]
# --- Flow control & certification ---
wsrep_slave_threads=4
wsrep_certify_nonPK=ON
wsrep_provider_options="gcache.size=2G; gcs.fc_limit=256; gcs.fc_factor=0.8; gcs.fc_single_primary=YES"
Note that the provider options string is one value — if you also set gcache.size in Step 2, both gcache and gcs keys must live in the same wsrep_provider_options declaration, because a second declaration replaces the first entirely rather than appending. The certification mechanism these values throttle is explained in the Write-Set Certification Process Explained reference.
Step 4 — Validate before you restart
Never restart a live node on an unparsed change. Confirm the merged tree is syntactically valid and inspect the effective values the daemon would load:
# Validate the full merged configuration without starting the daemon
mariadbd --validate-config --defaults-file=/etc/mysql/mariadb.cnf
# Inspect effective wsrep_ values the daemon would apply
mariadbd --verbose --help 2>/dev/null | grep -A 40 "wsrep_"
A non-zero exit from --validate-config means the restart would fail; treat it as a hard gate in your pipeline. Startup-time parse and provider-load errors, and how to read them in the log, are catalogued in Handling Galera Startup Errors & Logs.
Step 5 — Apply with a rolling restart
Restart one node at a time and wait for it to reach Synced before moving to the next, so the group never drops below quorum. The controlled shutdown that keeps each rejoin on the fast IST path — rather than forcing a full SST every restart — is detailed in Graceful Node Join and Leave Procedures.
Parameter Deep-Dive: The Knobs That Move Stability
Beyond identity, a handful of provider options account for most production incidents. Tune these deliberately; leave the rest at defaults.
| Parameter | Type | Default | Production value | Why it matters |
|---|---|---|---|---|
gcache.size |
size | 128M | 2G–8G | Sizes the write-set ring buffer that IST replays from. Too small forces full SST on every rejoin. |
gcs.fc_limit |
integer | 16 | 128–512 | Apply-queue depth before flow control throttles the group. The default stalls high-throughput OLTP. |
gcs.fc_factor |
float 0–1 | 0.5 | 0.8 | Queue fraction at which throttling releases; 0.8 resumes writes once the queue drains to 80% of fc_limit. |
wsrep_slave_threads |
integer | 1 | 4–16 | Parallel apply threads. Match to cores but never exceed innodb_thread_concurrency, or lock contention rises. |
evs.suspect_timeout |
period | PT5S | PT5S–PT15S | How long before a silent peer is suspected. Too aggressive on high-latency links causes false evictions. |
evs.inactive_timeout |
period | PT15S | PT15S–PT30S | Hard deadline before a peer is declared dead and removed from the group. |
gcache.size is the highest-leverage value for painless node operations: it must exceed the write volume produced across your longest expected maintenance window. gcs.fc_limit and gcs.fc_factor govern the throttle hysteresis — a low limit with a high factor produces oscillation, so raise the limit for write-heavy workloads and keep the factor at 0.8. wsrep_slave_threads should track available cores for parallel certification apply; over-provisioning increases InnoDB row-lock contention rather than throughput. The EVS timeouts (evs.suspect_timeout, evs.inactive_timeout) exist to tolerate cross-AZ and cloud network jitter — the defaults assume a low-latency LAN and evict too eagerly across availability zones. Low-latency variants of these same settings are compared in configuring wsrep_provider_options for low latency.
Verification & Health Checks
After a rolling change, confirm the group converged before routing traffic. Start with the live status variables:
-- Group health and this node's role
SHOW GLOBAL STATUS WHERE Variable_name IN (
'wsrep_cluster_size', -- active member count; must equal node count
'wsrep_cluster_status', -- must be 'Primary'
'wsrep_local_state_comment', -- must be 'Synced'
'wsrep_flow_control_paused', -- near 0.0; sustained > 0.1 means throttling
'wsrep_evs_state' -- must be 'OPERATIONAL'
);
Then confirm the value you set is the value the running provider actually loaded — the whole point of the precedence discipline in Step 1:
SHOW GLOBAL VARIABLES LIKE 'wsrep_provider_options';
A Python probe makes this a gate rather than a manual check. This one uses PyMySQL and handles the two write-conflict error codes Galera surfaces under contention — 1213 (deadlock, a certification conflict) and 1205 (lock wait timeout) — so a busy node under flow control does not read as a hard failure:
import sys
import pymysql
EXPECTED = {
"wsrep_cluster_status": "Primary",
"wsrep_local_state_comment": "Synced",
}
def check_node(host: str) -> bool:
try:
conn = pymysql.connect(
host=host, user="monitor", password="secret",
connect_timeout=5, read_timeout=5,
)
except pymysql.err.OperationalError as exc:
print(f"[FATAL] {host} unreachable: {exc}", file=sys.stderr)
return False
try:
with conn.cursor() as cur:
cur.execute(
"SHOW GLOBAL STATUS WHERE Variable_name IN "
"('wsrep_cluster_status', 'wsrep_local_state_comment', "
"'wsrep_flow_control_paused')"
)
status = {name: val for name, val in cur.fetchall()}
except pymysql.err.OperationalError as exc:
# 1213 = certification deadlock, 1205 = lock wait timeout.
# Under heavy write load these are transient, not config faults.
if exc.args and exc.args[0] in (1213, 1205):
print(f"[WARN] {host} contended ({exc.args[0]}); retry", file=sys.stderr)
return False
raise
finally:
conn.close()
for key, want in EXPECTED.items():
if status.get(key) != want:
print(f"[FAIL] {host} {key}={status.get(key)} (want {want})")
return False
if float(status.get("wsrep_flow_control_paused", 0)) > 0.1:
print(f"[WARN] {host} is flow-control throttled")
print(f"[OK] {host} Synced and Primary")
return True
if __name__ == "__main__":
ok = all(check_node(h) for h in sys.argv[1:])
sys.exit(0 if ok else 1)
Reusable versions of this probe, wired into alerting, are covered in Monitoring Galera Cluster State with Python and the broader Automated Node Health Monitoring guide.
Automation Integration: Treat wsrep.cnf as Code
Manual edits guarantee drift across a fleet. Render the file from a template and gate every change on validation before it reaches a node.
The following pattern parses the rendered file, enforces that the mandatory identity keys are present, and dry-runs the merged configuration against the MariaDB binary — the same three-stage gate from Step 4, wrapped for a CI pipeline:
import configparser
import subprocess
import sys
REQUIRED = {
"wsrep_cluster_name", "wsrep_cluster_address",
"wsrep_node_name", "wsrep_node_address", "wsrep_sst_method",
}
def validate_wsrep_config(config_path: str) -> bool:
parser = configparser.ConfigParser(strict=False)
parser.read(config_path)
if not parser.has_section("mysqld"):
print("ERROR: missing [mysqld] section", file=sys.stderr)
return False
missing = REQUIRED - set(parser.options("mysqld"))
if missing:
print(f"ERROR: missing keys: {', '.join(sorted(missing))}", file=sys.stderr)
return False
result = subprocess.run(
["mariadbd", "--validate-config",
"--defaults-file=/etc/mysql/mariadb.cnf"],
capture_output=True, text=True,
)
if result.returncode != 0:
print(f"SYNTAX ERROR:\n{result.stderr}", file=sys.stderr)
return False
return True
if __name__ == "__main__":
ok = validate_wsrep_config("/etc/mysql/wsrep.cnf")
print("Configuration validated." if ok else "Validation failed.")
sys.exit(0 if ok else 1)
For an Ansible-driven rollout, template wsrep_node_name and wsrep_node_address from inventory, run this gate as a pre-restart task, and serialize the play so only one node restarts at a time — the end-to-end idempotent pattern is documented in Automating Node Provisioning with Ansible. Complement it with drift detection: periodically diff live SHOW GLOBAL VARIABLES LIKE 'wsrep_%' output against the rendered template and alert on any divergence.
Troubleshooting
These are the failures that trace directly to a wsrep.cnf mistake rather than a hardware or network fault.
| Symptom in the log | Root cause | Remediation |
|---|---|---|
WSREP: Failed to read 'ready <addr>' from: wsrep_sst_mariabackup |
SST auth mismatch or mariabackup not installed on a peer |
Confirm wsrep_sst_auth matches the replicated SST account and install mariabackup on every node |
WSREP: Flow control paused sustained |
gcs.fc_limit too low or wsrep_slave_threads undersized |
Raise gcs.fc_limit to 256+, increase slave threads, watch wsrep_local_recv_queue_avg |
WSREP: view(view_id(NON_PRIM,...)) |
Network partition or EVS timeouts too aggressive | Verify inter-node reachability on 4567, raise evs.suspect_timeout/evs.inactive_timeout, check firewall rules |
WSREP: gcache page size mismatch |
gcache.page_size changed without a full-group restart |
Realign the value across all nodes and perform a coordinated restart |
Node starts but wsrep_cluster_size=1 |
gcomm:// prefix missing, or a runtime override sent it standalone |
Restore the gcomm:// prefix and audit MYSQLD_OPTS/systemd drop-ins for a stray wsrep_cluster_address |
For a node stuck reporting the wrong wsrep_local_state_comment after a config change, the state-machine recovery steps are in Fixing wsrep_local_state_comment Issues. For the exact byte sequences the provider emits at startup, see Handling Galera Startup Errors & Logs.
Related
- Galera Cluster Setup & Node Management — the parent guide to node lifecycle, provisioning, and operations
- Automating Node Provisioning with Ansible — render and roll out
wsrep.cnfidempotently from inventory - Bootstrapping Your First Galera Cluster — safe first-node formation and the single-bootstrap rule
- Initial Data Synchronization Methods — the SST/IST model behind the state-transfer parameters
- Handling Galera Startup Errors & Logs — reading provider-load and parse failures at boot