WIUNIX ACADEMY
Why is your Docker port open when UFW says it is blocked?
Page 3 of 3. Review the earlier steps and prerequisites before continuing.
YOUR SYSTEM. YOUR STEPS.
Where are you working?
Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.
USE YOUR OWN VALUES
Configure this tutorial
Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.
Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.
Set values for my environment8 editable values
LET’S BUILD
Repeat the same external test, including IPv6
Repeat from the same independent workstation. For this IPv4 HTTP example, direct access should now fail while the local host request still succeeds. A 401/403 is NOT a closed port. If the host has public IPv6, separately test every actual IPv6 address with brackets and the same port; a v4-only result says nothing about v6.
Do not declare success from a timeout alone. Combine the runtime binding, local health, expected proxy path and external results. Other host services, additional replicas or container direct-routing paths can remain reachable outside this recipe's scope.
The terminal asks for an actual public IPv6 without brackets. Press Enter if the host has none; repeat for each address where present. This optional value is read in your terminal, not stored in the browser.
Independent workstation
curl --noproxy '*' --connect-timeout 3 --max-time 5 -sS -o /dev/null -w 'HTTP %{http_code}\n' 'http://203.0.113.10:8080/'
printf "Public IPv6, without brackets (Enter if none): "
IFS= read -r public_ipv6
if [ -n "$public_ipv6" ]; then
curl --noproxy '*' --connect-timeout 3 --max-time 5 -sS -o /dev/null -w 'HTTP %{http_code}\n' "http://[$public_ipv6]:8080/"
fi
No HTTP response on the direct external path; local and intended authenticated/TLS entry points remain healthy. Document IPv6 separately where present.
LET’S BUILD
Optional: reach the local backend over your existing SSH account
On your workstation with OpenSSH, bind the tunnel only to local loopback. Use an already authorized SSH account and verify the normal host key; do not disable host-key checking. Keep this terminal open. This creates access for local users of your workstation, not an application login system.
Independent workstation
ssh -N -o ExitOnForwardFailure=yes -L '127.0.0.1:18080:127.0.0.1:8080' -p '22' 'admin@203.0.113.10'SSH keeps running with a loopback-only local forward; port conflicts fail visibly.
LET’S BUILD
Test the forwarded path from a second terminal
Run in another workstation terminal while SSH remains open. Close the SSH session with Ctrl+C when finished. If access fails, keep the backend local and diagnose the tunnel; do not republish it to the Internet.
Independent workstation
curl --noproxy '*' --connect-timeout 3 --max-time 5 -sS -o /dev/null -w 'HTTP %{http_code}\n' 'http://127.0.0.1:18080/'The request reaches the same HTTP backend through SSH. This does not validate browser host-header or application-origin policy.
LET’S BUILD
If results disagree, inspect before changing more
Run these focused host checks. A stale wildcard means a wrong project/container or an extra published port. A healthy backend with a broken containerized proxy points to the network architecture. An empty port list is not proof of isolation in host or routed networking.
An empty ss result alone is not proof either: NAT publishing need not appear as an ordinary listening process. Inspect actual Docker bindings as well. Do not disable Docker firewall management or paste an iptables DOCKER-USER recipe onto the native nftables backend; their control points differ.
Docker host
cd '/srv/myapp' || exit 1
sudo docker compose -f compose.yaml ps
sudo docker ps --format 'table {{.Names}}\t{{.Ports}}'
sudo ss -lntp
cid=$(sudo docker compose -f compose.yaml ps -q 'web')
test -n "$cid" || exit 1
sudo docker inspect "$cid" --format '{{json .HostConfig.PortBindings}}'
Identify the exact mismatch before another edit. Avoid publishing full logs or resolved environment configuration.
LET’S BUILD
Contain a failed change without reopening the port
If the app is unhealthy and cannot be repaired inside the window, this optional host command stops only the selected service and causes downtime. It is a containment choice, not a mandatory step. Repair the private mapping and recreate through the owning procedure, then repeat both access tests.
Keep the original Compose copy to recover unrelated settings, but restoring its old wildcard mapping can reopen the exposure. Reapprove any public access explicitly. Never remove volumes or reset the host firewall as a recovery shortcut.
Docker host
cd '/srv/myapp' || exit 1
sudo docker compose -f compose.yaml stop 'web'If selected, only the affected service stops; persistent volumes remain. Recovery ends only after local and authorized-client health checks pass.
Keep a small acceptance record
Keep the engine version, selected project/service, before/after bindings, local response, external IPv4/IPv6 probes and authorized proxy result. Repeat after future deployment changes that touch ports or networks.
This guide was reviewed against official documentation on 27 October 2026. Commands are instructional data and were not executed against a production service during authoring. It provides a bounded verification method, not a claim that every deployment is secure.
GO TO THE SOURCE
Official references
Each environment states how it was reviewed. A documentation review does not claim a hands-on test of every version.
- Docker: firewall integration and UFW
- Docker: publishing ports and loopback limits
- Docker: inspect published mappings
- Compose: service port syntax
- Compose: resolve and validate configuration
- Compose: recreation behavior
- Docker: iptables user filtering
- Docker: native nftables backend
- Docker Engine on RHEL
- Red Hat: firewalld zones and policies
KEEP LEARNING