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:

  1. Base server defaults — distribution drop-ins such as /etc/mysql/mariadb.conf.d/50-server.cnf.
  2. Galera-specific overrides — your /etc/mysql/wsrep.cnf (or a 60-galera.cnf drop-in), included after the base file.
  3. Runtime injection — systemd EnvironmentFile, MYSQLD_OPTS, container entrypoint arguments, and explicit command-line flags, which override everything parsed from disk.
How MariaDB layers configuration into one effective [mysqld] set Three layers are parsed lowest-precedence first: base defaults in 50-server.cnf, then wsrep.cnf overrides, then runtime injection from EnvironmentFile, MYSQLD_OPTS and command-line flags. They merge into a single [mysqld] section where the last value parsed for each key wins, and that effective set is read once by the Galera provider libgalera_smm.so. Later layers silently overwrite earlier keys, so command-line and runtime flags win over anything on disk. PARSE ORDER ↓ · PRECEDENCE ↑ 1 · Base server defaults /etc/mysql/…/50-server.cnf parsed first · lowest precedence 2 · wsrep.cnf overrides 60-galera.cnf · [mysqld] directives overrides the base layer 3 · Runtime injection EnvironmentFile · MYSQLD_OPTS · CLI parsed last → overrides everything last key wins Effective [mysqld] one merged parameter set last value parsed wins own one layer per key Galera provider libgalera_smm.so provider_options read once at load
MariaDB merges every drop-in into a single [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.so from the MariaDB server package.
  • mariabackup installed 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_threads and 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.