VPS Tutorials
MySQL / MariaDB Won't Connect on a VPS (2026): Troubleshooting Port 3306, bind-address, Privileges, and Service Startup Failures
When MySQL or MariaDB won't connect on a VPS, there's usually more than one cause: the service isn't running, 3306 isn't listening, bind-address only binds to localhost, a firewall/security group is blocking it, user privileges are wrong, the disk is full, or InnoDB fails to start. This article troubleshoots layer by layer: local connections, port listening, logs, remote access, and database state.

When MySQL or MariaDB won't connect, many websites go down immediately.
WordPress reports:
Error establishing a database connection
The application log may show:
ERROR 2002 (HY000): Can't connect to local MySQL server through socket
ERROR 2003 (HY000): Can't connect to MySQL server on 'x.x.x.x'
Access denied for user
Connection refused
Lost connection to MySQL server
These errors all look like "the database is broken," but the root cause can be completely different: the service isn't running, the port isn't listening, the socket path is wrong, the disk is full, MySQL only listens on 127.0.0.1, a firewall blocks 3306, user privileges are wrong, or even InnoDB won't start.
This article follows a troubleshooting order: first determine whether the failure is a local connection or a remote connection, then check the service, port, logs, privileges, and disk. Don't open 3306 to the public internet right away, and don't reinstall the database immediately.
First test on the VPS itself:
mysql -u root -p
Or:
mysqladmin -u root -p ping
If you can't connect locally either, check the service and logs first.
If you can connect locally but a remote computer can't:
mysql -h 你的VPS_IP -P 3306 -u 用户名 -p
Then focus on the listening address, firewall, security group, and user privileges.
This step is critical. Many people can't connect remotely and go change the database password; but actually the local connection works fine and the problem is at the network layer. Others can't even connect locally yet keep fiddling with the security group, which is completely the wrong direction.
On Ubuntu/Debian the service name may be mysql or mariadb.
First check:
systemctl status mysql --no-pager
systemctl status mariadb --no-pager
If you're not sure of the service name:
systemctl list-units | grep -Ei 'mysql|mariadb'
Check recent logs:
journalctl -u mysql --since "30 minutes ago" --no-pager
journalctl -u mariadb --since "30 minutes ago" --no-pager
If the service isn't running, first try:
sudo systemctl restart mysql
Or:
sudo systemctl restart mariadb
After restarting, don't just check whether the command returned; check the status and logs again. Many MySQL/MariaDB startup failures have their real cause written in the logs.
Common log locations:
/var/log/mysql/error.log
/var/log/mariadb/mariadb.log
/var/log/mysql/mysql.log
You can find it like this:
sudo ls -lh /var/log/mysql/ /var/log/mariadb/ 2>/dev/null
sudo tail -n 100 /var/log/mysql/error.log
If this file doesn't exist, you can also use journal:
journalctl -u mysql -n 100 --no-pager
journalctl -u mariadb -n 100 --no-pager
Common keywords:
| Log keyword | Common direction |
|---|---|
No space left on device | Disk or inode full |
Can't create/write to file | Privilege, disk, or temp directory issues |
InnoDB: Database was not shutdown normally | Recovery after abnormal shutdown |
InnoDB: Unable to lock ./ibdata1 | Another mysqld process or file lock |
Bind on TCP/IP port: Address already in use | 3306 is in use |
Access denied for user | Username, password, or privilege issues |
Too many connections | Connection limit reached |
Logs are more reliable than guessing. Read the logs first, then decide the next step.
MySQL/MariaDB is very sensitive to disk. A full system disk, full inodes, or an unwritable /tmp can all cause startup or write failures.
First check:
df -h
df -i
Then check the database directory size:
sudo du -h --max-depth=1 /var/lib/mysql | sort -h
If the disk is full, don't casually delete /var/lib/mysql. That's the database data directory; deleting the wrong thing means data loss.
You can prioritize cleaning:
sudo apt clean
journalctl --disk-usage
sudo journalctl --vacuum-time=7d
If MySQL binlogs are large, don't just rm. First enter MySQL and check:
SHOW BINARY LOGS;
Then decide whether to clean up based on your backup and replication situation. If unsure, take a snapshot or backup first.
If the service shows running but external connections fail, check the port first:
ss -lntp | grep 3306
You may see:
127.0.0.1:3306
0.0.0.0:3306
[::]:3306
The difference is significant:
127.0.0.1:3306: only allows local connections0.0.0.0:3306: listens on all IPv4 addresses[::]:3306: listens on IPv6; exact behavior depends on configuration
If WordPress and MySQL are both on the same VPS, 127.0.0.1 is actually safer and you don't need to open it to the public internet.
If you really need remote connections, for example when the application server and database are separated, then consider having MySQL listen on an internal IP or a specific public IP.
MySQL/MariaDB's listening address is usually controlled by bind-address.
Check the configuration:
sudo grep -R "bind-address" /etc/mysql/ /etc/my.cnf* 2>/dev/null
Common configurations:
bind-address = 127.0.0.1
If you want to allow remote connections, you might change it to:
bind-address = 0.0.0.0
But be very careful here. 0.0.0.0 means listening on all network interfaces, which is not the same as safely allowing access. You must also combine it with a firewall, security group, and user privileges, allowing access only from trusted IPs.
Binding to an internal address is more recommended, for example:
bind-address = 10.0.0.5
Restart after changing:
sudo systemctl restart mysql
ss -lntp | grep 3306
When you can't connect to 3306 remotely, it's often not MySQL itself but the firewall or the cloud provider's security group.
Local firewall:
sudo ufw status verbose
sudo iptables -S
If using firewalld:
sudo firewall-cmd --list-all
Also check the cloud provider's security group:
- Whether inbound 3306 is allowed
- Whether the source IP is restricted to your application server or office IP
- Whether the wrong protocol or network interface was used
- Whether only IPv4 was allowed while traffic actually goes over IPv6
Don't open 3306 directly to 0.0.0.0/0. Exposing a database to the public internet is very risky; brute-force attacks and scans will find it quickly.
If it's only for temporary administration, prefer an SSH tunnel:
ssh -L 3307:127.0.0.1:3306 root@你的VPS_IP
Then connect locally:
mysql -h 127.0.0.1 -P 3307 -u 用户名 -p
This way the database still only listens locally, and remote access goes through an encrypted SSH tunnel.
MySQL users are distinguished by 用户名@来源主机. app@localhost and app@% are two different grants.
After entering MySQL, check users:
SELECT user, host FROM mysql.user;
Check grants:
SHOW GRANTS FOR 'app'@'localhost';
SHOW GRANTS FOR 'app'@'%';
If the remote application server IP is 10.0.0.8, the safer approach is to grant only that source:
CREATE USER 'app'@'10.0.0.8' IDENTIFIED BY '强密码';
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'10.0.0.8';
FLUSH PRIVILEGES;
Don't just do this to save effort:
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'password';
This kind of configuration is a common source of incidents. Give application users only the databases and privileges they need, and don't expose root remotely.
If you see:
Access denied for user 'app'@'x.x.x.x'
Focus on checking:
- Whether the username is correct
- Whether the password is correct
- Whether
hostmatches the source IP - Whether the application is connecting to the correct database instance
- Whether the MySQL 8 authentication plugin is compatible with the client
Check the current user:
SELECT user, host, plugin FROM mysql.user;
MySQL 8's default authentication plugin may make old clients incompatible. Don't blindly change root; you can set a compatible plugin only for the application user, or upgrade the client.
If the logs or application report:
Too many connections
First check current connections:
SHOW PROCESSLIST;
SHOW STATUS LIKE 'Threads_connected';
SHOW VARIABLES LIKE 'max_connections';
You can temporarily raise it:
SET GLOBAL max_connections = 200;
But this is only temporary. Long term, check:
- Whether the application's connection pool is configured too large
- Whether slow queries are holding connections
- Whether there's crawler or attack traffic
- Whether the PHP-FPM/application process count exceeds what the database can handle
- Whether the database machine has enough memory
On a small VPS, blindly setting max_connections to 1000 may blow up memory directly.
If MySQL/MariaDB runs in Docker, check the container first:
docker ps
docker logs 容器名 --tail 100
docker compose ps
docker compose logs --tail 100 mysql
Check port mapping:
docker ps --format 'table {{.Names}}\t{{.Ports}}'
Test inside the container:
docker exec -it 容器名 mysql -u root -p
Test on the host:
mysql -h 127.0.0.1 -P 3306 -u root -p
Common Docker pitfalls:
- Wrong data volume permissions
- Data directory mounted incorrectly
- Environment variables only take effect on first initialization; changing them later has no effect
- Listening works inside the container, but the port isn't mapped to the host
- The application container should connect to the service name
mysqlbut connects to127.0.0.1instead
In Docker scenarios, 127.0.0.1 is relative to the container itself, not necessarily the host.
If a website reports a database connection failure, I usually check like this:
systemctl status mysql --no-pager
systemctl status mariadb --no-pager
journalctl -u mysql --since "30 minutes ago" --no-pager
sudo tail -n 100 /var/log/mysql/error.log
Check disk:
df -h
df -i
sudo du -h --max-depth=1 /var/lib/mysql | sort -h
Check port:
ss -lntp | grep 3306
Local connection:
mysqladmin -u root -p ping
mysql -u root -p
Only for remote access, check:
sudo ufw status verbose
sudo iptables -S
sudo grep -R "bind-address" /etc/mysql/ /etc/my.cnf* 2>/dev/null
After entering MySQL, check:
SELECT user, host, plugin FROM mysql.user;
SHOW PROCESSLIST;
SHOW VARIABLES LIKE 'max_connections';
Following this order, you can basically categorize the problem into service, disk, listening, network, privileges, or connection count.
- Whether local
mysql -u root -pcan connect - Whether
systemctl status mysql/mariadbis normal - Whether
journalctland error.log have clear errors - Whether
df -handdf -iare normal - Whether
/var/lib/mysqlhas grown abnormally large - Whether
ss -lntp | grep 3306is listening - Whether
bind-addressmatches expectations - Whether UFW / iptables / firewalld is blocking
- Whether the cloud provider's security group only allows trusted IPs
- Whether the MySQL user's
user@hostmatches the source - Whether there is
Too many connections - In Docker scenarios, whether container logs, port mapping, and data volumes were checked
- 3306 was not exposed bare to the whole internet
The biggest danger with database connection problems is checking things in a mixed-up way. If you can't connect locally, check the service and logs first; if you can connect locally but not remotely, then check listening, firewall, security group, and privileges. As long as you separate these layers, MySQL/MariaDB connection failures won't turn into mysticism.