Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Automation By James Joyner IV · · 9 min read

Automation Error Guide: 'connect ECONNREFUSED' Webhook Target Connection Refused

Fix connect ECONNREFUSED / connection refused when a webhook or job calls a downstream target: diagnose dead service, wrong port, DNS, firewall, and TLS issues.

  • #automation
  • #troubleshooting
  • #errors
  • #networking

Overview

ECONNREFUSED means your client opened a TCP connection to a host:port and the remote machine actively rejected it — it sent back a TCP RST instead of completing the handshake. Unlike a timeout, refusal is immediate: nothing is listening on that port, or a firewall is sending a reject. When an automation step or webhook delivery calls a target that refuses, the step fails fast and the workflow errors out.

You will see this in the caller’s log:

Error: connect ECONNREFUSED 10.0.4.21:8080
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1595:16) {
  errno: -111, code: 'ECONNREFUSED', syscall: 'connect',
  address: '10.0.4.21', port: 8080
}

Other runtimes report the same condition differently:

requests.exceptions.ConnectionError: HTTPConnectionPool(host='orders-svc', port=8080): Max retries exceeded (Caused by NewConnectionError('Connection refused'))

It occurs the instant the connection is attempted — at webhook delivery, an HTTP action in a job, or a sidecar calling a local port. Because refusal is immediate, retries usually fail just as fast unless the target recovers.

Symptoms

  • Caller logs ECONNREFUSED / Connection refused with a specific address:port.
  • The failure is instant (milliseconds), not a multi-second hang.
  • Webhook deliveries show many rapid failed attempts in a row.
  • A health probe to the same host:port also fails immediately.
# Reproduce the refusal directly against the reported target
curl -v http://10.0.4.21:8080/health
*   Trying 10.0.4.21:8080...
* connect to 10.0.4.21 port 8080 failed: Connection refused
* Failed to connect to 10.0.4.21 port 8080: Connection refused
# nc gives a clean refused vs timeout signal
nc -zv 10.0.4.21 8080
nc: connect to 10.0.4.21 port 8080 (tcp) failed: Connection refused

Common Root Causes

1. The target service is down or crashed

Nothing is listening on the port because the process exited or never started.

# On the target host: is anything bound to 8080?
ss -ltnp | grep ':8080' || echo "no listener on 8080"
systemctl status orders-svc --no-pager | head -5
no listener on 8080
● orders-svc.service - Orders API
   Active: failed (Result: exit-code) since ...

A failed unit and an empty ss output mean the refusal is simply “no one is home.”

2. Wrong port (config drift)

The service is up but listening on a different port than the caller targets — common after a config change or a default-port assumption.

ss -ltnp | grep orders
LISTEN 0 511 0.0.0.0:9090 users:(("orders-svc",pid=812,fd=12))

The process listens on 9090; the webhook target still says 8080. Refused, instantly.

3. Binding to 127.0.0.1 instead of 0.0.0.0

The service only listens on loopback, so calls from another host (or another container) are refused even though local curl works.

ss -ltnp | grep ':8080'
LISTEN 0 511 127.0.0.1:8080 users:(("orders-svc",pid=812,fd=12))

127.0.0.1:8080 accepts local clients only; a remote caller gets refused. Rebind to 0.0.0.0:8080 (or the pod/service address).

4. DNS resolves to the wrong / stale host

The hostname resolves to an old IP where nothing listens — frequent after a redeploy that moved the service.

getent hosts orders-svc
nc -zv $(getent hosts orders-svc | awk '{print $1}') 8080
10.0.4.21       orders-svc
nc: connect to 10.0.4.21 port 8080 (tcp) failed: Connection refused

If the current pod IP differs from 10.0.4.21, stale DNS/endpoints are sending you to a dead address.

5. Firewall / security group sending REJECT

A firewall configured to REJECT (not DROP) returns an RST that surfaces as ECONNREFUSED. The service is healthy but unreachable from the caller’s network.

# On the target: is there a reject rule?
sudo iptables -L INPUT -n --line-numbers | grep -E "8080|reject"
3    REJECT     tcp  --  0.0.0.0/0  0.0.0.0/0  tcp dpt:8080 reject-with icmp-port-unreachable

6. Sidecar / proxy not ready (Kubernetes startup race)

In a service mesh, the app calls out before the sidecar proxy has started listening, so the local proxy port refuses connections during startup.

kubectl logs deploy/payments -c app | grep -i ECONNREFUSED | tail -3
kubectl get pod -l app=payments -o jsonpath='{.items[0].status.containerStatuses[*].ready}'
connect ECONNREFUSED 127.0.0.1:15001
true false

One container not ready (false) during startup explains transient refusals at boot.

Diagnostic Workflow

Step 1: Reproduce and classify (refused vs timeout)

nc -zv <HOST> <PORT>

Connection refused (instant) = nothing listening or a REJECT rule. A hang then timed out is a different problem (DROP/blackhole) — don’t chase a refused fix for a timeout.

Step 2: Check for a listener on the target

# On the target host
ss -ltnp | grep ":<PORT>"
systemctl status <service> --no-pager | head -5

No listener confirms the service is down or on a different port.

Step 3: Verify the bind address and port match the caller

ss -ltnp | grep "<service>"

Confirm it listens on 0.0.0.0:<PORT> (not 127.0.0.1) and on the port the caller actually targets.

Step 4: Confirm DNS points at a live address

getent hosts <HOST>
# Kubernetes
kubectl get endpoints <svc> -o wide

Compare the resolved IP / endpoints to where the service is actually running.

Step 5: Rule out firewall reject

sudo iptables -L INPUT -n | grep -E "<PORT>|REJECT"
# Cloud: check the security group inbound rules for the port

A REJECT ... <PORT> rule produces the refusal even when the service is healthy.

Example Root Cause Analysis

An n8n HTTP Request node calling the internal orders-svc starts failing every run with connect ECONNREFUSED 10.0.4.21:8080. The failures are instant, so it is refusal, not a timeout.

On the target host, the listener check tells the story:

ss -ltnp | grep orders
LISTEN 0 511 0.0.0.0:9090 users:(("orders-svc",pid=812,fd=12))

The service is healthy but now listens on 9090. A recent release moved it off the default 8080, but the webhook/job target URL was never updated. Every connection to :8080 is refused because nothing is bound there.

Fix: point the caller at the correct port (or pin the service back to 8080) and re-run:

# Update the node/target URL to http://orders-svc:9090
curl -v http://10.0.4.21:9090/health
< HTTP/1.1 200 OK

The connection completes and the workflow step succeeds.

Prevention Best Practices

  • Health-check the exact host:port your automation calls, not just the service generally — bind-address and port drift is the most common cause.
  • Use service discovery / DNS names with readiness gating so callers never target a stale or not-yet-ready endpoint.
  • Bind services to 0.0.0.0 (or the routable interface) deliberately; document any loopback-only ports so no one points a remote caller at them.
  • Prefer DROP over REJECT only when intended; an accidental REJECT rule turns “blocked” into “refused” and misleads triage.
  • Add bounded retries with backoff around webhook deliveries and HTTP job steps so a brief restart doesn’t fail the whole pipeline.
  • For ad-hoc triage, the free incident assistant can correlate ECONNREFUSED bursts with a deploy or restart. See related automation guides.

Quick Command Reference

# Classify the failure (refused = instant)
nc -zv <HOST> <PORT>
curl -v http://<HOST>:<PORT>/health

# Is anything listening, and where?
ss -ltnp | grep ":<PORT>"
ss -ltnp | grep "<service>"

# Service state on the target
systemctl status <service> --no-pager | head -5

# DNS / endpoints resolution
getent hosts <HOST>
kubectl get endpoints <svc> -o wide

# Firewall reject rules
sudo iptables -L INPUT -n | grep -E "<PORT>|REJECT"

# Kubernetes sidecar readiness
kubectl get pod -l app=<svc> -o jsonpath='{.items[0].status.containerStatuses[*].ready}'

Conclusion

ECONNREFUSED is an immediate, active rejection of your TCP connection. The usual root causes:

  1. The target service is down or crashed — nothing is listening.
  2. The service listens on a different port than the caller targets.
  3. It binds to 127.0.0.1 and refuses remote callers.
  4. DNS resolves to a stale address where the service no longer runs.
  5. A firewall/security-group REJECT rule sends an RST.
  6. A sidecar/proxy isn’t ready yet during startup.

Classify refused vs timeout first with nc -zv, then check for a listener on the exact host:port — the fix is almost always restoring the service, correcting the port, or fixing the bind address.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.