Skip to content

MySQL Tutorial Users, Roles and Security

Remote Access: Safe Ways to Connect

Lesson 254 of 308 5 min read By Deepak Paswan Updated Oct 5, 2026

A database on a server is of no use if your application, your laptop, or a colleague cannot reach it. But MySQL exposed to the internet is attacked constantly. This lesson shows how to allow the connections you need and nothing more.

Layers of control

A remote connection must pass four checks, in this order:

  1. Network: can the packet reach the server? (firewall, security group, routing)
  2. bind_address: does MySQL listen on that network interface?
  3. Account host: does an account exist for that user and that client host?
  4. Privileges: what may that account do?

Failing at any of them gives a different error, which helps with diagnosis (below).

1. bind_address: where MySQL listens

query.sql
SQL
SELECT @@bind_address AS listens_on, @@port AS port, @@skip_networking AS tcp_disabled, @@skip_name_resolve AS no_dns;
Output
OUTPUT
+------------+------+--------------+--------+
| listens_on | port | tcp_disabled | no_dns |
+------------+------+--------------+--------+
| 127.0.0.1  | 3384 |            0 |      0 |
+------------+------+--------------+--------+
1 row in set
bind_addressMeaning
127.0.0.1Only local TCP connections (safest). Remote clients cannot connect
* (the default of many installs)All IPv4 and IPv6 interfaces
10.0.0.5Only that interface (a private network address)
0.0.0.0All IPv4 interfaces

For a database that only a web server on the same machine uses, keep 127.0.0.1 and no remote access at all. For a separate database server, bind it to the private network interface, never to a public address.

2. The firewall

Port 3306 (MySQL) should be reachable only from the machines that need it:

terminal
BASH
# Linux (ufw): allow the web server only
ufw allow from 10.0.0.12 to any port 3306 proto tcp
ufw deny 3306

On cloud providers, use security groups / firewall rules the same way: allow the application subnet, deny the rest. Never allow 0.0.0.0/0 (the whole internet) to port 3306.

3 and 4. Accounts for the right hosts

query.sql
SQL
CREATE USER 'shop_app'@'10.0.0.12' IDENTIFIED BY 'Ap9!pass#Long#Random#1';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'10.0.0.12';
SELECT user, host FROM mysql.user WHERE user = 'shop_app';
Output
OUTPUT
+----------+-----------+
| user     | host      |
+----------+-----------+
| shop_app | 10.0.0.12 |
+----------+-----------+
1 row in set

The account is valid only from the web server’s address. A connection from any other host is refused, even with the right password (Creating Users).

Connecting from your own computer: the SSH tunnel

For administration and development, you rarely need to open MySQL to your own IP. Use an SSH tunnel: the database port stays closed to the world, and your tool talks through an encrypted SSH connection:

terminal
BASH
# local port 3307  ->  (through SSH to the server)  ->  MySQL on 127.0.0.1:3306 there
ssh -N -L 3307:127.0.0.1:3306 deploy@db.example.com

Then connect your client to 127.0.0.1:3307:

terminal
BASH
mysql -h 127.0.0.1 -P 3307 -u shop_report -p

MySQL sees a local connection from the server itself ('shop_report'@'localhost'). MySQL Workbench, DBeaver and others have an “SSH tunnel” option built in. Only people with SSH access to the server can reach the database, and everything is encrypted.

Other safe options

OptionGood for
Private network / VPC with security groupsApplication servers and database servers in the same cloud
VPN (WireGuard, OpenVPN, a cloud VPN)Teams that need regular access to private networks
A bastion hostControlled, logged entry to the private network
Managed database proxies and IAM-based auth (cloud)Short-lived credentials instead of long-term passwords
TLS with client certificatesStrong identity for service-to-service connections (Encrypted Connections)

Shared hosting

Many shared hosts block remote MySQL on purpose. They offer “Remote MySQL” in the control panel, where you add the IP address that may connect. Add only the addresses you really need (not %). Otherwise use their web phpMyAdmin or an SSH tunnel if SSH is available.

Diagnose connection errors

ErrorLayerMeaning and fix
ERROR 2003 (HY000): Can't connect to MySQL server on 'host' (110) or (111)Network / bindTimeout (110): a firewall drops the packets. Refused (111): nothing listens there (server down, wrong port, or bind_address is 127.0.0.1)
ERROR 1130 (HY000): Host '10.0.0.7' is not allowed to connect to this MySQL serverAccount hostNo account matches the client’s host. Create the account for that host, or use the right one
ERROR 1045 (28000): Access denied for user 'x'@'10.0.0.7' (using password: YES)Account / passwordWrong password, or a different account matched first (check the host shown in the message)
ERROR 2013 (HY000): Lost connection to MySQL server at 'reading initial communication packet'NetworkOften a firewall or a TCP wrapper in the middle, or skip_name_resolve and DNS problems
ERROR 3159 (HY000): Connections using insecure transport are prohibited while --require_secure_transport=ONTLSThe server requires TLS, the client connected without it

The host in the “Access denied” message is the one MySQL saw, which tells you which account it tried to match. If you connected through a NAT, a proxy or a tunnel, MySQL sees that address, not your laptop’s.

Test from the client side

terminal
BASH
# is the port reachable?
nc -vz db.example.com 3306            # Linux, macOS
Test-NetConnection db.example.com -Port 3306    # Windows PowerShell

and from MySQL itself, after connecting:

query.sql
SQL
SELECT CURRENT_USER() AS matched_account, USER() AS connected_as, @@hostname AS server_host;
Output
OUTPUT
+----------------------+------------------------+-------------+
| matched_account      | connected_as           | server_host |
+----------------------+------------------------+-------------+
| shop_app@10.0.0.%    | shop_app@10.0.0.12     | db1         |
+----------------------+------------------------+-------------+

(An example of what you would see.) CURRENT_USER() is the account whose privileges apply. USER() is how the client identified itself and from where.

skip_name_resolve

By default, MySQL tries to resolve client IP addresses to host names (a reverse DNS lookup). That is slow, and breaks when DNS has problems. With skip_name_resolve = ON, MySQL uses IP addresses only, so every account host must be an IP or a pattern, not a name. It is the usual production setting.

What not to do

  • bind_address = 0.0.0.0 and GRANT ... TO 'root'@'%': the classic combination that gets servers ransomed.
  • Opening 3306 to the internet “just for a minute”.
  • Using the same account and password for the website and for remote administration.
  • Sending passwords over unencrypted connections across networks.

Within an hour of a new server’s port 3306 being exposed, bots start trying the common passwords for root, admin and mysql.

dangerous
Port 3306 is a target
If you can reach your database port from a random network address, so can everyone else. Check with an external port scan from outside your network, and close what should not be open.