Fix RabbitMQ ECONNREFUSED 5672 between Docker containers
Your consumer starts, RabbitMQ starts, and the first connect is refused. Fix the amqp URL, pick a healthcheck that waits for port 5672, and avoid the guest trap.
TL;DR#
connect ECONNREFUSED 127.0.0.1:5672 from a Node consumer running in Docker Compose has two RabbitMQ-flavoured causes. First, the amqp URL points at localhost, which inside a container is the consumer itself: use the broker's service name, as in amqp://guest:guest@rabbitmq:5672. Second, once the host is right, RabbitMQ takes several seconds to open port 5672 after its container starts, so a consumer that connects once at boot is refused again, this time at the broker's private IP. Gate the consumer on a healthcheck that tests the AMQP port (rabbitmq-diagnostics -q check_port_connectivity), not ping, which reports healthy too early. If you mount your own /etc/rabbitmq/, expect ACCESS_REFUSED next.
The error#
The case comes from a Stack Overflow question: a RabbitMQ container and a Seneca consumer started by the same docker-compose.yml, with the consumer reading its broker address from AMQP_RECEIVE_URL. Seneca wraps the socket error in its own message:
seneca: Action hook:listen,role:transport,type:amqp failed: connect ECONNREFUSED 127.0.0.1:5672.The question uses Seneca; the same failure appears with amqplib, the plain AMQP 0-9-1 client for Node. Reproduced on 10 October 2026 with amqplib 2.2.0, Node 22.23.3 (node:22-alpine) and RabbitMQ 3.13.7 (rabbitmq:3-management), Docker Engine 29.7.2 and Compose 5.5.0, an unhandled amqp.connect('amqp://guest:guest@localhost:5672') prints (abridged: the stack frames and errno fields of the two inner errors are cut):
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1135:18)
at afterConnectMultiple (node:net:1716:7) {
code: 'ECONNREFUSED',
[errors]: [
Error: connect ECONNREFUSED ::1:5672
Error: connect ECONNREFUSED 127.0.0.1:5672Note the empty message on the first line. On this Node version localhost resolved to both ::1 and 127.0.0.1, both attempts were refused, and the error that reaches your code is an AggregateError whose message is an empty string. A console.error(err.message) in a catch block prints a blank line; log err.code and err.errors instead, or you will be debugging a silent crash.
Why it happens#
The wrong host. The accepted answer on the question names the first cause: "the listener is trying to connect to localhost(127.0.0.1) which is not the rabbitmq service container IP". Each container has its own loopback interface, and Compose registers every service name in its internal DNS. That part is not specific to RabbitMQ: the ECONNREFUSED 127.0.0.1:5432 fix for Postgres walks through it, including why the host side of a ports mapping is irrelevant between two services. The question's ports: - 5672:5672 line only matters for clients on your machine.
A broker that is running but not listening. The second answer suspects the other cause: "your RabbitMQ service is not fully up, at the time the connection is attempted". This is where RabbitMQ differs from a lighter service. The Compose startup-order guide says "Compose does not wait until a container is 'ready', only until it's running", and a RabbitMQ container spends a long time between the two: the Erlang runtime boots, the broker waits for its metadata store, and only then does the AMQP listener open. In the reproduction, the start banner and the listener were about 12 seconds apart:
rabbitmq-1 | 2026-10-10 22:24:37.639582+00:00 [info] <0.254.0> Starting RabbitMQ 3.13.7 on Erlang 26.2.5.16 [jit]
rabbitmq-1 | 2026-10-10 22:24:49.292169+00:00 [info] <0.846.0> started TCP listener on [::]:5672
rabbitmq-1 | 2026-10-10 22:24:49.404605+00:00 [info] <0.684.0> Server startup complete; 5 plugins started.With the host fixed to rabbitmq and a short-form depends_on: - rabbitmq, the consumer connected inside that window and was refused at the broker's private address:
consumer-1 | [consumer] connecting to amqp://guest:guest@rabbitmq:5672
consumer-1 | [consumer] code=ECONNREFUSED message=connect ECONNREFUSED 172.20.0.2:5672The address in the error tells you which cause you have. 127.0.0.1 or ::1 means the URL still says localhost. A private address such as 172.20.0.2 means DNS worked and the broker had not opened the port yet.
The image ships no healthcheck. The official image's Docker Hub page says it "does not come with a default HEALTHCHECK defined", and points users to write their own. Without one, condition: service_healthy has nothing to wait for.
Fix#
-
Point the URL at the service name and pass it to the consumer. The accepted answer's
un.envholds exactly this (under the name the question's Seneca code reads,AMQP_RECEIVE_URL; the examples below call itAMQP_URL), and the consumer must read it rather than fall back to a default — amqplib connects toamqp://localhostwhen it is given no URL:bashAMQP_URL=amqp://guest:guest@rabbitmq:5672 -
Add a healthcheck that tests the AMQP port, and make the consumer wait for it. RabbitMQ's monitoring guide orders its health checks in stages. Stage 1,
rabbitmq-diagnostics -q ping, only checks that the Erlang runtime is running (and, indirectly, that CLI tools can authenticate with it); it says nothing about client listeners. Stage 4 addscheck_port_connectivity, which "will try to open a TCP connection to the discovered listener ports". The second one is what a client needs:yamlservices: rabbitmq: image: rabbitmq:3-management hostname: rabbitmq healthcheck: test: ["CMD", "rabbitmq-diagnostics", "-q", "check_port_connectivity"] interval: 5s timeout: 10s retries: 10 start_period: 30s consumer: build: . restart: on-failure environment: AMQP_URL: amqp://guest:guest@rabbitmq:5672 depends_on: rabbitmq: condition: service_healthyThe image tag is the one the reproduction ran;
check_port_connectivityis documented in the current RabbitMQ monitoring guide, so the same healthcheck applies to the 4.x images. Thehostnameline is not decoration. The Docker Hub page explains that RabbitMQ stores its data under a node name that "defaults to the hostname", and asks for an explicit hostname "so that we don't get a random hostname and can keep track of our data". That matters as soon as/var/lib/rabbitmqsits on a volume, as it does in the question.The monitoring guide also notes that most CLI commands,
rabbitmq-diagnosticsincluded, join the Erlang distribution, which has a cost, and that the RabbitMQ Kubernetes Operator uses a plain TCP check on the AMQP port as its readiness probe. That is the same idea as the accepted answer'swait-for-it.sh rabbitmq:5672wrapper: wait for the port, not for the process. Either works; the healthcheck keeps the waiting in the Compose file instead of the consumer's image. -
Retry the first connection in the consumer anyway. A healthcheck only helps where Compose honours
depends_on, and it does nothing if the broker restarts while the consumer runs. A bounded retry loop covers start-up on any platform, and exiting oncloselets therestart: on-failurepolicy from step 2 bring the consumer back after a broker restart:js// consumer.js (amqplib) const amqp = require('amqplib'); const url = process.env.AMQP_URL; if (!url) throw new Error('AMQP_URL is not set'); async function connectWithRetry(attempts = 15, delayMs = 2000) { for (let i = 1; i <= attempts; i += 1) { try { return await amqp.connect(url); } catch (err) { const reason = err.code || err.message; console.warn(`RabbitMQ not ready (attempt ${i}/${attempts}): ${reason}`); if (i === attempts) throw err; await new Promise((resolve) => setTimeout(resolve, delayMs)); } } } (async () => { const conn = await connectWithRetry(); conn.on('error', (err) => console.error('AMQP connection error:', err.message)); conn.on('close', () => { console.error('AMQP connection closed, exiting so Docker restarts the consumer'); process.exit(1); }); const ch = await conn.createChannel(); await ch.assertQueue('new_user_notification'); console.log('Connected, waiting for messages on new_user_notification'); await ch.consume('new_user_notification', (msg) => { if (msg === null) return; console.log('Received:', msg.content.toString()); ch.ack(msg); }); })().catch((err) => { console.error('Giving up on RabbitMQ:', err); process.exit(1); });Note
err.code || err.message: it keeps the log readable when the error is theAggregateErrorwith an empty message shown above. -
Recreate the stack, so the consumer gets the new environment and the broker gets its healthcheck:
bashdocker compose down docker compose up --build
Verify the fix#
With the healthcheck from step 2, Compose holds the consumer until the broker is healthy, and the first connection succeeds. From the reproduction:
rabbitmq-1 | 2026-10-10 22:24:49.292169+00:00 [info] <0.846.0> started TCP listener on [::]:5672
Container aotd-docker-and-rabbitmq-econnrefused-between-containers-rabbitmq-1 Healthy
consumer-1 | [consumer] connected after 243 ms, queue assertedWithout any healthcheck, the retry loop from step 3 shows the boot window from the client's side: eight refusals, two seconds apart, then a connection once the listener is up:
consumer-1 | RabbitMQ not ready (attempt 7/15): ECONNREFUSED
consumer-1 | RabbitMQ not ready (attempt 8/15): ECONNREFUSED
rabbitmq-1 | 2026-10-10 22:26:39.567401+00:00 [info] <0.838.0> started TCP listener on [::]:5672
consumer-1 | Connected, waiting for messages on new_user_notificationThe close handler earns its place when the broker restarts under a running consumer. After docker compose restart rabbitmq, amqplib emitted close, the consumer exited, Docker restarted it once (the container's restart count went to 1), and the retry loop waited out the new boot:
consumer-1 | AMQP connection closed, exiting so Docker restarts the consumer
consumer-1 | RabbitMQ not ready (attempt 1/15): ECONNREFUSED
consumer-1 | RabbitMQ not ready (attempt 7/15): ECONNREFUSED
consumer-1 | Connected, waiting for messages on new_user_notificationOn your own stack, docker compose ps should list the broker as healthy, and docker compose exec rabbitmq rabbitmq-diagnostics -q listeners lists the AMQP listener on 5672 once it is open.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
Why a ping healthcheck still lets ECONNREFUSED through#
rabbitmq-diagnostics -q ping looks like the natural healthcheck, and it is the one that fooled the reproduction. With only the test line changed to ping, Compose marked the broker healthy, started the consumer, and the consumer was refused, twice out of two runs:
Container aotd-docker-and-rabbitmq-econnrefused-between-containers-rabbitmq-1 Healthy
consumer-1 | [consumer] connecting to amqp://guest:guest@rabbitmq:5672
consumer-1 | [consumer] code=ECONNREFUSED message=connect ECONNREFUSED 172.20.0.2:5672This is consistent with how the monitoring guide defines ping: the runtime answers it before the RabbitMQ application has opened its client listeners. If your healthcheck says healthy and the consumer still gets ECONNREFUSED at the broker's IP, check which command the healthcheck runs before anything else.
The next error: ACCESS_REFUSED after mounting /etc/rabbitmq#
The question's Compose file also mounts a host directory over the whole config directory, /rabbitmq/conf:/etc/rabbitmq/. That hides the files the official image puts there. One of them, conf.d/10-defaults.conf, contains loopback_users.guest = false, with the comment "allow access to the guest user from anywhere on the network". RabbitMQ's own default, per its access control guide, is that "the guest user is prohibited from connecting from remote hosts".
So once the host and the timing are fixed, a consumer connecting as guest from another container fails in a new way. Reproduced with the healthcheck from step 2 and a mounted directory that contains only a one-line rabbitmq.conf (log.console = true):
rabbitmq-1 | 2026-10-10 22:25:48.756869+00:00 [info] <0.660.0> Server startup complete; 0 plugins started.
rabbitmq-1 | 2026-10-10 22:25:52.796628+00:00 [error] <0.697.0> PLAIN login refused: user 'guest' can only connect via localhost
consumer-1 | [consumer] code=undefined message=Handshake terminated by server: 403 (ACCESS-REFUSED) with message "ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN. For details see the broker logfile."The same mount also hid /etc/rabbitmq/enabled_plugins, which in this image lists the management and Prometheus plugins: the broker logged "0 plugins started", against "5 plugins started" without the mount, so the management plugin was not running either. The fix RabbitMQ recommends is a user of your own, not re-opening guest. The image reads RABBITMQ_DEFAULT_USER and RABBITMQ_DEFAULT_PASS; with the same mount kept and only those two variables and the URL changed, the consumer connected:
services:
rabbitmq:
image: rabbitmq:3-management
hostname: rabbitmq
volumes:
- ./conf-own:/etc/rabbitmq/
environment:
RABBITMQ_DEFAULT_USER: app
RABBITMQ_DEFAULT_PASS: change-me
consumer:
environment:
AMQP_URL: amqp://app:change-me@rabbitmq:5672The default user is created only when a node starts with an uninitialised database (a "blank node"), so on a volume that already holds data, recreate the volume or add the user with rabbitmqctl. Better still, mount single files into /etc/rabbitmq/conf.d/ rather than replacing the directory. If the variables never reach the container at all, check the YAML shape of the environment block: a - KEY: value item fails with the Compose "unexpected type map" error.
The reproduction used RabbitMQ 3.13.7 because that image was already on the test machine; the Docker Hub tags are now on the 4.x series, whose image ships the same 10-defaults.conf line and no healthcheck. When the same kind of failure shows up in CI rather than on your machine, with a job running inside a container, the GitHub Actions ECONNREFUSED fix explains which host a job container must use. With the service name in the URL, a healthcheck that waits for the AMQP port, and a user that is allowed to log in remotely, the consumer's first connection is no longer a race against the broker's boot.
Further reading: Docker: Run a Shell Command Before ENTRYPOINT at Startup.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix Docker: Client Version Is Too Old
Separate an outdated Docker client from a pinned API or the wrong context, and check the minimum your exact Engine release accepts.
Fix Docker Compose 'Exec format error' loading a library
Your app crashes on docker-compose up while it works fine outside Docker — here is why a native module refuses to load, and the three fixes that actually remove it.
Docker 'OCI runtime create failed': a flag after the image
See why runc tries to execute -w, why the official node image now answers node: bad option: -w, and which cause each other create-time message and exit code points to.


