← Start of tutorial

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.

Ubuntu 26.10Reviewed against official documentation ·

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
08

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.

bash

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
How do I know it worked?

No HTTP response on the direct external path; local and intended authenticated/TLS entry points remain healthy. Document IPv6 separately where present.

09

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.

bash

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'
How do I know it worked?

SSH keeps running with a loopback-only local forward; port conflicts fail visibly.

10

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.

bash

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/'
How do I know it worked?

The request reaches the same HTTP backend through SSH. This does not validate browser host-header or application-origin policy.

11

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.

bash

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}}'
How do I know it worked?

Identify the exact mismatch before another edit. Avoid publishing full logs or resolved environment configuration.

12

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.

bash

Docker host

cd '/srv/myapp' || exit 1
sudo docker compose -f compose.yaml stop 'web'
How do I know it worked?

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.

KEEP LEARNING

Your next step

Explore all tutorials ↗