The calendar made the decision for us. MariaDB 10.6 — our fleet standard, forty-odd production instances — goes end of life in July 2026, and 11.4 is the current long-term-support release, so the upgrade was not a question of if but of how many weekends it would cost. The answer surprised me in both directions. The mechanical upgrade on a rehearsed box took eleven minutes: stop, swap packages, start, run mariadb-upgrade, done. Finding everything the upgrade would break took three weeks, and none of it was in the server binary. The casualties were a system variable removed two releases ago that our config still carried, a monitoring cron calling mysql_upgrade by its old name, one report query whose plan flipped on the new optimizer, and an authentication assumption baked into a provisioning script. Every one of those was found on a restored backup in staging, which is the entire thesis of this article: an LTS-to-LTS MariaDB upgrade is a rehearsal exercise, not a courage exercise.
What follows is the method we ran across the fleet: why the version skip is legitimate, how to build the rehearsal so it finds the real breakage, what actually broke for us category by category, and the cutover design that keeps a rollback possible when the binaries themselves do not.
Can you really jump from 10.6 straight to 11.4?
Yes, and it is the intended path. MariaDB's LTS releases — 10.6, 10.11, then 11.4 — are designed for direct in-place upgrades from earlier versions, including skipping the intermediate LTS. The data dictionary and the mysql system schema are migrated forward by mariadb-upgrade, the renamed successor of mysql_upgrade, which checks and fixes system tables, columns, and privilege structures to match the new server. The jump is not exotic; fleets on 10.6 that waited out 10.11 are a large part of the intended audience.
What the jump does not support is the direction nobody advertises: downgrades are not supported, at all, across a major-version boundary. Once mariadb-upgrade has touched the system schema and the new server has written to the data directory, you cannot point the 10.6 binaries at those files and go back. That single fact drives the whole cutover design. Your rollback plan is not "keep the old packages installed" — it is "keep an old-version replica or a restorable backup that never got upgraded," and you hold it until the new version has survived long enough in production that you trust it. For us that meant a fourteen-day retention on pre-upgrade backups and one deliberately-not-upgraded replica kept on 10.6 for the first week after each cutover. Cheap insurance, and the only kind that works.
How do you build a rehearsal that finds real breakage?
The rehearsal that finds things is a production-shaped one: restore the latest physical backup onto a staging box with the production my.cnf untouched, then swap the binaries, then read the error log like it owes you money. Our restore of choice is a mariabackup snapshot — the same one the disaster-recovery drill already validates, and if yours is not drilled, the mariabackup operational guide is the place to fix that first. The sequence that matters:
-- on the staging restore, BEFORE upgrading: baseline facts
SELECT VERSION();
SHOW GLOBAL VARIABLES LIKE 'version%';
-- snapshot the config the old server was actually running
SELECT VARIABLE_NAME, GLOBAL_VALUE
FROM information_schema.GLOBAL_VARIABLES
ORDER BY VARIABLE_NAME; -- save this, diff it post-upgrade
After the package swap, the first start of the new binary against the old config is the highest-signal moment of the whole rehearsal. MariaDB is tolerant of unknown variables in the sense that many are warnings, but some are fatal, and the error log is the only honest list. Read every line from that first boot. Our config carried a variable deprecated in 10.11 and removed since — the old server had logged a gentle deprecation warning for two years, the new one refused to start, and the fix was deleting a line that had not done anything since 2023. That is the category to hunt: settings your config accumulated over years of copy-paste that the new server no longer recognizes. Then run mariadb-upgrade, restart, and diff the GLOBAL_VARIABLES snapshot you saved against the new server's output — defaults shift between LTS releases, and a changed default you never set explicitly is still a behavior change you own.
The second half of the rehearsal is workload, and it is the half people skip. Replay the top hundred queries from the production slow log against the staging box and compare plans and timings. Optimizer behavior moves between LTS releases — new transformations, different cost constants, better use of the engine-independent histograms that arrived during the 10.x line. Our one regression was a monthly report query that picked a different join order on 11.4 and went from four minutes to forty; the fix was an ANALYZE TABLE to refresh histogram statistics on two skewed columns, the same machinery covered in the histogram statistics field notes. One regression in a hundred queries is a good outcome — but only if you find it in staging.
What actually broke for us, category by category?
Four categories, in the order they cost us time. Configuration first, covered above: removed variables are the cheapest breakage to fix and the most embarrassing to find in production, because the error log told you for years. Tooling second: the client and admin utilities were renamed across the 10.x line from mysql_* to mariadb_* names — mariadb, mariadb-admin, mariadb-dump, mariadb-upgrade — with compatibility symlinks that exist on most packages but are not a thing to build new automation on. Our monitoring cron invoked mysql_upgrade by name after every minor patch; it survived on symlinks until one package build dropped them, and the fix was a sed across the cron repo plus a grep for every remaining mysql_ invocation. Audit your scripts for the old names before the upgrade does it for you.
Authentication third, and this one is a provisioning-script issue more than a runtime one. MariaDB 11.4 ships the PARSEC authentication plugin, which adds proper password-expiration support with salted challenge-response — a real improvement, but also a signal that the auth surface is moving. Our breakage was older and dumber: the provisioning script created accounts assuming the historical default plugin and then set passwords in a way that interacted badly with stricter validation on the new version. The fix was to make the intended plugin explicit in every CREATE USER — IDENTIFIED VIA mysql_native_password or ed25519 or parsec, chosen deliberately per account class — instead of inheriting whatever the server default happens to be this release. Explicit beats default in every upgrade I have ever run.
Fourth, character sets and collations, the category that did not break but demanded verification. The 10.x line added the UCA-14.0.0 collations for utf8mb4, and new releases continue to refine the defaults story; nothing changed under us, but a handful of ORM-generated tables had explicit COLLATE clauses pinned years ago, and we verified those pins still resolved identically on 11.4 rather than assuming. The cost of that verification was an afternoon; the cost of a silent collation change in a unique index is a corruption class I prefer to admire from a distance.
How do you cut over production without owning an irreversible mistake?
The cutover pattern that respects the no-downgrade rule is the replication hop. For each primary with replicas: upgrade a replica first (it is expendable, and a newer-version replica replicating from an older-version primary is the supported upgrade direction), let it catch up and bake, then fail over to it, then upgrade the old primary and bring it back as a replica on the new version. Single-primary setups without replicas get the same logic one level down: build a new 11.4 replica off the 10.6 primary, verify, promote, and keep the old 10.6 server shut down but intact for the retention window as your rollback-of-last-resort alongside the pre-upgrade backup.
Post-cutover verification is a checklist, not a vibe. Row counts and spot checksums on the highest-stakes tables; the application smoke suite against real traffic shapes; the error log watched closely for the first week, because the second-order warnings — a plugin behaving differently, a deprecated feature an ORM calls silently — only appear under real workload; and the slow log compared against the pre-upgrade baseline for plan drift on queries your staging replay missed. Our fleet ran 11.4 with one 10.6 replica retained for the first week per cluster, and the retention window closed without a single rollback. The rehearsals found everything worth finding. That is not luck; it is what rehearsals are for. If your fleet straddles MySQL and MariaDB and you are weighing which upgrade mechanics differ where, the operational differences piece maps the divergence, and the version-skip scars on the MySQL side are covered in the MySQL 5.7 to 8.0 field notes.
Where MonPG fits
The signals worth trending through an upgrade window are the ones that tell you the new version is behaving like the old one, only better: query latency distributions against the pre-upgrade baseline, error rates and error-log volume per server, replication lag through each hop, and connection-pool behavior as drivers meet the new server. Full disclosure, as in every article of this series: I work on MonPG, which monitors PostgreSQL in production today and does not monitor MariaDB yet. MariaDB support is coming soon and in active development — the /mariadb-monitoring page tracks where it stands — and upgrade-window observability is on the list of signals it is being built around: baseline-versus-current comparisons surfaced per instance so a plan regression is a dashboard line, not a user complaint. Until that ships, the slow-log comparison and the error-log watch are your kit. If PostgreSQL is also in your fleet, that monitoring is live today — see the PostgreSQL overview, or browse more field notes on the blog.