Backups You Can Actually Restore, Part 2: MariaDB Without a Barman
The same three-layer strategy — reliable backups, a restore habit, an immutable copy — for the database that doesn't have a Barman.
Last time I laid out the PostgreSQL version of this: Barman on its own server, a monthly scripted restore, and an off-site copy on Object Lock storage that nobody — not even you — can delete. If you run small-business infrastructure, though, you almost certainly also run MariaDB, because half the small-business web is WordPress, WooCommerce, or Magento sitting on it.
So here’s the honest opening statement: MariaDB has no Barman. There is no single, mature, pull-based backup orchestrator that owns the whole lifecycle the way Barman does for PostgreSQL. What MariaDB gives you instead are two excellent primitives — mariadb-backup for physical backups and the binary log for point-in-time recovery — and the expectation that you assemble them yourself.
That’s less bad than it sounds. The assembly is about a hundred lines of shell and two systemd units, and you end up understanding your recovery path completely — which, per the previous article, is the entire point. The three layers stay identical:
- Boring, reliable backups
- Restore testing as a scheduled habit
- An immutable off-site copy that survives a full compromise
The tool choice, and one warning
Use mariadb-backup (formerly Mariabackup). It ships in the standard repositories alongside the server, takes hot physical backups of InnoDB without stopping traffic, and supports incrementals. On MariaDB 10.4+ it uses BACKUP STAGE locking, which avoids the long global lock that older FLUSH-TABLES-based approaches suffered under write load.
The warning: if you’ve used Percona XtraBackup with MySQL, do not point it at a modern MariaDB. The two data formats diverged years ago and XtraBackup does not support current MariaDB versions. mariadb-backup is the fork maintained for exactly this reason. Mixing them up is a classic way to discover, at restore time, that your backups were never valid.
What about mysqldump/mariadb-dump or mydumper? Logical dumps have their place — they’re portable across versions and great for migrating a single small database. But restore time scales brutally with data size (every row re-inserted, every index rebuilt), and there’s no clean PITR story. For “the shop is down, restore it,” physical backups win. Keep a weekly logical dump as a portability bonus if you like; don’t make it the plan.
The base setup
Two flows run continuously, and — matching the Barman architecture — both are pull-based, driven from a separate backup server that connects to the database host. The database host holds no credentials for the backup host. Layer zero of immutability, same as before.
Flow 1: physical backups over SSH, weekly full + daily incremental.
mariadb-backup can stream its output, so the backup server pulls like this:
# on the backup server — full backup
ssh backup@db1 "mariadb-backup --backup --stream=mbstream \
--user=backup --password-file=/etc/mysql/backup.cnf" \
| mbstream -x -C /srv/backups/db1/full-$(date +%F)
Incrementals reference the last backup’s LSN with --incremental-basedir, capturing only changed pages. For a typical small-business database this turns daily backups from gigabytes into megabytes.
The backup user needs only backup-related privileges (RELOAD, PROCESS, LOCK TABLES, REPLICATION CLIENT, and on newer versions the granular BACKUP admin privileges) — not full root. The SSH key on the backup server should be restricted to the backup command on the DB host, and the direction matters: backup server → DB host, never the reverse.
Flow 2: continuous binlog streaming — MariaDB’s WAL archiving.
This is the piece most small setups miss, and it’s what turns “restore last night’s backup” into “restore to 14:31, right before the bad deploy.” The binary log client can act as a fake replica and stream binlogs to disk continuously:
mariadb-binlog --read-from-remote-server --raw --stop-never \
--host=10.0.0.20 --user=binlogstream \
--result-file=/srv/backups/db1/binlog/ mariadb-bin.000001
The user needs only REPLICATION SLAVE. Wrap this in a systemd unit with Restart=always, because unlike Barman’s receiver it will not babysit itself — when it dies, nothing archives until it’s restarted, and that’s a silent gap. Two settings on the server side protect you: binlog_format = ROW (deterministic replay) and a generous binlog_expire_logs_seconds — keep binlogs on the primary at least a few days, so a streaming outage over a weekend doesn’t mean purged, unrecoverable log.
Monitoring is yours to build, and it’s small: alert if the newest full/incremental is older than expected, if the binlog streamer unit isn’t active, or if the newest streamed binlog file hasn’t grown in N minutes on a database that’s taking writes. Three checks. If nothing alerts, you have logs, not monitoring.
The restore habit — with MariaDB’s extra gotcha
Monthly, scripted, on a scratch VM. Same as before, but MariaDB adds a step people forget: a physical backup is not usable as-is. It must be prepared — crash recovery applied to make the datafiles consistent:
# prepare the full, then apply each incremental in order
mariadb-backup --prepare --target-dir=/restore/full
mariadb-backup --prepare --target-dir=/restore/full \
--incremental-dir=/restore/inc-monday
# ... repeat per incremental, then
mariadb-backup --copy-back --target-dir=/restore/full
chown -R mysql:mysql /var/lib/mysql
Then point-in-time: the backup directory contains a file recording the binlog file and position at backup time. Replay from there to your target moment:
mariadb-binlog --start-position=387659 \
--stop-datetime="2026-08-29 14:31:00" \
/srv/backups/db1/binlog/mariadb-bin.0004* | mariadb
And verify with business data, not with “the daemon started”: newest order, newest user, a count that should only go up. Fail the script loudly if the answer is older than your RPO.
The prepare step is exactly why the drill matters more for MariaDB than for PostgreSQL. It’s an extra transformation between “backup exists” and “database runs,” with its own failure modes (mismatched incremental chains, wrong order, forgotten --prepare entirely). A backup you’ve never prepared is a hypothesis.
The immutable layer
Identical philosophy to the PostgreSQL article, so I’ll be brief on the theory: off-site is not enough, because the modern adversary deletes backups with your credentials before encrypting production. The property you need is WORM — Object Lock on S3-compatible storage, compliance mode, retention matched to your recovery window.
The mechanics differ only in that there’s no barman-cloud doing the shipping. After each backup completes and each binlog file rotates, sync to the locked bucket with a put-only credential:
DB primary ──▶ backup server ──▶ object storage (Object Lock, EU region)
(pull) (local copy) (immutable copy)
Any S3 client works — the tool matters far less than the credential design. The key used for shipping can put objects and nothing else: no delete, no overwrite of locked versions, no lifecycle changes, no lock configuration. Then a compromise of the backup server — blast radius two — costs you the local copy only.
Because you’re streaming binlogs as raw files, they ship incrementally and cheaply; you’re not re-uploading a monolith every night. One detail worth encoding in the sync script: ship a binlog file only once it has rotated, or ship the active file under a temporary name — locked storage plus an append-in-place file is an awkward combination, since you can’t overwrite an object you’ve already locked.
The GDPR reconciliation carries over unchanged: finite, documented lock periods; erasure fulfilled in production immediately and propagating as backups age out; retention justified under Article 32, which obliges you to be able to restore in the first place. Infinite retention is where the security control becomes the compliance liability.
What this costs
The same small VM you already run Barman on can host this — the flows are independent per database and light. Object storage for the immutable tier stays in coffee-money territory for typical small-business data volumes. Everything is in the standard MariaDB packages plus a shell script you own and can read in five minutes.
The one real cost versus PostgreSQL is honesty about maintenance: you own the orchestration. There’s no barman check summarizing the world in one exit code; you wrote the three checks, and you keep them true as things change. For a small operation that’s a fair trade — a hundred lines you understand beats a platform you don’t.
The habit, restated
Same three sentences as last time, because they don’t change with the engine: backups are taken by software, restores are proven by habit, deletion is prevented by architecture. MariaDB just asks you to write a little more of the software yourself — and rewards you with a recovery path you actually understand.
Next in the immutability thread: what Object Lock modes really guarantee, append-only patterns for file-level backups, and the erasure question done properly.
Madalin
AI integrator🚀 Senior Architect | SRE & Database Expert | AI Orchestrator 👋 Building the future at the speed of thought. ⚡️ I don't just write code; I architect high-performance, bulletproof ecosystems. With a foundation in Systems Engineering and a mastery of Go and TypeScript, I bridge the gap between heavy-duty backend reliability and seamless, high-conversion frontends.
Continue the conversation
If this article reflects the challenges your organisation is navigating, explore more practical guidance across Madalin.