How to Secure a MariaDB/MySQL Installation on Fedora

Run the built-in security script after installing MariaDB on Fedora, then lock down remote access and configure SELinux and the firewall to protect your database.

Story / scenario opener

You just installed MariaDB on a fresh Fedora server. The service starts, the prompt appears, and everything looks fine. Then you realize the database is listening on every network interface, the root account accepts connections from anywhere, and a default test database sits wide open. A quick script can fix most of it, but understanding what the script actually changes prevents future headaches.

What is actually happening

MariaDB ships with a permissive default configuration. It prioritizes getting up and running over locking things down. The installation creates a root account that authenticates via the local Unix socket, leaves an anonymous user for local testing, and includes a test database that any local user can access. The server also binds to 0.0.0.0 by default, meaning it accepts connections from any interface that has an IP address. Hardening means stripping away the defaults that assume a trusted local environment and replacing them with explicit boundaries. Think of it like moving a development workstation from a public cafe to a locked server room. You remove the guest Wi-Fi, change the default passwords, and close the doors you do not need open.

Run the hardening steps in order. Skipping one leaves a gap the next step cannot close.

Install and start the service

Install the server package and enable the systemd unit. Fedora packages MariaDB as mariadb-server, which pulls in the client tools and the runtime libraries automatically.

sudo dnf install mariadb-server -y
# -y skips the confirmation prompt. Use it in scripts, not interactive sessions.
sudo systemctl enable --now mariadb
# enable adds the unit to the default target. --now starts it immediately.

Verify the service is running and check the journal for startup warnings.

systemctl status mariadb
# Shows active state, main PID, and the last 10 log lines.
journalctl -xeu mariadb
# -x adds explanatory text. -e jumps to the end. -u filters by unit.

Fedora places user configuration in /etc/my.cnf.d/. Package defaults live in /usr/lib/my.cnf.d/. Edit files in /etc/. Never touch /usr/lib/. Package updates overwrite /usr/lib/ without warning.

Check the status output before restarting. Half the time the service is already healthy.

Run the hardening script

MariaDB ships with mysql_secure_installation. The script walks through five standard hardening steps: setting a root password, removing anonymous users, disabling remote root login, dropping the test database, and reloading the privilege tables.

sudo mysql_secure_installation
# Runs interactively. Press Enter to skip the initial root password prompt.

Answer the prompts carefully. When asked to set the root password, choose a strong password and record it in a password manager. Enable all other recommendations. The script modifies the mysql.user table and the mysql.db table directly. It does not change network bindings or SELinux contexts.

Run the script immediately after installation. Delaying it leaves the database exposed to local enumeration attacks.

Verify the authentication plugin

On Fedora, MariaDB defaults to socket-based authentication for the root account. This means the database checks the Linux username instead of asking for a password. It is more secure than a password for local administration because it ties database access to system-level privileges.

sudo mysql -e "SELECT user, plugin, host FROM mysql.user WHERE user='root';"
# Queries the mysql.user table. Shows the auth plugin and allowed hosts.

You should see unix_socket in the plugin column. Do not change this unless you have a specific reason. Switching to mysql_native_password or caching_sha2_password removes the Linux privilege boundary and forces password management for administrative tasks.

If you accidentally changed the plugin and lost access, boot into a rescue environment, stop MariaDB, and restart it with --skip-grant-tables. Fix the plugin, then restart normally.

Leave unix_socket alone. System-level root access is the intended boundary.

Create a dedicated application user

Never use the root account for application database connections. Applications should run with the minimum privileges required to function. Create a separate database and a user scoped to that database.

sudo mysql -e "CREATE DATABASE myapp_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
# Creates the database. utf8mb4 supports full Unicode including emojis.
sudo mysql -e "CREATE USER 'myapp'@'localhost' IDENTIFIED BY 'StrongPassword!';"
# Creates the user. localhost restricts connections to the Unix socket or loopback.
sudo mysql -e "GRANT SELECT, INSERT, UPDATE, DELETE ON myapp_db.* TO 'myapp'@'localhost';"
# Grants only the required DML privileges. Avoid ALL PRIVILEGES for apps.
sudo mysql -e "FLUSH PRIVILEGES;"
# Reloads the grant tables. Usually automatic, but explicit here for clarity.

Scope the user to localhost unless the application runs on a different host. If remote access is needed, restrict it to a specific IP rather than using the % wildcard. Wildcards bypass network-level controls and make auditing impossible.

Test the new credentials before wiring them into your application.

mysql -u myapp -p -e "SELECT 1;"
# Verifies authentication works. Replace -p with the actual password or use a prompt.

Drop test users immediately after verification. Unused accounts are audit liabilities.

Bind to localhost

If your application and database are on the same server, prevent MariaDB from listening on external interfaces. Edit the server configuration file in the drop-in directory.

sudo nano /etc/my.cnf.d/mariadb-server.cnf
# Opens the drop-in config. Changes here override package defaults.

Add the binding directive under the [mysqld] section.

[mysqld]
bind-address = 127.0.0.1
# Restricts TCP listeners to the loopback interface.
# Unix socket connections still work normally.

Restart the service to apply the change.

sudo systemctl restart mariadb
# Reloads the configuration and restarts the daemon.

Verify the binding with a network scan or socket check.

ss -tlnp | grep 3306
# Shows listening TCP sockets. Should only list 127.0.0.1:3306.

If you see 0.0.0.0:3306, the configuration did not load. Check for syntax errors in /etc/my.cnf.d/. MariaDB refuses to start on invalid config files and logs the exact line in the journal.

Reboot before you debug network bindings. Half the time stale systemd caches hold the old socket state.

Configure the firewall and SELinux

If remote access is genuinely required, open port 3306 only to trusted sources. Fedora uses firewalld by default. Runtime rules apply immediately. Persistent rules survive reboots. Always apply both.

sudo firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=192.168.1.0/24 port port=3306 protocol=tcp accept'
# Adds a persistent rule. Replace the subnet with your actual trusted range.
sudo firewall-cmd --reload
# Applies the persistent rules to the runtime configuration.

SELinux on Fedora already confines MariaDB with its mysqld_t domain. The database process cannot read arbitrary files or bind to unauthorized ports. If you move the data directory away from the default /var/lib/mysql, you must relabel it.

sudo semanage fcontext -a -t mysqld_db_t "/new/data/dir(/.*)?"
# Adds a persistent file context rule. mysqld_db_t allows database file access.
sudo restorecon -Rv /new/data/dir
# Applies the new context recursively. -v shows what changes.

Verify the context matches expectations.

ls -ldZ /new/data/dir
# Shows the SELinux context. Should end with mysqld_db_t.

If MariaDB refuses to start after moving data, check journalctl -t setroubleshoot. SELinux denials print a one-line summary with the exact policy violation. Read those before disabling SELinux.

Run firewall-cmd --reload after every rule change. Otherwise the runtime config and the persistent config diverge.

Enable automatic security updates

Keep the MariaDB package up to date using dnf-automatic. Fedora's release cadence is six months. Security patches arrive weekly. Manual updates drift. Automated updates stay current.

sudo dnf install dnf-automatic -y
# Installs the timer and service units.
sudo systemctl enable --now dnf-automatic-install.timer
# Enables the timer. Runs daily by default. Checks for security updates.

The timer triggers dnf-automatic-install.service, which downloads and applies only security-critical updates. It does not upgrade major versions. Use dnf upgrade --refresh for routine package updates. Use dnf system-upgrade for crossing Fedora releases. They are different commands.

Check the timer schedule to confirm it is active.

systemctl list-timers dnf-automatic-install.timer
# Shows the next trigger time and last run.

Snapshot the system before the first automatic run. Future-you will thank you.

Common pitfalls and error patterns

The mysql_secure_installation script will refuse to proceed and print ERROR 1045 (28000): Access denied for user 'root'@'localhost' if you previously changed the root authentication plugin to password-based without updating the script's expectations. Switch back to unix_socket or run the script with sudo mysql_secure_installation --use-socket.

If you see [FAILED] Failed to start mariadb.service during boot, your configuration probably contains a syntax error or references a missing data directory. Run journalctl -xeu mariadb and look for InnoDB: Unable to lock ./ibdata1 or Can't open and lock privilege tables. The journal tells you exactly which file or directory is missing.

SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux. Disabling SELinux removes the confinement boundary and exposes the database to local privilege escalation.

Trust the package manager. Manual file edits drift, snapshots stay.

When to use this configuration

Use this hardened localhost setup when your application and database run on the same Fedora host. Use a remote-access configuration with explicit firewall rules when your application runs on a separate server or container. Use unix_socket authentication for administrative tasks and password-based authentication for application connections. Stay on the default data directory unless you have a specific storage requirement.

Where to go next