To secure a MariaDB server, treat the database as a chain of trust boundaries rather than a single password prompt. The Linux account starts the service. MariaDB chooses a listener and authentication plugin. Account host patterns decide which identity matches a connection. Grants decide what that identity can do. Files, logs, backups, and encryption keys expose different copies of the same data. Monitoring and audit records determine whether misuse is noticed.
Running mariadb-secure-installation is useful, but it is only one review step. It does not design application privileges, prevent an internet-facing listener, encrypt remote sessions, protect backup media, choose an audit policy, patch the operating system, or prove that recovery still works after hardening.
This guide establishes a production baseline for a new MariaDB installation on Linux. Commands use Ubuntu-style service and file locations where examples are needed, but the security reasoning applies to other distributions. Verify paths, package behavior, and available plugins on the exact server version before making changes.
Define the security model before changing settings
Start by documenting:
- Which applications connect, from which hosts and networks.
- Which operators require administration and whether access is local or remote.
- Whether automated deployment, backup, monitoring, and replication need separate identities.
- Which data requires transport encryption, at-rest encryption, masking, or retention controls.
- Which team receives authentication, privilege, audit, backup, and capacity alerts.
- The supported MariaDB release and patch source.
- Recovery objectives and where encryption keys are backed up.
A “database user” should represent one workload or operational function. Sharing one powerful credential among an application, migration job, backup task, and human administrators prevents meaningful revocation and audit.
Capture a baseline before hardening
Record package source, version, listener, service identity, accounts, plugins, and active options:
apt-cache policy mariadb-server mariadb-client
dpkg-query -W -f='${Package}t${Version}n'
mariadb-server mariadb-client 2>/dev/null
systemctl status mariadb --no-pager
systemctl cat mariadb
sudo ss -lntp | grep ':3306' || true
sudo mariadb -e "SELECT VERSION(), @@version_comment, @@hostname;"
Inspect users and authentication plugins:
sudo mariadb -e "SELECT User, Host, plugin FROM mysql.user ORDER BY User, Host;"
sudo mariadb -e "SHOW GRANTS FOR 'root'@'localhost';"
sudo mariadb -e "SHOW PLUGINS;"
Protect the output. Account names, host patterns, plugin inventory, paths, and grants describe the attack surface.
Find option files and relevant network settings:
mariadbd --help --verbose 2>/dev/null |
sed -n '/Default options are read from/,/Variables and options/p' | head -n 30
sudo grep -R --line-number -E
'^s*(bind-address|port|skip-networking|skip-name-resolve|ssl|tls)'
/etc/mysql 2>/dev/null
Save the baseline in a restricted change record. Hardening without a baseline makes rollback and incident investigation harder.
Keep MariaDB on a maintained release
Security configuration cannot compensate for an end-of-life server. Determine who supplies fixes: Ubuntu, MariaDB Community upstream, or a commercial provider. The package version string may not equal the latest upstream patch because distributions backport fixes.
sudo apt update
apt list --upgradable 2>/dev/null | grep -E '^mariadb|^galera' || true
apt-cache policy mariadb-server
Do not enable unattended major upgrades by pointing production at a rolling repository. Pin an approved release family through managed repository configuration, then install tested maintenance patches on a defined schedule. Monitor MariaDB and distribution security advisories.
Preserve Unix socket authentication for local root
From MariaDB 10.4, Linux installations commonly configure root@localhost to use the unix_socket plugin. Operating-system root can connect through the local socket:
sudo mariadb --protocol=socket
This is not an empty database password. MariaDB verifies the Unix process credentials. It removes a reusable root password that could leak through backup files, option files, scripts, or operator handling.
Confirm the model:
SELECT USER(), CURRENT_USER(), @@external_user;
SHOW GRANTS FOR 'root'@'localhost';
Keep socket authentication when administration occurs through controlled sudo. Protect Linux root with SSH key policy, MFA or privileged-access controls, minimal sudo membership, host logging, and timely patching.
Do not convert root to password authentication merely because an older tutorial expects mariadb -u root -p. If a remote tool cannot use socket authentication, create a separate account with the exact privileges and transport controls that tool needs.
Run mariadb-secure-installation as a review
The utility can remove anonymous accounts, remove unintended remote root accounts, delete the test database, and review root authentication:
sudo mariadb-secure-installation
Read each prompt. On current installations there is usually no need to replace socket authentication with a root password. Afterward, verify the actual state rather than trusting completion text:
sudo mariadb -e "SELECT User, Host, plugin FROM mysql.user ORDER BY User, Host;"
sudo mariadb -e "SELECT SCHEMA_NAME FROM information_schema.SCHEMATA ORDER BY SCHEMA_NAME;"
Anonymous accounts have an empty User value. A test database can also be reintroduced by automation, so recurring account and schema inventory is more reliable than a one-time wizard.
Bind only to required network interfaces
For local applications, use the Unix socket or loopback listener. Ubuntu packages commonly bind to 127.0.0.1:
sudo mariadb -e "SHOW VARIABLES WHERE Variable_name IN ('bind_address','port','skip_networking');"
sudo ss -lntp | grep ':3306'
If no TCP clients are required, skip-networking can disable TCP entirely after compatibility testing. If remote clients are required, bind to a specific private interface rather than all interfaces:
[mariadb]
bind-address = 10.20.30.10
Validate the effective configuration and restart in a maintenance window:
my_print_defaults mariadb mariadbd server mysqld
sudo systemctl restart mariadb
systemctl status mariadb --no-pager
sudo journalctl -u mariadb --since "5 minutes ago" --no-pager
sudo ss -lntp | grep ':3306'
Do not use 0.0.0.0 unless every interface is deliberately in scope and external controls are proven. A listener on all interfaces can become public after an unrelated network or firewall change.
Enforce network segmentation and source restrictions
Firewall rules should allow only approved application, backup, monitoring, replication, or administration sources. Example UFW rule:
sudo ufw allow from 10.20.30.25 to 10.20.30.10 port 3306 proto tcp
sudo ufw status numbered
Before enabling or modifying a host firewall remotely, confirm SSH access and an out-of-band recovery path. Cloud security groups, network ACLs, host firewall, and MariaDB grants should reinforce each other, not substitute for each other.
Do not rely on IP filtering for confidentiality. It cannot prevent credential or data exposure to an observer on the permitted path. Use TLS for traffic crossing hosts unless the connection is protected by an equally reviewed encrypted transport.
Create exact user-host identities
MariaDB accounts are pairs such as 'appuser'@'10.20.30.25'. 'appuser'@'localhost', 'appuser'@'10.20.30.25', and 'appuser'@'%' are different accounts and can have different plugins and grants.
Create the user first, then grant privileges:
CREATE USER 'app_runtime'@'10.20.30.25'
IDENTIFIED BY 'REPLACE_WITH_SECRET';
GRANT SELECT, INSERT, UPDATE, DELETE
ON appdb.* TO 'app_runtime'@'10.20.30.25';
SHOW GRANTS FOR 'app_runtime'@'10.20.30.25';
Generate credentials in a secret manager. Avoid placing real passwords in interactive SQL history. For manual setup, use a protected temporary process approved by the organization and remove artifacts afterward.
Avoid % host patterns. They broaden matching and make network mistakes more consequential. When workloads scale dynamically, prefer a controlled private subnet only after analyzing how MariaDB account matching works and combine it with firewall and TLS controls.
Test the exact identity from the real client host:
mariadb --host=10.20.30.10 --user=app_runtime --password
--database=appdb -e "SELECT USER(), CURRENT_USER(), DATABASE();"
USER() describes the presented identity; CURRENT_USER() shows the account MariaDB matched. A difference can reveal an unexpected host-pattern match.
Separate runtime, migration, backup, and monitoring privileges
A useful production split is:
| Identity | Typical purpose | Avoid granting |
|---|---|---|
| Runtime | Application reads and writes | Global privileges, user management, routine schema destruction |
| Migration | Controlled schema changes | Continuous use by the application process |
| Backup | Consistent backup operations | Application writes or account administration |
| Monitoring | Status and performance collection | Data modification and schema changes |
| Operator | Approved administration | Shared anonymous credentials |
Exact backup and monitoring privileges vary by version, feature, and tool. Follow the official tool documentation and test the least-privilege account. Do not copy broad legacy examples without determining why every privilege is required.
Audit grants:
SELECT User, Host, plugin
FROM mysql.user
ORDER BY User, Host;
SHOW GRANTS FOR 'app_runtime'@'10.20.30.25';
Automate a normalized grant inventory and review differences. Protect it as sensitive configuration.
Use roles where they reduce grant drift
Roles can standardize privileges for groups of accounts:
CREATE ROLE app_reader;
GRANT SELECT ON appdb.* TO app_reader;
CREATE USER 'reporting'@'10.20.30.40'
IDENTIFIED BY 'REPLACE_WITH_SECRET';
GRANT app_reader TO 'reporting'@'10.20.30.40';
SET DEFAULT ROLE app_reader FOR 'reporting'@'10.20.30.40';
Verify role activation behavior with the exact server version and client. Avoid granting WITH ADMIN OPTION unless the recipient must delegate the role. A role with broad privileges multiplied across many users still creates broad access; roles improve management, not the underlying privilege design.
Apply account limits and lifecycle controls
MariaDB CREATE USER supports account locking, password expiration, TLS requirements, and resource limits such as maximum user connections. Availability-sensitive applications need careful values: a limit that is too small causes an outage during legitimate load, while no limit allows one compromised or malfunctioning service to consume every connection.
Example:
ALTER USER 'app_runtime'@'10.20.30.25'
WITH MAX_USER_CONNECTIONS 40;
Measure the connection pool, deployment surge, job concurrency, and failover behavior before choosing 40 or any other value. Monitor rejected connections.
Lock dormant accounts instead of immediately dropping them when a rollback window is required:
ALTER USER 'old_service'@'10.20.30.50' ACCOUNT LOCK;
After the retention and dependency review, remove unused users through change control. Test that no automation still depends on them.
Add password validation where passwords are used
MariaDB includes password-validation plugins such as simple_password_check, cracklib_password_check, and password_reuse_check, but they are not enabled by default. A plugin only helps when the server receives a plaintext password to validate; creating accounts from precomputed hashes can bypass content validation because the original password is unavailable.
Before installing a plugin:
SHOW PLUGINS;
SHOW VARIABLES LIKE 'strict_password_validation';
Determine package availability, compatibility, policy, and failure behavior on staging. Strong randomly generated secrets stored in a secret manager matter more than human complexity patterns. Password validation does not replace rotation, revocation, leak detection, TLS, or least privilege.
Encrypt remote connections with TLS
TLS protects credentials and data in transit. Verify the server's cryptographic support and current TLS variables:
SHOW VARIABLES LIKE 'have_ssl';
SHOW VARIABLES LIKE 'ssl_%';
SHOW STATUS LIKE 'Ssl_version';
Configure the CA, server certificate, and private key using version-specific MariaDB documentation. Protect the private key with restrictive ownership and permissions. Validate certificate identity, expiration, chain, and key match before restart.
Require TLS for remote accounts:
ALTER USER 'app_runtime'@'10.20.30.25' REQUIRE SSL;
SHOW CREATE USER 'app_runtime'@'10.20.30.25';
For stronger identity, REQUIRE X509, issuer, or subject constraints can bind the account to a client certificate, but certificate issuance and rotation must be operationally reliable.
Test that an encrypted connection succeeds and an unencrypted connection fails. Exact client flags vary by connector version; use its current TLS verification options. Do not use a flag that encrypts without verifying the server certificate and hostname when the threat model requires authenticity.
Protect files, directories, logs, and sockets
MariaDB should run as its dedicated service user, not root. Inspect systemd:
systemctl show mariadb -p User -p Group -p ProtectSystem
-p ProtectHome -p PrivateTmp -p NoNewPrivileges
systemctl cat mariadb
Inspect sensitive paths without printing file contents:
sudo namei -l /var/lib/mysql
sudo find /var/lib/mysql -maxdepth 1 -printf '%u:%g %m %pn' | head
sudo find /etc/mysql -type f -printf '%u:%g %m %pn'
sudo find /var/log/mysql -maxdepth 1 -printf '%u:%g %m %pn' 2>/dev/null
Do not fix permission errors with chmod -R 777. Determine the service identity and required access for each path. World-readable configuration can expose credentials. Logs can contain SQL text, identifiers, or data from errors and slow queries. Socket directory permissions influence which local users can attempt connections.
AppArmor on Ubuntu adds a mandatory access-control boundary. If moving the data directory or logs, update the policy through the documented process rather than disabling AppArmor globally.
Protect backups as production data
Backups often contain the entire database, account definitions, stored routines, and historical records no longer visible in the application. Apply:
- Restricted service identity and file permissions.
- Encryption in transit and at rest.
- Separate storage credentials.
- Immutability or write-once retention where appropriate.
- Restore authorization and audit.
- Defined retention and secure expiration.
- Tested key recovery independent of the database host.
Create a restrictive local directory for staging:
sudo install -d -m 0700 /var/backups/mariadb
sudo mariadb-dump --all-databases
--single-transaction --routines --events --triggers
| gzip > /var/backups/mariadb/all-$(date +%F-%H%M%S).sql.gz
sudo chmod 0600 /var/backups/mariadb/all-*.sql.gz
Do not leave the only backup on the database server. Test a restore into an isolated environment and verify application-level correctness. Never test by overwriting production.
Evaluate data-at-rest encryption carefully
MariaDB data-at-rest encryption requires a key-management and encryption plugin. It can protect InnoDB tablespaces, logs, temporary data, and other supported components, but coverage has limitations. Metadata, error logs, audit output, and some storage-engine files may not be encrypted by the same control.
Encryption keys must live separately from the encrypted data in a design that supports startup, rotation, backup, disaster recovery, and revocation. A key stored beside the database with equally broad access gives little protection against host compromise.
Before enabling encryption:
- Inventory every data copy and log.
- Select a supported key-management plugin.
- Back up keys independently.
- Test encrypted backup and restore.
- Measure I/O and recovery overhead.
- Test key rotation and disaster startup.
- Document what remains unencrypted.
Never enable tablespace encryption in production before proving that the backup tool and recovery environment can decrypt the data.
Configure auditing for useful events
The MariaDB Audit Plugin can record connections, disconnects, failed authentication, queries, and table access depending on configuration. Logging everything can generate high volume, expose sensitive SQL, and create availability risk if storage fills.
Start from use cases:
- Privileged login and grant changes.
- Failed authentication patterns.
- Access to regulated schemas.
- Account creation, alteration, lock, and deletion.
- Administrative operations outside expected windows.
Inspect plugin availability and variables:
SHOW PLUGINS;
SHOW VARIABLES LIKE 'server_audit%';
Test event selection, log destination, rotation, permissions, capacity, forwarding, and alert parsing in staging. Sending audit events to a protected remote logging system improves resilience against local tampering. Ensure the logging path cannot fill the database filesystem unnoticed.
Reduce information leakage through logs and diagnostics
General and slow query logs may capture literal values. Error logs can include query fragments or data during failures. Process lists and shell histories can expose credentials passed on command lines.
Avoid:
mariadb --password=REAL_SECRET
mariadb-dump --password=REAL_SECRET
Use a prompt, protected option file, socket authentication, or a secret-injection mechanism supported by the tool and platform. Restrict diagnostic bundles and redact secrets before sharing.
Enable verbose query logging only for a bounded diagnostic window, then disable it and handle the collected file according to data policy.
Monitor the security controls
A hardened configuration that silently stops working is not a control. Monitor:
- MariaDB and operating-system security updates.
- Listener addresses and unexpected firewall changes.
- Failed and successful privileged authentication.
- Account, role, and grant drift.
- TLS certificate expiration and verification failures.
- Audit pipeline health, backlog, and disk use.
- Backup completion, age, encryption, transfer, and restore tests.
- Binary log, error log, audit log, and data filesystem capacity.
- Unexpected plugin or configuration changes.
- OOM kills, crash recovery, and restart loops.
Useful baseline queries:
SHOW GLOBAL STATUS LIKE 'Aborted_connects';
SHOW GLOBAL STATUS LIKE 'Connections';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
Trend values rather than alerting on one absolute count. Correlate failed connections with source network, account, deployment, and rate.
Troubleshoot hardening failures safely
Application gets Access denied
Confirm the exact account match:
SELECT User, Host, plugin FROM mysql.user WHERE User = 'app_runtime';
SHOW GRANTS FOR 'app_runtime'@'10.20.30.25';
Test from the real client. Do not “fix” it by adding 'app_runtime'@'%' with global privileges.
Remote connection times out
Check route, listener, and firewalls:
sudo ss -lntp | grep ':3306'
sudo ufw status verbose
nc -vz -w 3 10.20.30.10 3306
A timeout occurs before MariaDB authentication. Changing grants cannot repair it.
TLS-required account cannot connect
Verify certificate chain, hostname, validity, client trust store, MariaDB TLS variables, and connector options. Do not disable certificate verification permanently. Compare server and client time because clock errors affect certificate validity.
MariaDB fails after file-permission changes
Read the first service error:
systemctl status mariadb --no-pager
sudo journalctl -u mariadb --since "15 minutes ago" --no-pager
sudo namei -l /var/lib/mysql
Restore the documented owner and mode for the exact path. Check AppArmor denials. Do not recursively change the entire /etc/mysql or data directory without understanding special files and mount behavior.
Operator is locked out
Try the expected local socket path first:
sudo mariadb --protocol=socket
Do not jump immediately to --skip-grant-tables. If emergency privilege bypass is necessary, isolate the server from networks, use the official version-specific recovery procedure, minimize duration, restore normal startup, rotate affected credentials, and audit what changed.
Production security checklist
- A maintained MariaDB branch and patch source are documented.
- Linux root and sudo access are strongly controlled.
- MariaDB runs under a dedicated unprivileged service account.
- Local root uses the understood socket-authentication model.
- Anonymous users, remote root accounts, and test schemas are absent.
- Each workload has a separate identity and exact host match.
- Runtime, migration, backup, monitoring, and operator grants are separated.
- Port 3306 is local-only or limited to approved private sources.
- Remote sessions verify TLS certificates.
- Passwords are random, secret-managed, rotated, and revocable.
- Configuration, data, sockets, logs, and keys have reviewed permissions.
- Audit coverage is useful, protected, rotated, and monitored.
- Backups are encrypted, off-host, retained, and restore-tested.
- Data-at-rest encryption keys can be recovered separately.
- Security controls have alerts and periodic drift review.
- Emergency access and credential-compromise runbooks are rehearsed.
FAQ
Is mariadb-secure-installation enough to secure MariaDB?
No. It reviews several initial accounts and schemas, but network exposure, TLS, least privilege, file security, auditing, backups, patching, and monitoring require separate controls.
Is Unix socket authentication less secure than a root password?
Not inherently. It ties local MariaDB root access to operating-system root, avoiding a reusable database password. Its strength depends on Linux root and sudo security.
Should application users use the % host wildcard?
Avoid it when specific hosts or controlled subnets can be used. % broadens account matching and increases the impact of network-policy mistakes.
Should MariaDB port 3306 be public?
No. Keep it local or on private networks with source-limited firewall rules. Use a VPN, private connectivity, or a controlled proxy for administration rather than public exposure.
Does TLS replace database grants?
No. TLS protects transport and can authenticate certificates. Grants still determine which objects and operations an authenticated account may use.
Should all SQL queries be written to the audit log?
Only when the use case and capacity justify it. Full query logging can expose sensitive values and generate heavy volume. Select events deliberately and protect the logging pipeline.
Does disk encryption replace MariaDB data-at-rest encryption?
They address different threats. Full-disk encryption protects offline media, while database-level encryption can provide more granular protection and key handling. Neither protects data after an authorized process decrypts it.
How often should MariaDB privileges be reviewed?
Review continuously through drift detection and at defined intervals, plus after deployments, employee changes, incidents, and architecture changes. Remove or lock unused accounts promptly.
Conclusion
To secure a MariaDB server, preserve a clear identity model, expose the smallest network surface, grant each workload only what it needs, encrypt remote traffic, protect every stored copy, and monitor whether those controls remain active. A one-time wizard cannot provide that assurance.
Start with a maintained release and local socket administration. Remove unintended accounts, bind deliberately, restrict sources, separate runtime from operational identities, and verify account matching from real clients. Add TLS, audit, backup encryption, at-rest encryption, and password controls according to the data and threat model. Finally, rehearse lockout, restore, certificate rotation, and compromise response. Security is complete only when the controls are both restrictive and operable during failure.
Suggested Internal Links
- Install MariaDB on Ubuntu 24.04: Production Setup Guide —
/install-mariadb-ubuntu-24-04/ - Configure MariaDB Remote Access Without Exposing It —
/configure-mariadb-remote-access/ - Create MariaDB Databases, Users, and Least-Privilege Grants —
/mariadb-users-grants/ - Configure TLS Encryption for MariaDB Client Connections —
/configure-mariadb-tls/ - Audit MariaDB Users, Privileges, and Security Events —
/audit-mariadb-privileges/
Suggested External Sources
- Securing MariaDB — https://mariadb.com/docs/server/security/securing-mariadb
- MariaDB security documentation — https://mariadb.com/docs/server/security/
mariadb-secure-installation— https://mariadb.com/docs/server/clients-and-utilities/deployment-tools/mariadb-secure-installation- Unix socket authentication — https://mariadb.com/docs/server/reference/plugins/authentication-plugins/authentication-plugin-unix-socket
CREATE USERreference — https://mariadb.com/docs/server/reference/sql-statements/account-management-sql-statements/create-userGRANTreference — https://mariadb.com/docs/server/reference/sql-statements/account-management-sql-statements/grant- Password validation plugins — https://mariadb.com/docs/server/reference/product-development/plugin-development/password-validation
- Data-at-rest encryption overview — https://mariadb.com/docs/server/security/securing-mariadb/encryption/data-at-rest-encryption/data-at-rest-encryption-overview
- MariaDB Audit Plugin configuration — https://mariadb.com/docs/server/reference/plugins/mariadb-audit-plugin/mariadb-audit-plugin-configuration