Skip to content
Latchkey

Wait For a Service Port With nc in a Loop

Looping nc -z until it succeeds makes a CI step wait for a service container to accept connections before tests run.

Service containers report "started" before they actually accept connections. A short nc -z loop closes that gap and stops flaky "connection refused" failures at the start of a test run.

What it does

The pattern calls nc -z in a bash loop, sleeping between attempts, until the port accepts a connection or a deadline passes. It turns a fixed sleep (which is either too short and flaky or too long and slow) into a condition that polls the real readiness signal.

Common usage

Terminal
# wait up to 30s for Postgres, then proceed or fail
for i in $(seq 1 30); do
  nc -z localhost 5432 && break
  echo "waiting for postgres ($i)"; sleep 1
done
nc -z localhost 5432 || { echo "postgres never came up"; exit 1; }

Why a fixed sleep is worse

ApproachProblem
sleep 5; run testsFlaky if the service is slow, wasteful if it is fast
nc -z loopProceeds the instant the port is ready, fails fast if it never opens
Docker healthcheckBest when available, but not every image ships one

In CI

A bounded loop (seq 1 N with sleep 1) gives a hard ceiling so a genuinely dead service fails the job instead of hanging until the global timeout. A port being open still does not mean the app finished migrations, so some stacks add an application-level health probe after the nc check.

Common errors in CI

If the loop always falls through to the failure branch, the host is often wrong: inside a GitHub Actions service container the host is localhost, but in a docker-compose network it is the service name, not localhost. "nc: bad address" means DNS could not resolve that service name yet, which is itself a readiness or network issue.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →