When this guide is the right one
Use this runbook if your Minecraft container starts normally, but players outside the server cannot connect. Typical symptoms are:
Connection refused- connection timeouts
- the server works on
localhost, but not via the public IP docker compose pslooks healthy, yet nobody can join
The goal is simple: make sure Minecraft Java traffic can reach your server on TCP port 25565 from the internet.
60-second quick check
- Confirm Docker published
25565:25565. - Confirm the Linux host is listening on port 25565.
- Check how Docker interacts with your host firewall.
- Allow TCP
25565in the Hetzner Cloud Firewall. - Test from a different network.
Prerequisites
- A Minecraft Java server running in Docker or Docker Compose
- A public IPv4 or IPv6 on your Hetzner server
- SSH access to the Linux host
If you have not deployed the server yet, start with Setting up a Minecraft Server on a Hetzner Cloud Server with Docker Compose.
Step 1: Verify that Docker actually publishes the game port
Start with the local truth. If Docker never published the port, firewall changes will not help.
docker compose ps
docker ps --format 'table {{.Names}}\t{{.Ports}}'
You want to see 25565/tcp published on the host, for example:
0.0.0.0:25565->25565/tcp
:::25565->25565/tcp
If nothing is published, fix the Compose file first:
services:
mc:
image: itzg/minecraft-server:latest
ports:
- "25565:25565"
Then apply the change:
docker compose up -d
Step 2: Check whether the host is listening on 25565
Next, confirm that the Linux host sees a listener on the public side.
sudo ss -tlnp | grep 25565
If the command returns nothing, one of these is usually true:
- the container is not running
- the port mapping is missing
- the service failed during startup
In that case, inspect the logs:
docker compose logs -f
Step 3: Check the Linux firewall path
Docker Engine creates its own packet-filtering and NAT rules for published ports. On a normal Linux bridge network, traffic to 25565:25565 is diverted before it reaches the INPUT and OUTPUT chains used by UFW. As a result, a published container port can remain reachable even if UFW would otherwise deny it.
You can still record the intended rule in UFW:
sudo ufw allow 25565/tcp
sudo ufw status
Do not treat that rule as proof that UFW controls the published Docker port. Verify the effective path from another network.
If you need source-IP restrictions on the Linux host, use rules that are evaluated before Docker accepts forwarded traffic. With the default iptables backend, Docker documents the DOCKER-USER chain for this purpose. With Docker's nftables backend, use a separate forward chain with the correct priority. Do not disable Docker's firewall management just to make UFW behave differently; that can break container networking.
Do not remove SSH access while testing. If your firewall is very restrictive, keep 22/tcp allowed from your admin IP before changing anything else.
Step 4: Open the Hetzner Cloud Firewall
Hetzner Cloud Firewalls sit outside the server. That means your Linux host can be configured correctly and traffic can still be dropped before it ever reaches the VM.
In the Hetzner console:
- Open your project.
- Go to
Firewalls. - Create or edit the firewall attached to the Minecraft server.
- Add an inbound rule for
TCP, port25565, source0.0.0.0/0if you want public access. - If you also want IPv6 players to connect, add
::/0as a source as well.
For the initial test, keep the rule simple. Once joining works, you can tighten other ports such as SSH.
Step 5: Test from outside the server
Do not test only from the host itself. Use one of these instead:
- join from your normal Minecraft client on a different connection
- ask a friend to connect
- test from a mobile hotspot
Your Java client should connect to:
YOUR_SERVER_IP:25565
If the connection works now, the network path is fixed and you can move on to a nicer address with Minecraft Domain Without a Port: How to Set Up an SRV Record.
Common mistakes
| Symptom | Likely cause | Fix |
|---|---|---|
docker ps shows no published ports | Compose file has no ports mapping | Add "25565:25565" and recreate |
| UFW denies the port, but it is still reachable | Docker published ports bypass normal UFW processing | Restrict it with the Hetzner Cloud Firewall or Docker-aware forward rules |
| UFW allows the port, but it is still blocked publicly | Hetzner Cloud Firewall still drops traffic | Add inbound TCP 25565 there as well |
| Port scan says closed, logs look normal | Wrong protocol | Use TCP for a Java server |
| RCON works internally, but players cannot join | Only admin path works | Publish the Minecraft game port, not just RCON |
Security note
Open only the port you actually need for players. For a normal Minecraft Java setup, that is usually TCP 25565 and nothing else. Do not expose RCON just because you want admin access. Keep administrative access on the Docker host or inside a private Docker network.
On Hetzner, the Cloud Firewall is the simplest outer allowlist for public game traffic. Host-level Docker-aware rules remain useful as a second layer, but a plain UFW status alone does not describe the effective exposure of published container ports.
FAQ
Should I bind the game port only to one IP?
Only if you know exactly why you need that. For most single-server setups, the default public bind is fine.
What about the Query port?
Only enable and expose Query if you intentionally use it. A normal Java server does not need it for players to join.
Next steps
- Want a cleaner address? Read Minecraft Domain Without a Port: How to Set Up an SRV Record.
- Still in initial setup? Go back to the Hetzner Docker setup guide.
- Need secure administration instead of more open ports? Read Administer a Docker Minecraft Server Safely with RCON and Console Access.