How to Fix MySQL Connection Refused Error
A MySQL "connection refused" error stops your application cold the moment it tries to reach the database. Whether you see it as Can't connect to MySQL server, ERROR 2003 (HY000), or a generic Connection refused from a library, the meaning is the same: the client sent a TCP packet to the host and port where it believed MySQL was running, and the operating system on the far end actively rejected it. Nobody answered on that port.
The good news is that "refused" is a precise signal. It tells you the network path is mostly intact — the host was reachable, the port was closed, and the rejection came back in milliseconds. That narrows the problem to a small, predictable set of causes: the mysqld process is not running, it is listening on the wrong port or interface, a firewall is blocking port 3306, the user is not allowed to connect from that host, or — in containerized setups — the Docker networking or port mapping is misconfigured. The six steps below walk through each cause in the order you should check them, from the simplest to the most subtle.
What Does 'Connection Refused' Mean in MySQL?
In MySQL, connection refused is a transport-layer failure. The TCP handshake never completes because no process is accepting connections on the target port (default 3306). The client never reaches the MySQL authentication stage, so the database never gets a chance to validate a username or password. The error appears instantly, which is the key diagnostic clue.
This is fundamentally different from access denied, which is an application-layer failure. With access denied, the TCP connection succeeds, MySQL accepts it, and only then rejects the credentials — producing ERROR 1045 (28000): Access denied for user. The two errors look superficially similar in an application log, but their root causes and fixes are completely different. A refused connection means "fix the server, the port, or the firewall." An access-denied error means "fix the username, password, or the host the user is allowed to connect from."
There is a third related signal worth knowing: a timeout. If the client waits many seconds and then gives up with no response at all, the host was unreachable or a firewall silently dropped the packet. Refused is fast; timed out is slow and silent. Knowing which one you have determines where to look first.
Common Causes
| Cause | Error Message | Fix |
|---|---|---|
| mysqld not running | Can't connect to MySQL server (2003) |
systemctl start mysql |
| Wrong port in client config | Connection refused on non-3306 port |
Correct the port in the connection string |
| bind-address = 127.0.0.1 | Refused from remote, works locally | Set bind-address = 0.0.0.0 |
| Firewall blocks 3306 | Connection refused or timeout |
Allow 3306 in ufw/firewalld/security group |
| User not allowed from host | ERROR 1130 (HY000): Host not allowed |
GRANT ... TO 'user'@'%' |
| Docker port not published | Refused from host to container | Map -p 3306:3306 in run/compose |
Step-by-Step Fix Guide
Step 1: Check if mysqld is running
The single most common cause is that the MySQL daemon is simply not running. A crashed mysqld, a failed reboot, or a package update that stopped the service all produce an instant refusal. Check the service status first:
# Debian / Ubuntu
sudo systemctl status mysql
# RHEL / CentOS / Fedora
sudo systemctl status mysqld
# MariaDB
sudo systemctl status mariadb
If the status reads inactive, failed, or dead, start the service and enable it so it survives a reboot:
sudo systemctl start mysql
sudo systemctl enable mysql
# If it fails to start, read the logs for the real reason
sudo journalctl -u mysql -n 50 --no-pager
Common crash reasons in the logs include out-of-disk conditions (No space left on device), a corrupted ibdata1 file, or a port conflict where another process already grabbed 3306. Fix the underlying cause before restarting, or mysqld will just exit again.
Step 2: Verify the port MySQL is listening on
If mysqld reports active (running) but you still get a refusal, confirm it is actually listening on the port your client expects. MySQL can be configured to use a non-default port, or it can bind only to the loopback interface.
# Using ss (modern, preferred)
sudo ss -tlnp | grep mysql
# Using netstat (legacy)
sudo netstat -tlnp | grep mysql
Read the Local Address:Port column carefully:
LISTEN 0 151 127.0.0.1:3306 0.0.0.0:* users:(("mysqld",pid=1234,fd=21))
LISTEN 0 151 0.0.0.0:3306 0.0.0.0:* users:(("mysqld",pid=1234,fd=22))
127.0.0.1:3306 means MySQL accepts connections only from the local machine — any remote client will be refused. 0.0.0.0:3306 means it accepts connections on every interface. If the port is not 3306, your client connection string must match it (for example, --port=3307 or jdbc:mysql://host:3307/db).
Step 3: Check bind-address in my.cnf or my.ini
The listening interface is controlled by the bind-address directive in the MySQL configuration file. Many distributions ship MySQL bound to 127.0.0.1 by default for security, which is why remote connections are refused even when the server is healthy.
# Linux config locations
sudo grep -R bind-address /etc/mysql/
# Typical file: /etc/mysql/mysql.conf.d/mysqld.cnf
# Windows config location
# C:\ProgramData\MySQL\MySQL Server 8.0\my.ini
Open the file and locate the [mysqld] section. To allow remote connections, change the bind address to all interfaces:
[mysqld]
bind-address = 0.0.0.0
# Optional: restrict to a specific interface or IP
# bind-address = 192.168.1.50
After saving, restart MySQL for the change to take effect:
sudo systemctl restart mysql
Be aware that binding to 0.0.0.0 exposes MySQL to every network the host is attached to. Only do this behind a firewall, and combine it with strong passwords and a least-privilege user setup (see Step 5).
Step 4: Configure firewall rules
Even when MySQL listens on 0.0.0.0:3306, a host firewall or cloud security group can still reject or drop the connection. Check and open port 3306 using whichever firewall is active on the server:
# ufw (Ubuntu / Debian)
sudo ufw status
sudo ufw allow 3306/tcp
# firewalld (RHEL / CentOS)
sudo firewall-cmd --permanent --add-port=3306/tcp
sudo firewall-cmd --reload
# iptables (any distro)
sudo iptables -A INPUT -p tcp --dport 3306 -j ACCEPT
sudo iptables -L -n | grep 3306
Do not forget the cloud layer. AWS Security Groups, Azure Network Security Groups, and Google Cloud firewall rules operate independently of the OS firewall — a closed inbound rule on port 3306 there produces an instant refusal that looks identical to a local block. Add an inbound TCP rule for 3306 scoped to the client IP range whenever possible, rather than opening it to 0.0.0.0/0.
Step 5: Check MySQL user privileges and host
If the TCP connection now succeeds but you are still rejected at the MySQL layer, the connecting user may not be permitted to connect from the client's host. MySQL matches the user against both the username and the originating host stored in mysql.user. A user defined as 'app'@'localhost' cannot connect from a remote IP, and MySQL returns ERROR 1130 (HY000): Host 'x.x.x.x' is not allowed to connect to this MySQL server.
Log in locally as root and inspect the allowed hosts:
SELECT User, Host FROM mysql.user WHERE User = 'appuser';
-- Allow the user to connect from any host (use sparingly)
CREATE USER IF NOT EXISTS 'appuser'@'%' IDENTIFIED BY 'strong-password';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'%';
FLUSH PRIVILEGES;
For production, prefer a specific host or subnet over % — for example 'appuser'@'10.0.0.%' — to limit the blast radius if the credentials leak. After changing grants, run FLUSH PRIVILEGES; (or rely on the automatic reload in MySQL 8.0+) and retry the connection.
Step 6: Test connection with mysql CLI and telnet or nc
Before blaming your application framework, reproduce the failure with the simplest possible tools. First, connect with the official mysql client using the exact host, port, and credentials your app uses:
mysql -h 192.168.1.50 -P 3306 -u appuser -p appdb
If this fails, the error message is now precise (2003, 1045, or 1130) and points you back to the matching step above. If the mysql client succeeds but your app still fails, the issue is in the app's connection string or driver, not in MySQL itself.
To isolate the network from the database, test the raw TCP port with telnet or nc. These tools bypass MySQL entirely — a successful connection proves the port is open; a refusal proves it is not:
# telnet
telnet 192.168.1.50 3306
# netcat
nc -zv 192.168.1.50 3306
A healthy MySQL port returns a garbled handshake string (the MySQL greeting); a closed port returns Connection refused immediately. This two-tool approach cleanly separates network problems from database problems.
Docker MySQL Connection Fixes
MySQL inside Docker adds two extra layers that commonly cause refused connections: container networking and port publishing. By default, a container's port is not reachable from the host unless you explicitly map it.
Run the container with a published port so the host (and other machines) can reach MySQL:
docker run -d \
--name mysql \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=secret \
mysql:8.0
With docker-compose, define the port mapping and a named network so other services can reach MySQL by service name:
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: appdb
ports:
- "3306:3306"
networks:
- appnet
app:
image: myapp:latest
depends_on:
- db
environment:
DB_HOST: db # connect by service name, not localhost
DB_PORT: "3306"
networks:
- appnet
networks:
appnet:
Two Docker-specific traps cause most refusals. First, an app running on the host connects to 127.0.0.1:3306, but an app running inside another container must connect to the service name db (or its container IP) — localhost inside a container refers to that container, not the database. Second, MySQL's bind-address inside the official image is * by default, but a custom my.cnf mounted as a volume can override it back to 127.0.0.1, re-introducing the refusal. Inspect the effective config with docker exec -it mysql mysql -e "SHOW VARIABLES LIKE 'bind_address'".
Configuration Examples
A correct, remote-ready my.cnf for a Linux server behind a firewall:
[mysqld]
# Listen on all interfaces so remote clients can reach the server
bind-address = 0.0.0.0
# Default port; change only if you also update clients and firewall
port = 3306
# Connection limit; raise if you hit "Too many connections"
max_connections = 200
# Require TLS for remote sessions (recommended)
require_secure_transport = ON
Corresponding SQL to create a least-privilege remote user and apply it:
-- Create a user that can connect from a specific subnet only
CREATE USER 'appuser'@'10.0.0.0/255.255.255.0'
IDENTIFIED BY 'a-long-random-password';
-- Grant only what the app needs on a single database
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'appuser'@'10.0.0.0/255.255.255.0';
-- Apply the changes
FLUSH PRIVILEGES;
-- Verify the user and host
SELECT User, Host FROM mysql.user WHERE User = 'appuser';
Quick Reference Table
| Error Code | Message | Meaning |
|---|---|---|
| 2002 | Can't connect to local MySQL server through socket |
Wrong socket path or mysqld not running locally |
| 2003 | Can't connect to MySQL server on 'host' (111) |
Connection refused — port closed, service down, or firewall |
| 1045 | Access denied for user |
TCP connected; wrong username, password, or host not granted |
| 1130 | Host 'x.x.x.x' is not allowed to connect |
User exists but not for the connecting host — fix GRANT host |
| 1698 | Access denied for user 'root'@'localhost' |
Ubuntu auth_socket plugin; use sudo mysql or switch plugin |
FAQ
Why do I get "connection refused" but a coworker can connect fine?
The difference is almost always the source. Your coworker may be on a network or IP that the firewall allows, while yours is blocked, or their MySQL user has a permissive Host value (like %) while yours is restricted to localhost. Check the cloud security group, the host firewall, and the mysql.user host column for your specific IP.
Is "connection refused" the same as a timeout?
No. A refused connection returns immediately because the target port is closed or nothing is listening. A timeout returns slowly after the client gives up waiting, which usually means the host is unreachable or a firewall is silently dropping packets rather than rejecting them. The speed of the failure tells you which problem you have.
Should I set bind-address to 0.0.0.0 in production?
Only behind a firewall, and ideally restricted to a private subnet. Binding to 0.0.0.0 exposes MySQL to every interface the host has. Combine it with a strict firewall rule on port 3306, strong per-user passwords, TLS (require_secure_transport=ON), and least-privilege grants scoped to a specific IP range instead of %.
Why does my Docker app connect on localhost but fail from another container?
Inside a container, localhost refers to that container, not the database. From the host you can use 127.0.0.1:3306 (mapped via -p), but from another container on the same Docker network you must use the database service name (for example db) or its container IP. Put both services on the same named network and reference the service name in your connection string.
Conclusion
A MySQL "connection refused" error is frustrating but narrow: it means the TCP connection to the database never completed because nothing accepted it on the expected port. Work through the six steps in order — confirm mysqld is running, verify the listening port and interface, fix bind-address, open the firewall, check user host privileges, and reproduce the failure with the mysql client and telnet/nc. For Docker, add port publishing and service-name networking to that list.
The single most valuable habit is to separate transport from authentication. If the failure is instant, it is network or service; if it is an ERROR 1045 or 1130, the network is fine and the fix lives in your grants. Use the error code, not the wording of the message, to decide where to look — and you will resolve the vast majority of MySQL connection refusals in minutes rather than hours.