Start with the exact failure layer
“Velocity cannot connect” can describe four different failures:
- the backend container is stopped or restarting
- the proxy cannot resolve or route to the backend container
- nothing listens on the configured backend port yet
- 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"invelocity.tomlproxies.velocity.enabled: trueinpaper-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
| Error | Most likely layer | First action |
|---|---|---|
Unknown host lobby | Docker DNS/network membership | Compare live network names |
| Connection refused | Backend stopped or not listening | Read Paper startup logs |
| Connection timed out | Route or firewall drops traffic | Test from proxy namespace and inspect networks |
This server requires you to connect with Velocity | Forwarding mismatch | Align modern forwarding on both sides |
| Works after long delay | Proxy starts before Paper is ready | Use health/readiness checks; avoid restart loops |
| Works via published port only | Containers do not share a network | Fix 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
- Return to the complete Velocity Compose setup.
- Secure the working path with modern forwarding and backend isolation.
- If Paper itself is unstable, diagnose the Docker restart loop.