setupmc.com

Velocity Proxy Cannot Connect to a Backend in Docker: A Runbook

Fix Velocity backend connection failures by checking container state, Docker networks, service-name DNS, the container port, Paper readiness, and forwarding separately.

Networking
setupmc.com Team

Use the matching tool

Minecraft proxy configurator

Create a Docker Compose setup for Velocity, BungeeCord, or a custom proxy.

Open Minecraft proxy configurator

Start with the exact failure layer

“Velocity cannot connect” can describe four different failures:

  1. the backend container is stopped or restarting
  2. the proxy cannot resolve or route to the backend container
  3. nothing listens on the configured backend port yet
  4. TCP succeeds, but player forwarding rejects the login

Do not change forwarding secrets to fix an UnknownHostException, and do not publish the backend port merely to work around a missing Docker network.

Step 1: Preserve the first useful error

Capture state and bounded logs before restarting repeatedly:

docker compose ps -a
docker compose logs --tail=200 proxy lobby
docker inspect "$(docker compose ps -q lobby)" --format 'status={{.State.Status}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} health={{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}'

Replace lobby if your backend service has another name.

If Paper is exiting, solve that first. The container restart-loop runbook separates EULA, Java, permissions, memory, and configuration failures.

Step 2: Verify the address in velocity.toml

For this Compose model:

services:
  proxy:
    networks: [minecraft]
  lobby:
    networks: [minecraft]
networks:
  minecraft: {}

Velocity should use:

[servers]
lobby = "lobby:25565"
try = ["lobby"]

Avoid these common mistakes:

  • localhost:25565: points back into the proxy container
  • a container IP: changes when Docker recreates the container
  • a host mapping such as 25566: irrelevant for traffic inside the Compose network
  • the public domain: leaves Docker and creates an unnecessary external round trip

Docker registers the service name on shared Compose networks. Use the service name, not container_name, as the stable contract.

Step 3: Compare network membership

Render the effective model and inspect the live networks:

docker compose config
docker inspect "$(docker compose ps -q proxy)" --format '{{json .NetworkSettings.Networks}}'
docker inspect "$(docker compose ps -q lobby)" --format '{{json .NetworkSettings.Networks}}'

Both containers need at least one network name in common. If the proxy and backend are defined in separate Compose projects, their implicit default networks are different even if the YAML looks similar.

Create one explicit external network for separate projects:

docker network create minecraft-proxy

Then declare it in both Compose files:

networks:
  minecraft:
    external: true
    name: minecraft-proxy

Attach the relevant services to minecraft and recreate them. Do not use the deprecated links field as a substitute for correct network membership.

Step 4: Test from the proxy namespace

The decisive check runs from the same namespace as Velocity. The itzg/mc-proxy image includes mc-monitor, which is also used for its healthcheck:

docker compose exec proxy mc-monitor status --host lobby --port 25565

Interpret the result with the backend logs:

  • name cannot resolve: service name or shared network is wrong
  • connection refused: address resolves, but Paper is not listening on 25565
  • timeout: network path or firewall policy drops the connection
  • status succeeds: the TCP/application path works; investigate forwarding or login policy

Do not run only a test from the Docker host. Host reachability does not prove that the proxy container has the same route.

Step 5: Confirm Paper is actually ready

Inspect the most recent startup attempt:

docker compose logs --since=10m lobby

Look for the normal ready message and check that Paper did not bind to a different address or port. In server.properties, the normal container-friendly values are:

server-ip=
server-port=25565

Leaving server-ip empty allows the server to listen on the container interfaces. Setting it to a host IP that does not exist inside the container can prevent startup.

Step 6: Separate routing from forwarding

Once mc-monitor reaches Paper, a remaining player kick is usually configuration rather than networking. Compare:

  • player-info-forwarding-mode = "modern" in velocity.toml
  • proxies.velocity.enabled: true in paper-global.yml
  • matching forwarding secrets
  • matching proxy online-mode value
  • legacy Bungee forwarding disabled

Use the modern forwarding guide for the exact configuration. Change one layer at a time and repeat the same login test.

Symptom table

ErrorMost likely layerFirst action
Unknown host lobbyDocker DNS/network membershipCompare live network names
Connection refusedBackend stopped or not listeningRead Paper startup logs
Connection timed outRoute or firewall drops trafficTest from proxy namespace and inspect networks
This server requires you to connect with VelocityForwarding mismatchAlign modern forwarding on both sides
Works after long delayProxy starts before Paper is readyUse health/readiness checks; avoid restart loops
Works via published port onlyContainers do not share a networkFix the shared network and remove backend publication

Validate the repair

The repair is complete only when all checks pass:

docker compose ps
docker compose exec proxy mc-monitor status --host lobby --port 25565
docker compose port lobby 25565

The backend status check should succeed, and the final command should not reveal a host-published port. Join through Velocity and confirm that the backend sees the correct forwarded identity.

Next steps

Frequently asked questions

Short answers to the questions that usually come up while working through this topic.