The compliance questionnaire said "encrypt all data at rest" and the DBA team said "fine, that is one config block" — and both were right, for about six months. Then a server migration moved the data directory to new hardware and nobody moved the key file, and the restored node came up reading gibberish until someone found the backup copy of a 512-byte file that turned out to be worth more than the entire array it unlocked. MariaDB's data-at-rest encryption is genuinely good and genuinely simple to switch on. The part that bites is everything around the switch: key custody, rotation, the things that stay plaintext, and a recovery story that ends the day the keys do.
Which key management plugin should you run?
Encryption requires a key management and encryption plugin — MariaDB has no built-in keystore — and the choice is really three choices. file_key_management ships with every build and reads keys from a plain text file of id-and-hex pairs; the file can itself be encrypted with a separate key via file_key_management_filekey, and file_key_management_encryption_algorithm picks AES_CBC (the default) or AES_CTR. It is the right answer for small estates, provided you treat that file as a crown jewel: permissions 400, owned by the database user, backed up separately from the data, never on the same backup volume as the backups it protects. aws_key_management, also in community builds, moves custody into AWS KMS — the right answer if your organization already lives in AWS and wants keys behind IAM and CloudTrail. hashicorp_key_management talks to Vault's KV store, supports native key rotation, and is the one to check your edition for: it ships with MariaDB Enterprise Server, not the community packages. Whatever you pick, the keys are identified by 32-bit integer ids, with key id 1 as the default unless innodb_default_encryption_key_id says otherwise.
What do you actually turn on?
More knobs than most guides admit, because table encryption alone leaves plaintext all over the server. The core set: innodb_encrypt_tables controls InnoDB tablespace encryption — ON encrypts new tables by default, and FORCE refuses to create unencrypted InnoDB tables at all, which is the setting you want once you commit, because it converts "we encrypt everything" from a policy into a property of the server. innodb_encrypt_log encrypts the InnoDB redo log. innodb_encrypt_temporary_tables covers InnoDB internal temporary tables. Then the ones everyone forgets: encrypt_tmp_disk_tables encrypts on-disk temporary tables from queries, encrypt_tmp_files covers miscellaneous temporary files like binlog caches, aria_encrypt_tables does the same for Aria tables (including on-disk Aria temp tables), and encrypt_binlog encrypts the binary log — and that last one defaults to OFF, which means a server with perfectly encrypted tablespaces can be streaming every row change in cleartext through its binlogs, its relay logs, and every replica downstream. The binlog is the most-missed gap in the whole feature.
-- confirm the knobs that decide real coverage
SHOW GLOBAL VARIABLES
WHERE Variable_name IN
('innodb_encrypt_tables', 'innodb_encrypt_log',
'encrypt_binlog', 'encrypt_tmp_disk_tables',
'encrypt_tmp_files', 'aria_encrypt_tables',
'innodb_default_encryption_key_id');
-- encrypt an existing table; background threads do the heavy lifting
ALTER TABLE legacy_orders ENCRYPTED=YES ENCRYPTION_KEY_ID=1;
-- which tablespaces are encrypted, with which key, and whether rotation is running
SELECT NAME, ENCRYPTION_SCHEME, CURRENT_KEY_ID,
CURRENT_KEY_VERSION, MIN_KEY_VERSION, ROTATING_OR_FLUSHING
FROM information_schema.INNODB_TABLESPACES_ENCRYPTION
ORDER BY NAME LIMIT 20;
Existing tables do not convert the moment you set the variable. Marking a table ENCRYPTED=YES flags it, and the background encryption threads — innodb_encryption_threads of them — walk the tablespace and re-encrypt pages in place while the server keeps serving. On a large dataset that initial conversion is a days-long background IO workload, not an event; plan it, throttle it, and watch it in INNODB_TABLESPACES_ENCRYPTION rather than wondering why the disks are busy.
How does key rotation actually work?
Per-page, lazily, in the background — which is the only way rotation can work on terabytes of data without downtime. Encrypted pages carry the key id and key version they were written with, and MariaDB can always read older versions as long as the plugin still has them. Rotating means introducing a new version of the key — the mechanics are plugin-specific, native in the HashiCorp plugin — and then letting the background threads re-encrypt pages to the latest version as they pass by. Two variables govern that machinery: innodb_encryption_rotate_key_age re-encrypts any page whose key version is at least the given number of versions behind the current key — the default is 1, and zero disables the background rotation checks entirely, which can also suppress the automatic first-pass encryption of new tables, so leave it at 1 — and innodb_encryption_rotation_iops caps the IO the rotation threads may spend, so the conversion does not compete with production latency. The operational posture is boring on purpose: bump the key version on your compliance schedule, keep rotation_iops conservative during business hours, and verify progress through the CURRENT_KEY_VERSION column rather than assuming the calendar did the work. Note what this does not do: it does not re-encrypt anything immediately, and old key versions must remain available until the last page written with them is gone — deleting an old key version early is a quieter way to corrupt your own database.
What stays unencrypted no matter what you set?
More than the architecture diagram suggests, and being precise here is what separates an encryption deployment from a checkbox. Memory first: pages are decrypted as they are read into the InnoDB buffer pool, so anyone who can read process memory — or a core dump — reads plaintext; at-rest encryption protects disks and files, never RAM. The wire is a separate problem entirely: replication and client connections need TLS, configured independently. The general query log, slow log, and error log are not covered by these knobs and can happily record the literal values you encrypted the tables to protect. Logical dumps are plaintext: mariadb-dump output contains your data in the clear, so pipe it through your own encryption or treat dump files as sensitive as the tablespaces. And replicas are independent servers with independent settings — encrypting the primary does nothing for any replica, each of which needs its own plugin and its own keys. The full inventory is the point: tablespaces, redo, binlogs, temp files, Aria, plus everything on this list that you must handle some other way.
What does it cost, and what breaks during recovery?
On modern CPUs with AES-NI, the steady-state overhead on IO-bound OLTP is low single-digit percent in my measurements — cheap enough that I enable it by default on anything touching personal data. The workloads that pay more are CPU-saturated write-heavy ones and the background conversion and rotation periods, which is what the IOPS throttle is for. Benchmark with your own dataset before promising anyone a number; "encryption is free" and "encryption is slow" are both claims about somebody else's workload.
Recovery is where the honesty matters most. mariabackup copies encrypted tablespaces as-is — your backups stay encrypted on disk, which is the behavior you want — but preparing and restoring them requires the same keys, which means key backup is now a hard dependency of every restore. Store the key file or KMS access separately from the data backups, in a place with its own access controls and its own restore test. If the keys are unavailable at startup, the server cannot read the encrypted tablespaces, full stop — there is no backdoor and no support escalation that changes that, because not having one is the entire product. And the dependency cuts both ways operationally: if you use a network KMS, its availability is now in your database's startup path, so caching and failover for the KMS deserve the same seriousness as the database itself. Lose the keys, lose the data is not a warning label on this feature; it is the feature. Write it in the runbook, rehearse the restore, and then the audit checkbox takes care of itself.
Where MonPG fits
The signals worth trending are unglamorous: encryption coverage across tablespaces from INNODB_TABLESPACES_ENCRYPTION so nothing silently stays plaintext, rotation backlog and thread progress during conversions, and key-management reachability so you learn about a KMS outage before the next restart does. Full disclosure, as in the rest of this series: I work on MonPG, which monitors PostgreSQL today and does not monitor MariaDB yet — MariaDB support is coming soon, in active development and tracked on the status page, and encryption-coverage auditing is exactly the kind of unglamorous check it is being built to automate. Until it ships, the queries above plus a rehearsed restore are the toolkit. If PostgreSQL is also in your fleet, that side of the workflow is live today — see the PostgreSQL overview and how the engines compare, or find more field notes on the blog.