Remote database access makes it possible for application servers, automation jobs, and administrators to connect to a central MySQL or MariaDB instance from another host. It reduces “database sprawl” and keeps state in one place, while still allowing services to run wherever they need to run.
A mysqld-compatible server only accepts remote connections when three layers agree: the daemon is listening on a non-loopback interface (bind-address), the network path allows TCP port 3306 (host firewall and any upstream security group), and a database account exists that matches both username and client origin (accounts are scoped as 'user'@'host').
Exposing port 3306 outside a trusted network is a common way to attract scans, password guessing, and unpleasant surprises. The steps below assume a Linux host with systemd and a Debian/Ubuntu-style configuration under /etc/mysql, using a private IP bind and an allowlist firewall rule to keep the blast radius small.
Remote access is safest on a private network; avoid publishing port 3306 to the public internet without strict allowlisting, TLS, and an additional access layer such as a VPN or SSH tunnel.
Steps to enable remote access to MySQL or MariaDB server:
- Identify the server private IP address that should accept remote connections.
$ sudo docker exec sg-mysql ip --brief address show scope global | rg '^eth0' eth0 UP 192.0.2.40/24
Binding to a specific private IP is usually safer than binding to every interface.
- Check which address the database currently listens on for TCP port 3306.
$ sudo docker exec sg-mysql ss --listening --numeric --tcp --processes | rg ':3306 ' LISTEN 0 151 192.0.2.40:3306 0.0.0.0:*
- Locate the active configuration line that sets bind-address.
$ sudo docker exec sg-mysql grep --recursive --line-number --extended-regexp '^[[:space:]]*bind-address' /etc/mysql /etc/mysql/conf.d/bind-address.cnf:2:bind-address = 192.0.2.40
MariaDB commonly uses /etc/mysql/mariadb.conf.d/50-server.cnf for server settings.
- Edit the server configuration so bind-address listens on the server private IP.
$ sudoedit /etc/mysql/conf.d/bind-address.cnf
[mysqld] bind-address = 192.0.2.40
Setting bind-address to 0.0.0.0 exposes the database on every interface; combining that with a permissive firewall can enable credential guessing and unauthorized data access.
- Restart the database service to apply the listener change.
$ sudo docker restart sg-mysql sg-mysqlOn non-container hosts, restart the systemd unit for MySQL or MariaDB.
- Verify the database service is active after the restart.
$ sudo docker ps --filter name=sg-mysql --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' NAMES IMAGE STATUS sg-mysql mysql:8.0 Up 4 minutes
- Confirm the server is listening on the intended address for TCP port 3306.
$ sudo docker exec sg-mysql ss --listening --numeric --tcp --processes | rg ':3306 ' LISTEN 0 151 192.0.2.40:3306 0.0.0.0:*
- Provision a least-privilege remote account restricted to the client host.
$ sudo mysql mysql> CREATE USER 'appuser'@'203.0.113.10' IDENTIFIED BY 'RemotePass123!'; Query OK, 0 rows affected (0.01 sec) mysql> GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'appuser'@'203.0.113.10'; Query OK, 0 rows affected (0.00 sec) mysql> SHOW GRANTS FOR 'appuser'@'203.0.113.10'; +------------------------------------------------------------------------------+ | Grants for appuser@203.0.113.10 | +------------------------------------------------------------------------------+ | GRANT USAGE ON *.* TO `appuser`@`203.0.113.10` | | GRANT SELECT, INSERT, UPDATE, DELETE ON `appdb`.* TO `appuser`@`203.0.113.10` | +------------------------------------------------------------------------------+ 2 rows in set (0.00 sec) mysql> EXIT Bye
Keep the host part specific; '%' allows logins from anywhere that can reach port 3306.
Require encrypted transport when TLS is configured: ALTER USER 'appuser'@'203.0.113.10' REQUIRE SSL;
- Allow inbound TCP port 3306 from the client address in ufw.
$ sudo ufw allow from 203.0.113.10 to any port 3306 proto tcp Rules updated
Cloud security groups and upstream firewalls must also allow TCP 3306 from the same client IP.
- Test a remote connection from the client host using the new account.
$ mysql --host=192.0.2.40 --port=3306 --user=appuser --password --database=appdb --execute="SELECT CURRENT_USER(), @@hostname, @@port;" CURRENT_USER() @@hostname @@port appuser@203.0.113.10 host 3306
Common failures map to specific fixes: ERROR 2003 usually indicates routing/firewall/listener issues; ERROR 1130 usually indicates the account host ('user'@'host') does not match the client IP; Access denied usually indicates password, privileges, or authentication plugin mismatch.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
