TROUBLESHOOTING
July 27, 2026

How to Fix "Connection Refused" Errors on a Server

6 min read
Author
CloudStick Team
Backend Developer
Share this article
How to Fix "Connection Refused" Errors on a Server
CloudStick
Connection Refused

"Connection Refused" Means Something Very Specific

A connection refused error means the target machine received your packet and actively rejected it — the OS sent back a TCP RST because nothing is bound to that port and listening. A timeout is a completely different failure: your packets go out and nothing ever answers, no rejection and no acknowledgment, so your client just sits there until it gives up waiting. These point to opposite classes of problems. Refused means "I'm here, but nobody's home on this port." Timeout usually means the packet never even reached a process to reject it — a firewall silently dropping it, a routing issue, or a host that's unreachable entirely.

The fastest way to tell the two apart is curl -v against the address and port in question. The wording in the output is unambiguous either way, so don't skip this step and jump straight to guessing.

$ curl -v http://203.0.113.10:3306
* Trying 203.0.113.10:3306...
* connect to 203.0.113.10 port 3306 failed: Connection refused
* Failed to connect to 203.0.113.10 port 3306: Connection refused
# compare with a timeout instead:
$ curl -v http://203.0.113.10:3306
* Trying 203.0.113.10:3306...
(hangs for roughly two minutes, then)
* Failed to connect to 203.0.113.10 port 3306: Connection timed out
PREREQUISITE

Before working through this, you'll need SSH access to the server (root or sudo), a client machine that isn't the server itself to test from externally, and to know exactly which service and port is failing — for example MySQL on 3306, or your app on a custom port.

There Are Only Four Realistic Causes

In practice, "connection refused" almost always traces back to one of four things, and you can rule each one in or out in under a minute once you know where to look.

  • The service isn't running. MySQL, Nginx, or your application process crashed, was never started after a reboot, or died from an out-of-memory kill — there's simply nothing bound to the port anymore.
  • The service is bound only to 127.0.0.1. It's running and healthy, but it only accepts connections from the loopback interface, so anything reaching the box's public or private IP gets rejected outright.
  • A firewall is blocking the port. CSF, UFW, or raw iptables rules are dropping the traffic before your application ever sees it — this most often shows up as a timeout, but some firewall configurations send an explicit reject that looks identical to a service being down.
  • The app is pointed at the wrong port. A connection string or proxy config references 3306 when MySQL was reconfigured to listen on 3307, or a reverse proxy forwards to a port your backend never opened.

Is The Service Running

Start with systemctl status <service> on the server itself. This tells you immediately whether the process is active, has failed, or was never started in the first place — and it's faster than any network-level test.

$ systemctl status mysql
● mysql.service - MySQL Community Server
Loaded: loaded (/lib/systemd/system/mysql.service; enabled)
Active: failed (Result: exit-code) since Mon 2026-07-27 09:14:02 UTC; 3min ago
Process: 18420 ExecStart=/usr/sbin/mysqld (code=exited, status=1/FAILURE)

If it shows failed, the port genuinely has nothing listening on it, which explains the refused connection completely. Pull the real reason with journalctl -u mysql -n 50 --no-pager — common culprits are a full disk, a corrupted InnoDB table, an out-of-memory kill, or a bad directive left over from the last config edit. For Nginx and Apache on a CloudStick server, the equivalent processes are nginx-cs and apache2-cs, and their own systemctl status output will tell you the same story.

Check The Interface

If the service is active, the next question is which interface it's actually listening on. ss -tlnp shows you every listening socket, the process holding it, and — critically — the address it's bound to.

$ ss -tlnp | grep 3306
LISTEN 0 151 127.0.0.1:3306 0.0.0.0:* users:(("mysqld",pid=1822,fd=24))
# vs a socket reachable externally:
LISTEN 0 151 0.0.0.0:3306 0.0.0.0:* users:(("mysqld",pid=1822,fd=24))

127.0.0.1:3306 means MySQL will happily accept connections from something else running on that same machine, but any packet arriving over the network stack from an external IP gets refused before MySQL even sees it — because the kernel never handed it the connection. 0.0.0.0:3306 means it's listening on every interface, including whatever public or private IP the server has. This single line is usually the whole answer when the service is confirmed running but remote clients still get refused.

Rule Out The Firewall

From a separate machine — not the server itself — test the port directly with nc -zv or telnet. This tells you what the outside world actually experiences, which can differ from what ss shows you locally if a firewall sits in between.

$ nc -zv 203.0.113.10 3306
nc: connect to 203.0.113.10 port 3306 (tcp) failed: Connection refused
$ telnet 203.0.113.10 3306
Trying 203.0.113.10...
telnet: Unable to connect to remote host: Connection refused

A firewall that's actively rejecting (rather than silently dropping) traffic produces the exact same "Connection refused" wording as a service that isn't listening, so don't assume it's the application until you've checked. On a server running CSF, grep the port out of /etc/csf/csf.conf under TCP_IN, or run csf -a <your-ip> to temporarily allow your current IP and re-test. With UFW it's ufw status numbered to see if the port is allowed at all.

Fix It

Apply the fix that matches whichever check above actually turned up the problem, rather than changing everything at once. If the service was down, sudo systemctl restart mysql (or nginx-cs, apache2-cs, php83cs-fpm) and confirm with systemctl status that it comes back as active (running) and stays that way after a few minutes.

If it's bound only to 127.0.0.1 and you genuinely need remote access, edit bind-address in /etc/mysql/mysql.conf.d/mysqld.cnf from 127.0.0.1 to the specific interface you want (your server's private IP for internal access, or 0.0.0.0 only if you truly need it reachable from anywhere), then sudo systemctl restart mysql.

WARNING

Binding MySQL to 0.0.0.0 without tightening the firewall around it exposes your database to the entire internet. Bind to a private interface where possible, and always pair a wider bind-address with a firewall rule that only allows the specific IPs that legitimately need access.

If CSF is the blocker, add the port to TCP_IN in /etc/csf/csf.conf and run csf -r to reload, or use csf -a <ip> to allow just the specific address that needs it rather than opening the port to everyone. On a CloudStick server, the dashboard's Firewall section handles this without hand-editing csf.conf directly — you add a port or IP rule through the UI and CloudStick applies it to CSF correctly, which avoids the syntax mistakes that come from editing the config file by hand under pressure. Finally, if the port itself was simply wrong in your app's config, correct the connection string or proxy target and restart the app — that one's rarely more complicated than it sounds once everything else has been ruled out.

Leave a comment
Full Name
Email Address
Message
Contents