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:

  1. 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.

  2. 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:*          
  3. 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.

  4. 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.

  5. Restart the database service to apply the listener change.
    $ sudo docker restart sg-mysql
    sg-mysql

    On non-container hosts, restart the systemd unit for MySQL or MariaDB.

  6. 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
  7. 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:*          
  8. 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;

  9. 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.

  10. 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.