When a container cannot be reached from the internet, or cannot reach anything itself, the first question is which network mode it uses. In bridge mode the container sits behind Docker's NAT and you publish ports with -p; in host mode the container shares the host's network stack directly and -p does nothing. Knowing which one you are debugging tells you where to look.
This guide walks through the checks in order, from the container outward, so you can find whether the problem is the application bind address, the published port, the host firewall, DNS, or MTU.
Before you start
You need:
- A Linux VPS with root access and Docker installed. If Docker is not on the server yet, see Install Docker and Docker Compose on a Linux VPS.
- SSH access, or the browser console in your VPS provider's control panel if SSH is not answering.
- A container to test with. Any web image works; examples below use a container named
webpublishing port 8080 on the host. - The server's public IP. Examples use
203.0.113.10— replace it with yours.
Note: Run the host commands as root or with sudo. The docker exec commands run inside the container and do not need sudo.
Understand what each mode does
Bridge mode is the default. Docker creates a docker0 bridge, gives the container an address on it (usually in 172.17.0.0/16), and adds NAT rules so the container can reach the internet. Incoming traffic only arrives if you publish a port with -p 8080:80, which adds a DNAT rule on the host.
Host mode removes that layer. With --network host the container binds directly to the host's interfaces and ports. There is no NAT, no separate IP, and -p is ignored with a warning. That makes host mode fast and simple for things like a reverse proxy, but every port conflict is now a conflict with the host itself.
Step 1: Confirm the network mode and addresses
Start by listing what is actually running and how.
docker ps --format 'table {{.Names}}\t{{.Ports}}\t{{.Status}}'
docker inspect -f '{{.Name}} -> {{.HostConfig.NetworkMode}}' $(docker ps -q)
docker network lsThe Ports column shows the mapping for bridge containers (0.0.0.0:8080->80/tcp) and is empty for host mode. docker inspect confirms the mode. If a container reports bridge but you expected host, the mode flag is missing from the run command or compose file.
Look at the bridge itself:
ip -brief addr show docker0
ip routeYou should see an address such as 172.17.0.1/16 on docker0. If the interface does not exist, no bridge-mode container has started yet.
Step 2: Test from inside the container
Get a shell inside the container and check the three things that break most often: address, route and DNS.
docker exec -it web sh -c 'ip addr; echo ---; ip route; echo ---; cat /etc/resolv.conf'Then test outbound traffic:
docker exec -it web ping -c 3 1.1.1.1
docker exec -it web curl -sS -o /dev/null -w '%{http_code}\n' https://example.comIf the ping to an IP works but the name lookup fails, the problem is DNS, not routing. If both fail, the container has no working path out. See Open a shell inside a running Docker container for more detail on the exec flags.
Step 3: Test the published port from the host, then from outside
For a bridge container, check that the host is listening and that the rule works locally.
ss -lntp | grep 8080
curl -v http://127.0.0.1:8080
curl -v http://203.0.113.10:8080Three outcomes matter:
- Localhost works, public IP fails on the same machine → a host firewall is dropping the port. Compare rule sets with UFW vs firewalld vs nftables vs iptables and open the port you published.
- Both fail with connection refused → nothing is listening. Check the container is running, the port mapping exists, and the application inside binds
0.0.0.0, not127.0.0.1. - Localhost fails but the container is up → the published port is bound to a specific host address, for example
-p 127.0.0.1:8080:80, which is only reachable from the server itself.
Test from a different machine, not from the server, because traffic from the host to its own public IP takes a different path:
curl -v http://203.0.113.10:8080Step 4: Fix host mode port conflicts
Host mode fails differently. There is no NAT, so the container's process must be able to bind the port on the host.
ss -lntp | grep -E ':(80|443|8080)\b'
docker logs --tail 50 webIf another process already owns the port, the container exits or logs address already in use. Stop the conflicting service, change the port the container listens on, or move that container back to bridge mode with a published port. Remember that -p in a host-mode run is silently ignored — do not debug a mapping that was never created.
Step 5: Container-to-container DNS
Containers on the default bridge can reach each other by IP but not by name. Name resolution between containers works only on a user-defined network, where Docker runs an embedded DNS server at 127.0.0.11.
docker network create appnet
docker run -d --name web --network appnet nginx
docker run --rm --network appnet alpine getent hosts webIf the last command returns nothing, the containers are not on the same user-defined network. Create one and attach both, as described in Docker networks: bridge, host and custom networks explained.
Step 6: Check outbound traffic and MTU
When containers can reach the gateway but not the internet, check forwarding and NAT rules on the host:
sysctl net.ipv4.ip_forward
iptables -t nat -S POSTROUTING | headnet.ipv4.ip_forward must be 1. If it is 0, enable it in /etc/sysctl.d/99-docker.conf with net.ipv4.ip_forward=1 and apply with sysctl --system. Docker normally sets this, so a 0 here means something else changed it.
A second common cause is an MTU mismatch on networks that use tunnels or encapsulation. Small requests work and larger ones hang. Test it:
docker run --rm alpine ping -M do -s 1400 -c 2 1.1.1.1If that fails, lower the MTU for the Docker daemon in /etc/docker/daemon.json:
{
"mtu": 1400
}Warning: Restarting Docker stops and restarts every running container unless you enabled live restore. Plan for a short outage before you run systemctl restart docker.
Then restart the daemon and re-run the test.
Step 7: Reach the host from a container
If a container needs to call a service on the host itself, do not hard-code the host IP. Add a name that points at the host gateway:
docker run --rm --add-host=host.docker.internal:host-gateway alpine getent hosts host.docker.internalCheck it works
Run these in order and all four should pass:
docker inspect -f '{{.HostConfig.NetworkMode}}' web
docker exec -it web curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1
curl -sS -o /dev/null -w '%{http_code}\n' http://203.0.113.10:8080
docker exec -it web getent hosts example.comThe first confirms the mode, the second proves the application answers inside the container, the third proves the path from the internet, and the fourth proves DNS. A 000 code means curl never connected — go back to the step that covers that layer.
Troubleshooting
- Connection refused from outside, works on localhost. The application binds
127.0.0.1inside the container. Change its listen address to0.0.0.0and restart the container. port is already allocated. Another container or host process owns that host port. Find it withss -lntpand pick a different published port.- Containers cannot resolve each other by name. They are on the default bridge, which has no DNS. Create a user-defined network and attach both containers.
- Ping to an IP works, hostnames fail. Check
/etc/resolv.confinside the container and the host's own resolver. A broken host resolver propagates into containers. - Small requests succeed, large transfers stall. Classic MTU problem. Lower the daemon MTU as shown above and restart Docker.
-pseems to have no effect. The container runs in host mode. Remove--network hostor stop passing-p.
Next steps
- Docker networks: bridge, host and custom networks explained for the full picture of drivers and DNS.
- Docker run flags you will actually use to get
-p,-vand-eright the first time. - Docker logs: view and rotate container logs when you need the application's own error output.
- UFW vs firewalld vs nftables vs iptables if the host firewall is blocking published ports.
