HAProxy 'Connection Refused': Complete Troubleshooting Guide (2024)
Fix HAProxy connection refused errors fast. Covers dead backends, misconfigured bind addresses, firewall blocks, and ACL issues with exact commands and config f
- Root cause 1: Backend server is down, crashed, or not listening on the port HAProxy expects — HAProxy logs show 'Connection refused' against the upstream IP:port
- Root cause 2: HAProxy bind address or frontend/backend port mismatch — the proxy itself never starts listening, so clients get a kernel-level TCP RST immediately
- Root cause 3: iptables/nftables or cloud security-group rules silently drop packets between HAProxy and backends, causing timeouts that then manifest as refused connections after retries exhaust
- Quick fix: Run `haproxy -c -f /etc/haproxy/haproxy.cfg` to validate config, then `systemctl status haproxy` and inspect `/var/log/haproxy.log` for the exact server and error code before touching anything else
| Method | When to Use | Time to Apply | Risk |
|---|---|---|---|
| Restart the failed backend service | Backend process crashed (ECONNREFUSED in HAProxy log against known-good server) | < 1 min | Low — service restart only |
| Fix haproxy.cfg bind/server directive | Wrong IP, port, or typo in config introduced by recent change | 2–5 min | Low with `haproxy -c` pre-check |
| Flush or update firewall rules | Backends reachable via SSH but not via HAProxy (asymmetric routing or iptables OUTPUT/FORWARD drop) | 5–10 min | Medium — test rule before persisting |
| Enable HAProxy health checks + retry | Intermittent refused connections due to rolling restarts or flapping upstreams | 10–15 min | Low — purely additive config change |
| Switch backend to UNIX socket | HAProxy and app on same host, TCP overhead or port conflict causing refused connections | 15–30 min | Medium — requires app reconfiguration |
| Rebuild HAProxy with correct OpenSSL | SSL/TLS handshake results in refused connection on HTTPS backends | 30–60 min | High — package manager or compile required |
Understanding 'Connection Refused' in HAProxy
When HAProxy attempts to forward a client request to a backend server and receives a TCP RST (reset) packet — or no response at all on a non-routable path — it logs the error and returns a 503 Service Unavailable to the client. The raw kernel error is ECONNREFUSED (errno 111 on Linux). You will see it in HAProxy logs as:
Server myapp/web1 is DOWN, reason: Layer4 connection problem, info: "Connection refused", check duration: 1ms
or during an active request:
backend myapp has no server available!
Before touching any configuration, you must distinguish between three distinct failure modes:
- HAProxy itself is not listening — the client cannot even reach the proxy frontend
- HAProxy is up but cannot reach the backend — the proxy is healthy but all upstream servers report DOWN
- Intermittent connection refused — some requests succeed; others fail due to flapping, overload, or connection pool exhaustion
Each mode has a different diagnosis path.
Step 1: Confirm HAProxy Is Running and Listening
The very first command to run is:
systemctl status haproxy
If the unit shows failed or inactive, read the journal:
journalctl -u haproxy -n 50 --no-pager
Common reasons HAProxy fails to start and logs Address already in use or cannot bind socket:
- Another process holds the bind port (e.g., Nginx also trying to bind
:443) - The configured IP address does not exist on any local interface yet (race condition at boot)
- SELinux or AppArmor blocking the bind syscall
Check what is already on the port:
ss -tlnp | grep ':80\|:443\|:8080'
If HAProxy is running, verify it is actually listening on the expected frontend port:
ss -tlnp | grep haproxy
# Expected output example:
# LISTEN 0 128 0.0.0.0:80 0.0.0.0:* users:(("haproxy",pid=12345,fd=5))
If the port is absent from the output, the bind directive in haproxy.cfg is wrong. Open the config and inspect the frontend section:
frontend http_in
bind *:80 # OK — wildcard
bind 10.0.1.5:443 # WRONG if 10.0.1.5 doesn't exist locally
Always validate the config before reloading:
haproxy -c -f /etc/haproxy/haproxy.cfg
# Should print: Configuration file is valid
Step 2: Inspect the HAProxy Stats and Logs
If HAProxy is running, enable or read the stats socket to see real-time backend state:
# Via netcat / socat (add to haproxy.cfg if not present: stats socket /run/haproxy/admin.sock)
echo 'show servers state' | socat stdio /run/haproxy/admin.sock
The output columns include server name, current state (UP/DOWN/MAINT), and the last health-check result. A DOWN state with L4CON (Layer 4 connection) is exactly ECONNREFUSED.
For log-based diagnosis, ensure HAProxy logging is enabled in haproxy.cfg:
global
log /dev/log local0
log /dev/log local1 notice
defaults
log global
option httplog
option dontlognull
Then tail the log in real time while reproducing the error:
tail -f /var/log/haproxy.log | grep -E 'DOWN|refused|503|backend'
Step 3: Test Backend Connectivity Directly from the HAProxy Host
SSH onto the HAProxy node and attempt to connect to each backend server manually:
# TCP connectivity test (no HTTP)
curl -sv --connect-timeout 5 http://10.0.1.10:8080/health
# Raw TCP using bash /dev/tcp
timeout 3 bash -c '</dev/tcp/10.0.1.10/8080 && echo open || echo refused'
# netcat
nc -zv 10.0.1.10 8080
If these commands also show Connection refused, the problem is definitively on the backend side — not in HAProxy's configuration. Proceed to Step 4.
If curl succeeds but HAProxy still reports DOWN, the issue is in the health-check configuration (wrong path, wrong port, SSL mismatch). Compare the option httpchk path against what the backend actually exposes:
backend myapp
option httpchk GET /healthz
server web1 10.0.1.10:8080 check
If the app only exposes /health (not /healthz), HAProxy receives a 404 which it may interpret as a failure.
Step 4: Fix a Down Backend
If the backend service is down:
# Check the service on the backend node
ssh 10.0.1.10 'systemctl status myapp'
# Restart it
ssh 10.0.1.10 'systemctl restart myapp'
# Confirm it is listening
ssh 10.0.1.10 'ss -tlnp | grep 8080'
Once the backend is listening, HAProxy health checks will promote it back to UP within one check interval (default inter 2s). To force an immediate check:
echo 'set server myapp/web1 state ready' | socat stdio /run/haproxy/admin.sock
Step 5: Fix Firewall Rules
If the backend is listening but unreachable from the HAProxy host, inspect iptables:
# On the HAProxy host — check OUTPUT chain
iptables -L OUTPUT -n -v | grep -E 'DROP|REJECT'
# On the backend host — check INPUT chain
iptables -L INPUT -n -v | grep -E 'DROP|REJECT'
# Trace a specific packet (requires iptables-legacy or nft)
iptables -I INPUT 1 -p tcp --dport 8080 -j LOG --log-prefix "DEBUG-8080: "
# Then check: dmesg | grep DEBUG-8080
To allow traffic from HAProxy to backend on Ubuntu/Debian with ufw:
# On the backend node — allow HAProxy's IP
ufw allow from 10.0.1.5 to any port 8080
For cloud environments (AWS Security Groups, GCP Firewall Rules, Azure NSG), ensure the HAProxy node's private IP or security group is whitelisted as an ingress source on the backend's port.
Step 6: Addressing HAProxy Slow or Timeout Symptoms
If connections are not refused outright but HAProxy is slow, check:
Too many open files / file descriptor exhaustion:
cat /proc/$(pidof haproxy)/limits | grep 'open files'
# If max is 1024, increase it:
ulimit -n 65536 # in systemd unit: LimitNOFILE=65536
Connection queue buildup:
echo 'show info' | socat stdio /run/haproxy/admin.sock | grep -E 'MaxConn|CurrConns|Uptime'
If CurrConns approaches MaxConn, increase maxconn in global and in the frontend/backend blocks.
DNS resolution delays for backend hostnames:
HAProxy resolves backend hostnames at startup by default. Use resolvers with resolve-prefer ipv4 and a short hold valid TTL, or switch to IP addresses in the server directives.
Preventing Recurrence
- Enable active health checks with
check inter 2s rise 2 fall 3on every server directive - Set
option redispatchso HAProxy retries a refused connection on a different backend server rather than failing immediately - Configure
retries 3in the defaults section - Use
errorfile 503 /etc/haproxy/errors/503.httpto present a user-friendly error page - Export metrics via the Prometheus exporter (
haproxy_exporter) and alert onhaproxy_server_status{state="DOWN"}before users notice
Frequently Asked Questions
#!/usr/bin/env bash
# HAProxy Connection Refused — Diagnostic Script
# Run as root on the HAProxy host
set -euo pipefail
HAPROXY_CFG="/etc/haproxy/haproxy.cfg"
HAPROXY_SOCK="/run/haproxy/admin.sock"
echo "=== 1. HAProxy Process Status ==="
systemctl status haproxy --no-pager -l || true
echo ""
echo "=== 2. Config Syntax Check ==="
haproxy -c -f "$HAPROXY_CFG" && echo "Config OK" || echo "CONFIG ERROR — fix before reloading"
echo ""
echo "=== 3. Listening Ports ==="
ss -tlnp | grep -E 'haproxy|LISTEN' || echo "No listening sockets found for haproxy"
echo ""
echo "=== 4. Backend Server States ==="
if [[ -S "$HAPROXY_SOCK" ]]; then
echo 'show servers state' | socat stdio "$HAPROXY_SOCK"
else
echo "Stats socket not found at $HAPROXY_SOCK — add to global: stats socket $HAPROXY_SOCK mode 660 level admin"
fi
echo ""
echo "=== 5. Recent HAProxy Log Errors ==="
journalctl -u haproxy --since '10 minutes ago' --no-pager | grep -E 'DOWN|refused|ERROR|ALERT' | tail -20 || \
grep -E 'DOWN|refused|ERROR' /var/log/haproxy.log 2>/dev/null | tail -20 || \
echo "No log entries found — check rsyslog/syslog configuration"
echo ""
echo "=== 6. File Descriptor Limits ==="
HAPROXY_PID=$(pidof haproxy 2>/dev/null || echo "")
if [[ -n "$HAPROXY_PID" ]]; then
echo "HAProxy PID: $HAPROXY_PID"
grep 'open files' "/proc/$HAPROXY_PID/limits"
else
echo "HAProxy not running"
fi
echo ""
echo "=== 7. Connectivity Test to Backends (parsed from config) ==="
# Extract server IPs and ports from haproxy.cfg
grep -E '^\s+server ' "$HAPROXY_CFG" | awk '{print $3}' | sort -u | while read -r addr; do
host=$(echo "$addr" | cut -d: -f1)
port=$(echo "$addr" | cut -d: -f2)
if timeout 3 bash -c "</dev/tcp/$host/$port" 2>/dev/null; then
echo " OPEN $host:$port"
else
echo " REFUSED/TIMEOUT $host:$port <-- investigate this backend"
fi
done
echo ""
echo "=== 8. Firewall Rules (INPUT/OUTPUT relevant ports) ==="
iptables -L INPUT OUTPUT -n -v 2>/dev/null | grep -E 'DROP|REJECT' || echo "No DROP/REJECT rules found (or iptables not in use)"
echo ""
echo "=== Diagnosis complete. Review REFUSED/TIMEOUT backends above. ==="Error Medic Editorial
The Error Medic Editorial team comprises senior DevOps engineers, SREs, and Linux systems administrators with combined experience spanning cloud-native infrastructure, high-availability proxy configurations, and production incident response. Our troubleshooting guides are written from real on-call runbooks and battle-tested against production environments running HAProxy, Nginx, Envoy, and Kubernetes ingress controllers.
Sources
- https://cbonte.github.io/haproxy-dconv/2.8/configuration.html
- https://www.haproxy.org/download/2.8/doc/management.txt
- https://stackoverflow.com/questions/tagged/haproxy
- https://github.com/haproxy/haproxy/blob/master/doc/configuration.txt
- https://www.haproxy.com/blog/haproxy-exposes-a-unix-socket-for-dynamic-configuration