
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.

