By default, MySQL and MariaDB on Ubuntu and Debian accept connections only from the same machine (127.0.0.1). That’s the right default. When an application server, a BI tool or a replica genuinely needs to connect from elsewhere, open access narrowly: one database user, one source IP, encrypted if it crosses the internet.
First, consider not opening it at all
If you only need to manage the database from your laptop (with DBeaver, MySQL Workbench or TablePlus), use an SSH tunnel — no MySQL configuration changes, no open port:
ssh -N -L 3307:127.0.0.1:3306 [email protected]
Then connect your client to 127.0.0.1:3307 with the database credentials. Most GUI clients have a built-in “SSH tunnel” option that does this for you. See SSH tunnels and port forwarding.
For server-to-server access, a private network or a WireGuard VPN between the servers is the next-best option. Exposing 3306 to the public internet should be the last resort.
Step 1: change the bind address
Find the setting:
sudo grep -rn "bind-address" /etc/mysql/
- MySQL on Ubuntu:
/etc/mysql/mysql.conf.d/mysqld.cnf - MariaDB on Debian/Ubuntu:
/etc/mysql/mariadb.conf.d/50-server.cnf
Set it to the interface that should accept connections:
[mysqld]
bind-address = 10.0.0.5 # private/VPN IP — preferred
# bind-address = 0.0.0.0 # all IPv4 interfaces, only with a strict firewall
Restart and verify:
sudo systemctl restart mysql # or mariadb
sudo ss -tlnp | grep 3306
You should see 10.0.0.5:3306 (or 0.0.0.0:3306) instead of 127.0.0.1:3306.
Step 2: create a user for the remote host
MySQL accounts are user@host. Create one that may connect only from the application server:
CREATE USER 'app'@'10.0.0.6' IDENTIFIED BY 'a-long-random-password';
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'10.0.0.6';
Avoid 'app'@'%' (any host) and never allow remote root. A subnet works too: 'app'@'10.0.0.%'. See MySQL users and privileges.
Step 3: firewall — allow one source only
UFW:
sudo ufw allow from 10.0.0.6 to any port 3306 proto tcp
firewalld:
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.6" port port="3306" protocol="tcp" accept'
sudo firewall-cmd --reload
If any provider-level firewall exists, mirror the rule there.
Step 4: test from the client
mysql -h 10.0.0.5 -u app -p appdb
# or just test the port
nc -zv 10.0.0.5 3306
Step 5: encrypt traffic over the internet
Over a private network or VPN, plain connections are acceptable. Over the public internet, require TLS. MySQL 8 generates certificates automatically and supports TLS out of the box; MariaDB needs certificate paths configured (ssl_cert, ssl_key, ssl_ca).
Force TLS for the account:
ALTER USER 'app'@'203.0.113.6' REQUIRE SSL;
Check from the client session:
SHOW STATUS LIKE 'Ssl_cipher';
A non-empty value means the connection is encrypted. For full protection against impersonation, clients should also verify the server certificate (--ssl-mode=VERIFY_IDENTITY with a proper CA).
Troubleshooting
ERROR 2003 (HY000): Can't connect to MySQL server on '…' (110) — timeout: the firewall (server or provider) drops the packets, or the IP is wrong.
ERROR 2003 … (111) — connection refused: MySQL isn’t listening on that interface. Recheck bind-address and that you restarted the service (and that no second config file overrides it — sudo mysqld --verbose --help | grep bind-address).
ERROR 1130 (HY000): Host '10.0.0.6' is not allowed to connect — no account matches that host. Create 'user'@'10.0.0.6'. Note that MySQL sees the client’s IP as it arrives — behind NAT, that’s the NAT address.
ERROR 1045: Access denied — wrong password, or a different user@host entry matched. SELECT user, host FROM mysql.user; and see MySQL error 1045.
Connections are slow to establish — MySQL does reverse DNS lookups on client IPs. Add skip-name-resolve under [mysqld] (then use IPs, not hostnames, in accounts).
Security checklist
- SSH tunnel or VPN considered first
-
bind-addressset to a private/VPN IP where possible - Dedicated user restricted to the client’s IP and needed privileges
- Firewall allows 3306 from that IP only
- TLS required when crossing the internet
- No remote root; no
'%'hosts - Failed login attempts monitored
Port 3306 open to the world will be found by scanners within hours. Combine these steps with general server hardening. Running app and database on separate VPS servers in the same location keeps latency low while letting each scale independently.