Linux: diagnosing "Connection refused" (ECONNREFUSED)
Hi, it's BlueByte. You run a command that should reach a service and it comes straight back with Connection refused — curl: (7) Failed to connect to localhost port 8080: Connection refused, or ssh: connect to host db01 port 22: Connection refused, or in code ConnectionRefusedError: [Errno 111] Connection refused. The good news is this is the fast, honest failure: the host answered, it just said no. We'll walk through what a refusal actually means, how to find which of four causes you have, fix each one, and confirm the connection completes.
What "Connection refused" means — a reject, not a timeout
A refusal is the kernel on the far side actively saying "nothing is here." Your machine sent a TCP SYN to the port; instead of a handshake, the remote kernel replied with a RST because no process was listening — or a firewall rule sent the reject. The variants all mean the same thing:
curl: (7) Failed to connect to localhost port 8080: Connection refused
nc: connect to 10.0.0.5 port 5432 (tcp) failed: Connection refused
ConnectionRefusedError: [Errno 111] Connection refusedcurl calls this exit code 7 — its own description is Failed to connect() to host or proxy. The detail that matters is that a refusal is instant. If the reply came back in milliseconds, the host is up and reachable; the problem is the port, not the network.
The four things that make a port refuse you
- Nothing is listening — the service is stopped, crashed, or never started, so the port is closed.
- It's listening on the wrong interface — the service bound to
127.0.0.1, so it answers local clients and refuses everyone remote. - You have the wrong port — the client points at
:8080but the service listens on:8000. - A firewall is rejecting — a rule sends a REJECT (an active RST or ICMP), which looks exactly like nothing listening.
First, ask what is actually listening
Before touching the client, look at the server. ss lists sockets — -t for TCP, -l for listening, -n to skip name resolution, -p for the owning process:
ss -tlnp | grep 8080LISTEN 0 511 127.0.0.1:8080 0.0.0.0:* users:(("gunicorn",pid=812,fd=7))If that returns nothing, nothing is listening — cause one. If it returns a line, read the local address carefully; that's the next check.
Read the bind address: 127.0.0.1 versus 0.0.0.0
The address before the port is everything. 127.0.0.1:8080 means the service only accepts connections from the same machine — a remote client, or even a container on a different interface, gets Connection refused. 0.0.0.0:8080 (or *:8080) means it listens on every interface. So in the output above, a local curl works but a colleague hitting the box remotely is refused — not a firewall, just a bind address. One subtlety trips people up: on many systems localhost resolves to ::1 (IPv6) first, so if the service bound only to IPv4 127.0.0.1, a client that reaches for ::1 is refused while 127.0.0.1 works. Force the family with curl -4 or curl -6 to see which side is missing.
Prove the path with curl and nc
Confirm from the client side what the server is doing:
curl -v http://db01:8080/ ; echo "exit: $?"
nc -vz db01 8080* Trying 10.0.0.5:8080...
* connect to 10.0.0.5 port 8080 failed: Connection refused
exit: 7
nc: connect to db01 (10.0.0.5) port 8080 (tcp) failed: Connection refusedcurl -v shows it resolved the name and reached the IP, then got refused — so DNS is fine and the host is up. exit: 7 confirms it's a refusal, not a timeout. nc -vz gives the same one-line verdict without sending a request.
Fix it by cause: start it, rebind it, or open it
- Nothing listening — start the service and check it came up:
sudo systemctl start myapp
ss -tlnp | grep 8080-
Wrong interface — change the service's listen address from
127.0.0.1to0.0.0.0(or the specific interface), restart it, and re-check withss. The socket should now read0.0.0.0:8080. -
Wrong port — point the client at the port
ssactually shows. -
Firewall reject — inspect the ruleset and allow the port:
sudo nft list ruleset | grep -i reject
sudo iptables -L -n | grep 8080A REJECT rule refuses instantly, just like a closed port; a DROP rule instead makes the client hang until it times out. So if ss shows a listener and the client is still refused fast, look for a REJECT rule specifically — a DROP would present as a timeout, not a refusal.
A real case: an app that only listens on localhost
A teammate can't reach a web app on 10.0.0.5:8080 and gets Connection refused immediately. Ping works, so the host is up. On the server, ss -tlnp | grep 8080 shows 127.0.0.1:8080 — the app is bound to localhost. You edit its config to listen on 0.0.0.0:8080, run sudo systemctl restart myapp, and ss now shows 0.0.0.0:8080. From the teammate's machine, curl -sS http://10.0.0.5:8080/ returns the page. Nothing was wrong with the network or the firewall — the service simply wasn't offering the port to anyone but itself.
Confirm the connection actually completes
After the fix, a request should get a real response, not a refusal:
curl -sS -o /dev/null -w "%{http_code}\n" http://db01:8080/health200A status code back — even a 404 — means the TCP connection succeeded and something answered. Pair it with ss -tlnp showing the service on the interface you expect.
Keep it from coming back, and how it differs from a timeout
Bind services to the interface you actually mean, add a startup health check so a crashed service is noticed before a user hits the refusal, and document each service's port so clients don't drift to the wrong one. This is a different failure from Connection timed out (curl exit 28): a timeout is silence — the packet was dropped by a firewall DROP rule, or the host is down, so the client waits and eventually gives up. A refusal is an instant no. And both differ from Could not resolve host (curl exit 6), which fails at DNS before any TCP is attempted. Next time you see Connection refused, start at ss on the server: is anything listening, and on which address.
Related questions
How is "Connection refused" different from "Connection timed out"?
A refusal is instant — the host sent a RST because nothing was listening, or a firewall rejected. A timeout is silence — the packet was dropped and the client waits, then gives up. curl reports exit 7 for refused and exit 28 for timed out.
ss shows the port on 127.0.0.1. Why can't a remote client connect?
Because 127.0.0.1 only accepts connections from the same machine. Rebind the service to 0.0.0.0 (or the specific interface) and restart; ss should then show 0.0.0.0:port.
ss returns nothing for my port.
Nothing is listening. The service is stopped, crashed, or bound to a different port. Start it with systemctl start and re-run ss to confirm it came up on the port you expect.
A firewall reject and "nothing listening" look identical from the client. How do I tell them apart?
Check the server. If ss shows a listener but the client is still refused, a REJECT rule is in the path — inspect nft or iptables. If ss shows no listener, it's the service, not the firewall.
I get "Connection refused" on localhost but the service is running.
Then it's on a different port or a different address family. Check ss -tlnp for the exact port, and note IPv6 versus IPv4 — a service on ::1 won't answer a client forced to 127.0.0.1.
References
Haneul Seo
Infrastructure engineer · 10+ years running Linux fleets
More in this category
systemd: Start request repeated too quickly
systemd refuses to start a unit that was started more than StartLimitBurst times (default 5) within StartLimitIntervalSec (default 10s), and Restart= counts against that limit. With the 100 ms default RestartSec a crashing service burns all five attempts in under a second. Find the real crash in the journal, fix it, run reset-failed, and give restarts room with RestartSec.
SSH: Received disconnect ... Too many authentication failures
Your agent is offering more keys than the server will let you try. Every public key sshd looks at burns one of the MaxAuthTries attempts — six by default, often three on a hardened host — so the right key never gets its turn and the server hangs up before you type anything. IdentitiesOnly=yes with an explicit IdentityFile pins the connection to one key and the attempt count drops to one.
Active Directory: replication fails with error 1722, The RPC server is unavailable
RPC reports 1722 (0x6ba, RPC_S_SERVER_UNAVAILABLE) when a lower layer fails to connect, so the real fault is almost never RPC itself — it is DNS, a blocked port, or a host-side setting on one of the two domain controllers. repadmin tells you which partner is failing, dcdiag /test:dns rules out name resolution, and Test-NetConnection plus the dynamic port range settle the firewall question. The most common miss is a rule that allows TCP 135 but not 49152–65535.
Windows Server RDS: The remote session was disconnected because there are no Remote Desktop License Servers available to provide a license
The 120-day RD Licensing grace period ended and the session host has no usable license server, so it refuses sessions. GetGracePeriodDays returning DaysLeft 0 and an empty SpecifiedLSList confirm it in seconds. The fix is a real, activated license server with CALs that are new enough for the host — a 2019 CAL cannot serve a 2022 session host — configured through the deployment or the Licensing policies, plus RPC ports open between the two.
Windows 11: "Your organization's security policies block unauthenticated guest access" when opening a NAS share (0x80070035)
The SMB client on Windows 10 Enterprise/Education/Pro for Workstations, Windows 11 Pro and Windows Server 2019+ refuses guest logons by default, and Windows 11 24H2 Enterprise/Pro/Education also requires SMB signing, which guest sessions can't do. A NAS share that only offers guest access therefore fails with the 'block unauthenticated guest access' dialog, Error code 0x80070035, or System error 3227320323, and Event ID 31017 'Rejected an insecure guest logon' lands in the SmbClient/Security log. The fix Microsoft recommends is a real account on the NAS and signing support in its firmware; Set-SmbClientConfiguration -EnableInsecureGuestLogons $true (plus -RequireSecuritySignature $false on 24H2) is the escape hatch, and it costs you signing and encryption on that client.
Ubuntu/Debian: E: Could not get lock /var/lib/dpkg/lock-frontend — who holds it and how to wait for it
Another package manager, usually Ubuntu's unattended-upgrades fired by a persistent systemd timer at boot, holds the dpkg frontend lock while your apt-get runs. apt-get gives up at once while apt waits because Ubuntu ships binary::apt::DPkg::Lock::Timeout "120" for the apt binary only. Read the PID from the message, let the run finish or pass -o DPkg::Lock::Timeout=<seconds> to apt-get, run dpkg --configure -a only after a genuinely interrupted run, and never delete the lock file: it is an fcntl lock the kernel releases when the holder exits.