Back to Blog
Troubleshooting 15 min read

TL;DR — Check These 5 Things First 1. bind-address in my.cnf is set to 127.0.0.1 (MySQL ignores all remote connections by default). 2. Firewall or security group is blocking port 3306. 3. MySQL user only allows 'user'@'localhost' — not 'user'@'%'. 4. IP not whitelisted in your cloud provider's trusted sources list. 5. SSL required but your client isn't using it (common with managed databases).

Each fix takes under 5 minutes. Full diagnostic walkthrough below.

Table of Contents

Can't Connect to Remote MySQL Database? Here's a Step-by-Step Fix

You're staring at a MySQL error message — ERROR 2003, ERROR 1045, or the ever-helpful Can't connect to MySQL server on 'hostname' — and your application is dead in the water. You know the database is running. You can see it in your cloud provider's dashboard. But your client refuses to connect.

If you can't connect to remote MySQL database instances, you are almost certainly hitting one of nine specific causes. The tricky part is that the error messages overlap. A firewall block and a bind-address misconfiguration can produce the exact same ERROR 2003 (HY000) output. You need a systematic approach.

This guide walks through every cause from most common to least common, with the exact commands to diagnose and fix each one. Start at Step 1 and work down — most people find their issue in the first three steps.

Step 1: MySQL Is Not Listening on the Network Interface

The symptom: You get ERROR 2003 (HY000): Can't connect to MySQL server on 'x.x.x.x' (111) or Connection refused. You can connect locally on the server with mysql -u root -p, but remote connections fail.

Why this happens: By default, MySQL binds to 127.0.0.1 — the loopback address. This means it only accepts connections from the machine it's running on. Every remote connection attempt is rejected at the network level before MySQL even sees it.

Diagnose it:

SSH into the MySQL server and check what address MySQL is listening on:

# Check the bind-address in the MySQL config
# Ubuntu/Debian:
cat /etc/mysql/mysql.conf.d/mysqld.cnf | grep bind-address

# CentOS/RHEL/Amazon Linux:
cat /etc/my.cnf | grep bind-address

# macOS (Homebrew):
cat /usr/local/etc/my.cnf | grep bind-address
# or for Apple Silicon:
cat /opt/homebrew/etc/my.cnf | grep bind-address

If you see bind-address = 127.0.0.1, that's your problem. You can also verify with netstat or ss:

# Check which address and port MySQL is listening on
ss -tlnp | grep 3306
# or
netstat -tlnp | grep 3306

If the output shows 127.0.0.1:3306, MySQL is only accepting local connections. If it shows 0.0.0.0:3306 or :::3306, it's listening on all interfaces and the problem is elsewhere.

Fix it:

Edit the MySQL configuration file and change the bind-address directive:

# /etc/mysql/mysql.conf.d/mysqld.cnf (Ubuntu/Debian)
# or /etc/my.cnf (CentOS/RHEL)

[mysqld]
# Listen on all network interfaces:
bind-address = 0.0.0.0

# Or bind to a specific private IP if you prefer:
# bind-address = 10.0.1.50

Restart MySQL for the change to take effect:

# systemd (Ubuntu 16.04+, CentOS 7+, Debian 9+):
sudo systemctl restart mysql
# or on some systems:
sudo systemctl restart mysqld

# Verify it's now listening on 0.0.0.0:
ss -tlnp | grep 3306

Security note: Setting bind-address = 0.0.0.0 means MySQL will accept connections from any IP — but only if they also pass firewall rules AND have valid user credentials. It does not mean the database is open to the world. Your firewall (Step 2) and user grants (Step 3) are the actual access controls.

If you're on a managed database (AWS RDS, DigitalOcean, PlanetScale, etc.): You don't have access to my.cnf. The provider handles bind-address configuration, and it's already set to accept remote connections. Skip to Step 2.

Step 2: Firewall Is Blocking Port 3306

The symptom: ERROR 2003 (HY000): Can't connect to MySQL server on 'x.x.x.x' (110) — note the error code 110 means connection timed out, compared to 111 (connection refused) in Step 1. You may also see mysql connection timeout errors in your application logs.

Why this happens: A firewall between your client and the MySQL server is silently dropping packets on port 3306. This could be the server's local firewall (iptables, ufw, firewalld), a cloud provider security group, or a network-level firewall.

Diagnose it:

First, test whether port 3306 is reachable from your client machine:

# From your local machine or application server:
telnet your-db-host.example.com 3306
# or
nc -zv your-db-host.example.com 3306

If it hangs (timeout) or returns Connection refused, the port is blocked. If you see garbled output that starts with a MySQL version string (like 5.7.42 or 8.0.35), the port is open and the issue is elsewhere.

Then check the server-side firewall:

# UFW (Ubuntu):
sudo ufw status verbose

# iptables (any Linux):
sudo iptables -L -n | grep 3306

# firewalld (CentOS/RHEL/Fedora):
sudo firewall-cmd --list-all

Fix it:

Open port 3306 for your specific IP address (do NOT open it to 0.0.0.0/0 in production):

# UFW (Ubuntu) — allow from a specific IP:
sudo ufw allow from 203.0.113.50 to any port 3306
sudo ufw reload

# iptables — allow from a specific IP:
sudo iptables -A INPUT -p tcp -s 203.0.113.50 --dport 3306 -j ACCEPT
# Make it persistent (Debian/Ubuntu):
sudo apt install iptables-persistent
sudo netfilter-persistent save

# firewalld (CentOS/RHEL) — allow from a specific IP:
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.50" port protocol="tcp" port="3306" accept'
sudo firewall-cmd --reload

For cloud provider security groups: AWS, DigitalOcean, GCP, and Azure all wrap your instance in a security group or firewall that blocks all inbound traffic by default. You need to add an inbound rule allowing TCP port 3306 from your IP. This is separate from the OS-level firewall.

For example, on AWS: 1. Go to EC2 > Security Groups > select the group attached to your RDS instance 2. Inbound rules > Edit > Add rule 3. Type: MySQL/Aurora, Port: 3306, Source: your IP or CIDR block

How to whitelist a database IP across every major cloud provider ->

Connecting from a dynamic IP? If your IP changes regularly (home internet, coffee shops, co-working spaces), maintaining firewall rules is painful. DBEverywhere gives you a static IP to whitelist once — all your database connections route through it regardless of where you are.

Step 3: MySQL User Not Allowed from Remote Host

The symptom: ERROR 1130 (HY000): Host '203.0.113.50' is not allowed to connect to this MySQL server. This is one of the most common causes of mysql access denied remote errors.

Why this happens: MySQL user accounts are defined as 'username'@'host'. A user created as 'appuser'@'localhost' can only connect from the local machine. Attempting to connect from any other IP produces ERROR 1130 — even if the password is correct.

Diagnose it:

Connect to MySQL locally (on the server itself) and check the user's host permissions:

-- See all users and their allowed hosts:
SELECT user, host FROM mysql.user;

-- Check grants for a specific user:
SHOW GRANTS FOR 'appuser'@'localhost';

If you see 'appuser'@'localhost' but no 'appuser'@'%' or 'appuser'@'203.0.113.50', the user is restricted to local connections.

Fix it:

Create a user that can connect from remote hosts, or update the existing one:

-- Option A: Allow from any host (use with firewall restrictions)
CREATE USER 'appuser'@'%' IDENTIFIED BY 'your_strong_password';
GRANT ALL PRIVILEGES ON your_database.* TO 'appuser'@'%';
FLUSH PRIVILEGES;

-- Option B: Allow from a specific IP only (more restrictive)
CREATE USER 'appuser'@'203.0.113.50' IDENTIFIED BY 'your_strong_password';
GRANT ALL PRIVILEGES ON your_database.* TO 'appuser'@'203.0.113.50';
FLUSH PRIVILEGES;

-- Option C: Allow from a subnet
CREATE USER 'appuser'@'203.0.113.%' IDENTIFIED BY 'your_strong_password';
GRANT ALL PRIVILEGES ON your_database.* TO 'appuser'@'203.0.113.%';
FLUSH PRIVILEGES;

Important: If a user 'appuser'@'localhost' already exists and you create 'appuser'@'%', MySQL treats these as two separate accounts. They can have different passwords and different privileges. MySQL matches the most specific host first — so 'appuser'@'localhost' still handles local connections, and 'appuser'@'%' handles everything else.

For managed databases like AWS RDS, you typically create the user through the RDS master account. The master user already has '%' as its host, so the issue is more commonly about secondary users you've created.

Step 4: Wrong Credentials

The symptom: ERROR 1045 (28000): Access denied for user 'appuser'@'203.0.113.50' (using password: YES).

Why this happens: The username/password combination is wrong, or the user exists for a different host (see Step 3). This is also the error you get when using mysql_native_password credentials against a server expecting caching_sha2_password (MySQL 8.0+ default).

Diagnose it:

-- Check if the user exists at all:
SELECT user, host, plugin FROM mysql.user WHERE user = 'appuser';

The plugin column tells you the authentication method. MySQL 8.0+ defaults to caching_sha2_password, while MySQL 5.7 used mysql_native_password. Some older clients don't support caching_sha2_password.

Fix it:

-- Reset the password:
ALTER USER 'appuser'@'%' IDENTIFIED BY 'new_strong_password';
FLUSH PRIVILEGES;

-- If your client doesn't support caching_sha2_password, switch the auth plugin:
ALTER USER 'appuser'@'%' IDENTIFIED WITH mysql_native_password BY 'new_strong_password';
FLUSH PRIVILEGES;

Double-check that you're using the right database name. ERROR 1045 can also appear if you specify a database the user has no access to with the -D flag.

Step 5: SSL/TLS Required but Not Configured

The symptom: ERROR 2026 (HY000): SSL connection error: error:00000001:lib(0):func(0):reason(1) or ERROR 1045 with a valid password (because the server rejected the non-SSL connection).

Why this happens: Most managed database providers — AWS RDS, DigitalOcean Managed Databases, PlanetScale, Aiven, Google Cloud SQL — require SSL/TLS connections by default. If your client doesn't present SSL parameters, the connection is rejected. This is an increasingly common cause of mysql connection refused errors.

Diagnose it:

-- Check if the server requires SSL (connect locally first):
SHOW VARIABLES LIKE 'require_secure_transport';

-- Check if a specific user requires SSL:
SELECT user, host, ssl_type FROM mysql.user WHERE user = 'appuser';

If require_secure_transport is ON, or the user's ssl_type is ANY, X509, or SPECIFIED, SSL is mandatory.

Fix it:

Connect using SSL flags:

# MySQL CLI with SSL:
mysql -h your-db-host.example.com -u appuser -p \
  --ssl-mode=REQUIRED

# With a CA certificate (common for managed databases):
mysql -h your-db-host.example.com -u appuser -p \
  --ssl-mode=VERIFY_CA \
  --ssl-ca=/path/to/ca-certificate.crt

For application connection strings:

# Python (mysql-connector-python):
mysql+mysqlconnector://appuser:password@host:3306/db?ssl_ca=/path/to/ca.pem

# PHP (PDO):
$pdo = new PDO($dsn, $user, $pass, [
    PDO::MYSQL_ATTR_SSL_CA => '/path/to/ca-certificate.crt',
]);

# Node.js (mysql2):
const connection = mysql.createConnection({
  host: 'your-db-host.example.com',
  user: 'appuser',
  password: 'password',
  ssl: { ca: fs.readFileSync('/path/to/ca-certificate.crt') }
});

Most providers offer their CA certificate as a download. AWS RDS uses a global bundle that covers all regions.

Step 6: IP Not Whitelisted in Cloud Provider

The symptom: Connection times out with no error message, or you get ERROR 2003 with error code 110 (timeout). You've confirmed that bind-address, firewalls, and user grants are all correct — but the cloud provider's network-level access control is silently dropping your traffic.

Why this happens: Managed databases on DigitalOcean, PlanetScale, Aiven, and others use a trusted sources list (sometimes called an "allow list" or "access control list") that is separate from MySQL's own user permissions. Even if your MySQL user is 'appuser'@'%', the provider's network layer blocks your IP before it reaches MySQL.

Fix it:

Add your IP (or your server's IP) to the provider's trusted sources:

  • DigitalOcean: Databases > your cluster > Settings > Trusted Sources > Add
  • PlanetScale: Database > Settings > Allowed IP addresses
  • AWS RDS: EC2 > Security Groups > Inbound rules (see Step 2)
  • Google Cloud SQL: SQL > your instance > Connections > Networking > Authorized networks > Add
  • Azure Database for MySQL: Server > Networking > Firewall rules > Add

The static IP advantage: If you connect through a service with a fixed IP, you add it once and never touch the allow list again — even when your team members work from different locations. This is one of the primary reasons developers use DBEverywhere: a single static IP to whitelist across every provider.

How IP whitelisting works with managed databases ->

Step 7: DNS Resolution Failure

The symptom: ERROR 2005 (HY000): Unknown MySQL server host 'your-db-host.example.com' (0) or Can't connect to MySQL server on 'your-db-host.example.com' (Name or service not known).

Why this happens: Your client machine can't resolve the database hostname to an IP address. This could be a typo in the hostname, a DNS outage, or a private hostname that's only resolvable within a specific VPC or network.

Diagnose it:

# Check if the hostname resolves:
nslookup your-db-host.example.com
# or
dig your-db-host.example.com

# If it doesn't resolve, try the IP directly:
mysql -h 10.0.1.50 -u appuser -p

Fix it:

  • Typo in hostname: Double-check the connection string. AWS RDS endpoints are long (mydb.abc123xyz.us-east-1.rds.amazonaws.com) and easy to copy incorrectly.
  • Private DNS: If the hostname is only resolvable inside a VPC (common with AWS RDS in private subnets), you need to connect from within that VPC or use a VPN/tunnel.
  • DNS outage: Use the IP address directly as a workaround. Find it with dig or nslookup from a machine that can resolve it, then connect using the IP.
  • Stale DNS cache: Flush your local cache: sudo systemd-resolve --flush-caches (Linux) or sudo dscacheutil -flushcache (macOS).

Step 8: Max Connections Reached

The symptom: ERROR 1040 (08004): Too many connections. This happens intermittently rather than consistently.

Why this happens: MySQL has a global max_connections setting (default: 151). When all connection slots are occupied — often by idle connections from application connection pools, ORM frameworks, or orphaned sessions — new connections are refused.

Diagnose it:

-- Check current vs. max connections:
SHOW VARIABLES LIKE 'max_connections';
SHOW STATUS LIKE 'Threads_connected';

-- See who's connected:
SHOW PROCESSLIST;

-- Check the historical high-water mark:
SHOW STATUS LIKE 'Max_used_connections';

If Threads_connected is at or near max_connections, you've found the issue.

Fix it:

-- Temporarily increase (resets on restart):
SET GLOBAL max_connections = 300;

-- Permanently increase (add to my.cnf):
-- [mysqld]
-- max_connections = 300

-- Kill idle connections hogging slots:
-- Find sleeping connections:
SELECT id, user, host, db, command, time
FROM information_schema.processlist
WHERE command = 'Sleep' AND time > 300;

-- Kill them:
KILL 12345;

For managed databases, you typically adjust max_connections through the provider's parameter group or configuration panel rather than SQL commands.

Step 9: Network or ISP Blocking Port 3306

The symptom: You've verified everything above, but port 3306 is unreachable. The same connection works from a different network (like a mobile hotspot or VPN).

Why this happens: Some corporate networks, university Wi-Fi, and even residential ISPs block outbound traffic on non-standard ports. Port 3306 is not considered "essential" like 80 (HTTP) or 443 (HTTPS), so it gets filtered. This is a common hidden cause of mysql connection timeout errors that only appear on certain networks.

Diagnose it:

# Test port 3306 connectivity:
nc -zv your-db-host.example.com 3306

# Compare with a known-open port:
nc -zv your-db-host.example.com 443

# If 443 works but 3306 doesn't, the port is likely blocked by your network.

Fix it:

  • Switch networks: Tether to your phone, use a VPN, or connect from a different network to confirm.
  • Ask your DB admin to use a non-standard port: MySQL can run on any port (e.g., 3307, 33060, or even 443). Change the port directive in my.cnf and restart.
  • Use an SSH tunnel: Route your MySQL traffic through an SSH connection on port 22, which is rarely blocked.
  • Use a browser-based tool: Services like DBEverywhere connect to your database over HTTPS (port 443) from the browser, then route the database traffic from the server side where port 3306 is open. The connection between your browser and DBEverywhere is on port 443 — no ISP blocking.

MySQL Error Message Reference Table

Here's a quick reference mapping common error messages to their most likely causes:

Error Message Most Likely Cause Step
ERROR 2003 (HY000) Can't connect to MySQL server on 'host' (111) bind-address set to 127.0.0.1, or MySQL not running Step 1
ERROR 2003 (HY000) Can't connect to MySQL server on 'host' (110) Firewall blocking port 3306, or IP not whitelisted Step 2, Step 6
ERROR 1130 (HY000) Host 'x.x.x.x' is not allowed to connect MySQL user restricted to localhost Step 3
ERROR 1045 (28000) Access denied for user 'x'@'x.x.x.x' (using password: YES) Wrong password, wrong user, or auth plugin mismatch Step 4
ERROR 1045 (28000) Access denied for user 'x'@'x.x.x.x' (using password: NO) Password not provided in connection string Step 4
ERROR 2026 (HY000) SSL connection error Server requires SSL, client not configured for it Step 5
ERROR 2005 (HY000) Unknown MySQL server host 'hostname' DNS resolution failure or typo in hostname Step 7
ERROR 1040 (08004) Too many connections All connection slots occupied Step 8

For the complete list, see the MySQL Server Error Message Reference in the official documentation.

FAQ

Why can I connect to MySQL locally but not remotely?

Three things must all be true for a remote connection to work: MySQL must be listening on a network-accessible interface (not just 127.0.0.1), the firewall must allow traffic on port 3306 from your IP, and the MySQL user must be granted access from your host. Local connections bypass all three — they use a Unix socket or loopback adapter. Start with Step 1 and work through Step 3.

Is it safe to set bind-address to 0.0.0.0?

Yes, as long as your firewall restricts which IPs can reach port 3306 and your MySQL users have strong passwords with specific host grants. bind-address = 0.0.0.0 tells MySQL to listen on all interfaces — it does not grant access to anyone. The firewall and user authentication are your actual access controls. The MySQL documentation on networking covers this in detail.

How do I connect to a managed database that requires SSL?

Download the CA certificate from your provider (AWS RDS, DigitalOcean, PlanetScale, etc.) and pass it in your connection string or client configuration. See Step 5 for exact commands per language. Most providers document their CA bundle download URL in their database connection guides.

My IP keeps changing — how do I keep my database accessible?

Dynamic IPs are one of the biggest headaches with database firewalls. You have three options: use a VPN with a static exit IP, set up an SSH tunnel through a server with a fixed IP, or use a browser-based gateway like DBEverywhere that provides a static IP you whitelist once. More on IP whitelisting strategies ->

Does ERROR 2003 always mean the database is down?

No. ERROR 2003 means "I can't establish a TCP connection to that host and port." The database might be running fine but unreachable because of a firewall, a bind-address restriction, a cloud provider access list, or even a DNS issue. It is a network-layer error, not a MySQL-layer error. Work through Steps 1, 2, 6, 7, and 9 before assuming the server is actually down.

Conclusion

When you can't connect to remote MySQL database instances, the problem is almost always one of these nine causes — and the first three (bind-address, firewall, user host grants) account for the vast majority of cases. The frustrating part is that several of them produce the same ERROR 2003 message, which is why a systematic top-to-bottom diagnostic matters.

Here's the short version:

  1. Check bind-address in my.cnf — change 127.0.0.1 to 0.0.0.0.
  2. Open port 3306 in your firewall and cloud security group.
  3. Create a MySQL user with '%' or your specific IP as the host.
  4. Verify credentials and authentication plugin (caching_sha2_password vs. mysql_native_password).
  5. Add SSL flags if your provider requires them.
  6. Add your IP to the provider's trusted sources list.
  7. Confirm DNS resolves correctly — use the IP directly if it doesn't.
  8. Check max_connections if the error is intermittent.
  9. Test from a different network if nothing else works.

If you're tired of chasing firewall rules and IP whitelists every time you need to check a table, DBEverywhere gives you phpMyAdmin and Adminer in your browser with a single static IP to whitelist. No local software, no SSH tunnels to maintain, no bind-address debugging. Five free sessions per month, $5/mo for unlimited.

Try DBEverywhere free ->

Try DBEverywhere Free

Access your database from any browser. No installation, no Docker, no SSH tunnels.

Get Started