Server — MariaDB — Network
The configuration article set bind-address = 127.0.0.1 and moved on. That single line does more security work than anything else in the MariaDB configuration. This article is about understanding why, verifying that it is doing what you expect, and adding the audit layer that tells you when something inside the machine is connecting to the database in a way you did not anticipate.
The threat model for MariaDB on February is not an attacker scanning the internet for open port 3306. The network binding prevents that. The more realistic concerns are an application misconfiguration that opens an unintended connection path, a Docker container escaping its network constraints and reaching the host database, or a service running with broader database access than it needs. Auditing at the database level catches those things regardless of whether they came from outside or inside.
Confirming the binding
This was checked in the install article, but the binding is important enough to re-examine here with a bit more rigour.
Confirm what MariaDB is actually listening on at the socket level:
sudo ss -tlnp | grep 3306
The output should show exactly one entry, bound to 127.0.0.1:3306. If it shows 0.0.0.0:3306 or :::3306, the bind-address setting is not in effect and MariaDB is accepting connections from any interface.
Also confirm the Unix socket, which is the other way local processes connect:
sudo ss -xlnp | grep mysql
The Unix socket at /run/mysqld/mysqld.sock is how applications connecting via localhost (as opposed to 127.0.0.1) reach MariaDB. This is normal and correct. The socket is owned by the mysql user and group, and is not world-readable.
Confirm the socket permissions:
ls -la /run/mysqld/mysqld.sock
The output should show srwxrwxrwx with owner mysql and group mysql. The world-writable permission on the socket is standard and expected; access control happens at the MariaDB user authentication layer, not the filesystem.
What is connecting right now
MariaDB tracks active connections in real time. To see what is currently connected:
sudo mariadb -e "SHOW PROCESSLIST;"
On a lightly loaded February you should see one or two system threads and connections from the application users created in the users and permissions article. The Host column shows where each connection originated: localhost for Unix socket connections, 127.0.0.1 for TCP connections on the loopback interface.
If you see a Host value that is not localhost or 127.0.0.1, something is connecting from outside the machine. That should not be possible with bind-address = 127.0.0.1 in effect, but if it appears, it means the binding configuration is not holding and needs immediate investigation.
Run this periodically or after adding a new service to confirm that only expected connections are present.
Auditing with the MariaDB Audit Plugin
The process list shows live connections. The audit plugin records a history: every connection attempt, every connection failure, and optionally every query that runs. For February’s purposes, connection auditing is the right scope: it tells you who connected, when, and whether authentication succeeded or failed.
The audit plugin ships with MariaDB but is not enabled by default. Enable it by adding configuration to /etc/mysql/mariadb.conf.d/50-server.cnf under [mysqld]:
# Audit plugin
plugin-load-add = server_audit
server_audit_logging = ON
server_audit_events = CONNECT
server_audit_file_path = /var/log/mysql/audit.log
server_audit_file_rotate_size = 10485760
server_audit_file_rotations = 5
server_audit_events = CONNECT records only connection and disconnection events, not individual queries. This produces a manageable log volume on a lightly loaded server. If you later need to audit specific query activity — for example, to confirm that an application is only reading and not writing to a database it should only read — you can extend this to CONNECT,QUERY_DDL,QUERY_DCL, which adds schema changes and privilege changes to the log without recording every SELECT.
Do not set server_audit_events = CONNECT,QUERY on a production database unless you have a specific reason and significant disk space. Logging every query on even a moderately active server generates enormous log volume quickly.
The server_audit_file_rotate_size and server_audit_file_rotations settings cap the audit log at five files of 10 MB each, rotating automatically. Fifty megabytes of audit history is generous for February’s connection volume.
Restart MariaDB to load the plugin:
sudo systemctl restart mariadb
Confirm the plugin loaded:
sudo mariadb -e "SHOW PLUGINS;" | grep audit
The output should show SERVER_AUDIT with status ACTIVE. If it shows DISABLED or does not appear at all, the plugin did not load. Check the error log at /var/log/mysql/error.log for the reason.
Reading the audit log
The audit log at /var/log/mysql/audit.log records one event per line in CSV format:
20260510 14:23:01,february,powerdns,localhost,,5,0,CONNECT,powerdns,,0
20260510 14:23:01,february,powerdns,localhost,,5,0,DISCONNECT,powerdns,,0
The fields are: timestamp, server host, username, client host, client IP, connection ID, query ID, operation, database, query text, return code.
A return code of 0 is success. A non-zero return code on a CONNECT event is a failed authentication attempt. To see failed logins specifically:
sudo grep 'CONNECT' /var/log/mysql/audit.log | grep -v ',0$'
On a correctly configured February, this should return nothing. If it returns entries, something is attempting to connect with incorrect credentials. Check the username and client host fields to identify what is making the attempt.
Reviewing user accounts and their access scope
Periodically audit the list of database users and what they are permitted to do. A user account that existed for a service you decommissioned three months ago is an unnecessary attack surface:
sudo mariadb -e "SELECT user, host, authentication_string != '' AS has_password FROM mysql.user ORDER BY user;"
Every user in this list should correspond to something actively running on February. The host column should be localhost for all application users; any user with % as the host can connect from any address, which should not exist given the bind-address constraint but is worth checking explicitly.
To see the specific privileges granted to a user:
sudo mariadb -e "SHOW GRANTS FOR 'powerdns'@'localhost';"
Each application user should have privileges scoped only to its own database. A user with GRANT ALL ON *.* has access to everything; that should only be the root user accessed via unix_socket.
To remove a user that is no longer needed:
sudo mariadb -e "DROP USER 'oldservice'@'localhost';"
Do this as part of the decommissioning process for any service, not as an afterthought.
The Docker consideration
The config article mentioned the Docker/UFW interaction, and it applies here too. Docker containers that use host networking (--net=host), as the PowerDNS Admin container does, share the host’s network stack and can reach 127.0.0.1:3306 directly. Containers using bridge networking cannot reach localhost on the host by default; they reach it via the bridge gateway IP (172.17.0.1 typically), which is also blocked by the bind-address setting.
This is correct behaviour, but it means the audit log will show connections from localhost for host-networked containers and from nothing for bridge-networked containers (which should not be connecting at all). If you add a Docker service that needs database access, the right pattern is always host networking with a dedicated database user scoped to that service’s database, and the audit log will confirm the connection pattern matches expectations.
What normal looks like
A healthy MariaDB audit log on February has a predictable rhythm: connections from known application users at known times, all from localhost, all with return code 0. The process list has a small number of connections from the expected users. No users exist in the mysql.user table beyond those actively in use.
Anything outside that pattern is worth investigating. The audit log does not tell you what to do about an anomaly, but it tells you the anomaly exists, which is the necessary first step. A MariaDB instance that has never had auditing enabled and has been running for months is a database whose connection history you cannot reconstruct. That is a position worth getting out of.