| Tested on | Red Hat Enterprise Linux 10.2 (Coughlan) |
|---|---|
| Package | podman-5.8.2-5.el10_2.x86_64netavark-1.17.2-1.el10.x86_64 |
| Applies to | Linux hosts with Podman where -p appears configured but clients cannot reach the published service |
| Privilege | Rootful examples as root; rootless low-port and pasta notes where behavior differs |
| Scope | Diagnosing published-but-unreachable services — podman port, podman inspect port bindings, curl connection refused versus reset, application listen address inside the container, wrong container-side port, stopped containers, host bind IP scope, pod-level publishing rules, rootless ports below 1024, --network=host, macvlan/ipvlan limits, firewall-cmd --reload with podman network reload, and custom SELinux policy only when AVCs exist. Does not cover full -p syntax, privileged-port sysctl recipes, generic firewall administration, or Netavark architecture. |
You published a port with -p, podman port shows a mapping, and clients still cannot connect. Before you open the host firewall or rebuild the image, decide which layer is failing:
- Host listener — the TCP socket Podman bound on the host
- Forwarding path — Netavark or pasta between host and container namespace
- Application socket — the process listening inside the container
Those three layers produce different curl errors and need different fixes.
-p syntax, random ports, UDP, and pod-level publish rules, see Podman port mapping. For sysctl, reverse-proxy, and firewalld redirect recipes on rootless ports below 1024, see Rootless Podman privileged ports.
First confirm Podman published the port
Start with what Podman thinks is bound on the host. Replace CONTAINER with your container name or ID:
podman port CONTAINEROn a healthy nginx publish in the lab:
80/tcp -> 0.0.0.0:18086That line means host TCP port 18086 forwards to container port 80 on all host addresses. If podman port prints nothing, you have a configuration problem — not a connectivity mystery. No client will reach a service Podman never published.
Inspect stores the same data in JSON:
podman inspect --format '{{json .NetworkSettings.Ports}}' CONTAINERSample output for the same mapping:
{"80/tcp":[{"HostIp":"0.0.0.0","HostPort":"18086"}]}When both commands show the expected host and container ports, move on to whether anything is listening on the container side and whether the container is still running.
curl: (7) Failed to connect: Connection refused
Connection refused means the TCP handshake did not find a listener on the path your client used. Test from the host first:
curl -v --connect-timeout 3 http://127.0.0.1:HOST_PORT/Sample output when nothing accepts the forwarded traffic:
* Trying 127.0.0.1:18080...
* connect to 127.0.0.1 port 18080 from 127.0.0.1 port 40566 failed: Connection refused
* Failed to connect to 127.0.0.1 port 18080 after 0 ms: Could not connect to server
curl: (7) Failed to connect to 127.0.0.1 port 18080 after 0 ms: Could not connect to serverWork through these checks in order — each eliminates a layer:
- Is the container running? (
podman psversuspodman ps -a) - Does
podman portshow the mapping you expect? - Is the application listening inside the container?
- Is the published container port the port the app actually uses?
- Is the host bind address limited to loopback when your client uses a LAN IP?
- Does host firewall or cloud security policy block remote clients only?
The sections below walk each branch with lab output. Do not jump to firewall-cmd until local curl to 127.0.0.1 behaves the way you expect.
curl: (56) Recv failure: Connection reset by peer
Reset is not the same as refused. The client reached a listener, then the remote side closed the connection before a valid response arrived. On the lab host I used a Python socket that accepts TCP and immediately closes; this is what curl printed:
curl -v --connect-timeout 3 http://127.0.0.1:18093/* Trying 127.0.0.1:18093...
* Connected to 127.0.0.1 (127.0.0.1) port 18093
> GET / HTTP/1.1
> Host: 127.0.0.1:18093
> Accept: */*
* Recv failure: Connection reset by peer
curl: (56) Recv failure: Connection reset by peerOn the lab host I reproduced that with a Python socket that accepts TCP and immediately closes — port forwarding worked, but there was no HTTP server behind it. Real stacks show the same pattern when:
- You speak HTTP to a TLS-only port (or the reverse)
- A reverse proxy or app accepts then crashes on the first read
- A health check hits the wrong path on a server that resets unknown routes
Do not assume every reset means the application bound to 127.0.0.1. Prove the listener first with ss inside the container (next section). If a listener exists on 0.0.0.0 at the right port, investigate application protocol and logs — not Podman's publish table.
Service listens only on container 127.0.0.1
This is the most common application-side cause when podman port looks correct but local curl still refuses the connection.
Port forwarding targets the container's network interface addresses. Traffic does not magically enter a process that bound only to loopback inside the namespace. Reproduce it with Python's built-in HTTP server.
Start a container that listens on loopback only and publish port 8080:
podman run -d --name port-bind-test -p 18080:8080 docker.io/library/python:3-alpine sh -c 'python3 -m http.server 8080 --bind 127.0.0.1'podman port still shows a publish rule:
8080/tcp -> 0.0.0.0:18080Test from the host:
curl -v --connect-timeout 3 http://127.0.0.1:18080/* connect to 127.0.0.1 port 18080 failed: Connection refused
curl: (7) Failed to connect to 127.0.0.1 port 18080 after 0 ms: Could not connect to serverInside the container, the listener is loopback-only:
podman exec port-bind-test ss -lntpState Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 5 127.0.0.1:8080 0.0.0.0:* users:(("python3",pid=1,fd=3))127.0.0.1:8080 is the smoking gun. Forwarded traffic arrives on the container interface, not on that socket.
Stop the failing container and run the same publish with an interface-wide bind:
podman rm -f port-bind-testRecreate the container with 0.0.0.0 as the bind address:
podman run -d --name port-bind-test -p 18080:8080 docker.io/library/python:3-alpine sh -c 'python3 -m http.server 8080 --bind 0.0.0.0'Confirm the listener changed:
podman exec port-bind-test ss -lntpState Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 5 0.0.0.0:8080 0.0.0.0:* users:(("python3",pid=1,fd=3))Now host curl succeeds:
curl -sS -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:18080/HTTP 200Fix the application configuration — bind address, Listen directive, or framework default — not Podman's -p flag. Remove the lab container when finished:
podman rm -f port-bind-testWrong container port in the publish mapping
The right-hand side of -p is the port inside the container namespace. If the app listens on 8080 but you published -p 18081:80, Podman forwards host 18081 to container port 80 where nothing listens.
Reproduce with Python on 8080 but publish container port 80:
podman run -d --name port-wrong -p 18081:80 docker.io/library/python:3-alpine sh -c 'python3 -m http.server 8080 --bind 0.0.0.0'podman port reports the mapping you asked for:
80/tcp -> 0.0.0.0:18081Host curl still refuses:
curl -v --connect-timeout 3 http://127.0.0.1:18081/* connect to 127.0.0.1 port 18081 failed: Connection refused
curl: (7) Failed to connect to 127.0.0.1 port 18081 after 0 ms: Could not connect to serverCompare the application listener:
podman exec port-wrong ss -lntpLISTEN 0 5 0.0.0.0:8080 0.0.0.0:* users:(("python3",pid=1,fd=3))The fix is -p 18081:8080 (or reconfigure the app to listen on 80). Compare podman port output with ss inside the container — do not open the host firewall when the container-side destination port is wrong.
podman rm -f port-wrongContainer is not running
A stopped container can still show port metadata in podman inspect, but no process serves the mapping.
Start a demo container, publish a port, then stop it:
podman run -d --name port-exited -p 18082:8080 docker.io/library/python:3-alpine sh -c 'python3 -m http.server 8080 --bind 0.0.0.0'Stop the container so we can prove the publish line alone does not keep the port open:
podman stop port-exitedList all containers:
podman ps -a --filter name=port-exitedCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
79482c54e6b2 docker.io/library/python:3-alpine sh -c python3 -m ... 12 seconds ago Exited (137) 1 second ago 0.0.0.0:18082->8080/tcp port-exitedExited means the publish line in podman ps -a is historical — nothing is listening now:
curl -v --connect-timeout 3 http://127.0.0.1:18082/curl: (7) Failed to connect to 127.0.0.1 port 18082 after 1 ms: Could not connect to serverStart the container again, or diagnose why it exited. For exit codes, State.ExitCode, and detached-mode pitfalls, see Fix container exits immediately.
podman rm -f port-exitedHost IP binding limits who can connect
-p 127.0.0.1:HOST_PORT:CONTAINER_PORT publishes only on the host loopback interface. Local curl works; a remote client using your LAN address does not.
Create a loopback-only publish:
podman run -d --name port-loopback -p 127.0.0.1:18083:8080 docker.io/library/python:3-alpine sh -c 'python3 -m http.server 8080 --bind 0.0.0.0'podman port shows the restricted host IP:
8080/tcp -> 127.0.0.1:18083On the host, ss confirms the listener is not on 0.0.0.0:
ss -lntp | grep 18083LISTEN 0 4096 127.0.0.1:18083 0.0.0.0:* users:(("conmon",pid=541816,fd=5))That is expected when you intentionally bind loopback. To accept LAN or internet clients, publish on 0.0.0.0:PORT or a specific host interface IP — see Podman port mapping for host-IP forms.
podman rm -f port-loopbackHost firewall blocks remote access only
When curl http://127.0.0.1:HOST_PORT works on the Podman host but another machine cannot connect, the publish path is probably fine and the perimeter is not.
Check what the host firewall exposes. On RHEL-family systems:
firewall-cmd --list-allLook for the host port in the active zone's ports or forward-ports list. Open only the port you intend to expose — do not disable firewalld as a troubleshooting shortcut.
When the client is off-host, also verify:
- cloud security groups
- router port forwarding
- VLAN routing
This article does not duplicate full firewall administration; it stops at confirming local publish works before you chase external policy.
firewall-cmd --reload broke a working mapping
On rootful Netavark bridge networks, firewall-cmd --reload can remove Netavark-managed rules while containers keep running. Egress and ingress forwarding may both regress depending on what changed.
When publish worked until a firewall reload, recreate Netavark integration for affected containers:
podman network reload CONTAINERFor many containers after a maintenance window:
podman network reload --allThat command rebuilds firewall hooks for running rootful bridge containers. It is not a substitute for fixing application bind addresses or pod publish mistakes. Rootless pasta uses a different path — see Fix container cannot access Internet for when network reload applies versus when it does not.
cannot set port bindings on a pod member
Pod members share one network namespace. Host ports belong on podman pod create, not on podman run --pod.
Create a pod with a publish rule:
podman pod create --name webpod -p 18084:80Try to add another mapping on a member container:
podman run --pod webpod -d -p 18085:80 docker.io/library/nginx:alpinePodman 5.8.2 on the lab host returns:
Error: invalid config provided: published or exposed ports must be defined when the pod is created: network cannot be configured when it is shared with a podOlder releases sometimes quoted a shorter variant:
Error: cannot set port bindings on an existing container network namespaceBoth mean the same thing: publish on podman pod create -p, then add members without their own -p flags. Full pod networking rules live in Podman pod networking.
podman pod rm -f webpodRootless host port below 1024
Rootless Podman cannot bind privileged host ports without extra system configuration. A publish like -p 80:8080 fails before the container starts:
Error: pasta failed with exit code 1:
Failed to bind port 80 (Permission denied) for option '-t 80-80:8080-8080'That is a publish-time failure — not a silent mapping that clients later refuse. Use a high host port during development, or follow Rootless Podman privileged ports for supported sysctl, redirect, and reverse-proxy patterns. This article does not duplicate those recipes.
--network=host changes the model
With --network=host, the container shares the host network stack. Normal -p publishing is not how reachability works — the application must listen directly on the host addresses and ports you expect clients to use.
Check the network mode before you debug bridge forwarding:
podman inspect --format '{{.HostConfig.NetworkMode}}' CONTAINERWhen the mode is host, inspect ss -lntp on the host and the application's bind configuration instead of podman port.
Macvlan and ipvlan networks
Podman port publishing is supported for bridge networking and pasta, not for macvlan/ipvlan networks. With macvlan or ipvlan, expose the service on the container's own network address and have clients connect to that address directly.
If your container uses a macvlan/ipvlan network, clients usually reach the container IP — not a separate published host port. See Podman networking modes for when to choose those drivers and how they differ from bridge publish.
Custom SELinux policy only when AVCs exist
Ordinary Podman port forwarding on RHEL does not need random semanage port additions. Investigate SELinux only when audit logs show denials against Podman, pasta, or container_runtime_t during bind or forward operations:
ausearch -m AVC -ts recentNo AVC lines means do not invent an SELinux port label fix. Custom policies are a secondary branch for unusual hardened environments — not the default explanation for refused connections.
Diagnostic order
Use this sequence before you change unrelated host settings:
1. podman ps — is the container running?
2. podman port — does Podman show the mapping?
3. curl from the host to 127.0.0.1:HOST_PORT — refused or reset?
4. podman exec CONTAINER ss -lntp — what address and port does the app use?
5. Confirm bind is 0.0.0.0 or the expected interface, not 127.0.0.1 only
6. Match container-side port in -p to the listener port
7. Check host bind IP in podman port (127.0.0.1 vs 0.0.0.0)
8. For pods, confirm -p was set on podman pod create
9. For rootless, rule out privileged host ports below 1024
10. If remote-only failure, check host firewall and cloud security groups
11. After firewall-cmd --reload on rootful bridge, try podman network reload
12. SELinux only when ausearch shows relevant AVC denialsTroubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
podman port empty |
No publish configured | Add -p and recreate; see port mapping guide |
Connection refused locally |
Stopped container, wrong container port, or 127.0.0.1 bind inside |
podman ps, ss -lntp in container, fix bind or mapping |
Connection reset by peer |
Listener accepts then drops (protocol/app) | Prove port with ss; check TLS/HTTP mismatch and app logs |
Mapping exists, app on 127.0.0.1 |
Loopback-only listener | Bind 0.0.0.0 or service interface address |
-p 8080:80 but app on 8080 |
Wrong container port in mapping | Change to -p 8080:8080 or move app to 80 |
Exited in podman ps -a |
No running process | podman start or fix exit cause |
| Works locally, fails from LAN | Host publish on 127.0.0.1 or firewall |
Widen host bind or open intended port in firewall |
Broke after firewall-cmd --reload |
Dropped Netavark rules | podman network reload |
-p rejected on podman run --pod |
Pod namespace already created | Publish on podman pod create |
Permission denied on port 80 rootless |
Privileged host port | High port or privileged-port guide |
--network=host |
No bridge publish path | Listen on host ports directly |
References
- podman-port(1) — Podman documentation
- podman-inspect(1) — Podman documentation
- podman-network-reload(1) — Podman documentation
- podman-pod-create(1) — Podman documentation
- Red Hat — Building, running, and managing containers
Summary
When Podman shows a published port but clients cannot connect, split the problem into three layers: the host listener Podman created, the forward path through Netavark or pasta, and the application socket inside the container. podman port and podman inspect confirm the first layer. curl error codes narrow the next step — refused usually means no listener on the forwarded path; reset means something accepted TCP but did not complete a valid conversation.
The flagship lab case is an application bound to 127.0.0.1 inside the container while -p targets the container interface. ss -lntp inside the namespace shows the bind address immediately. Wrong container-side ports, stopped containers, and loopback-only host publishes produce similar refused symptoms with different fixes. Pod members need -p on podman pod create; rootless publishes below 1024 fail at bind time with a clear permission error.
After legitimate firewall maintenance on rootful bridge networks, podman network reload restores forwarding rules without recreating containers. For full publish syntax and pod design, continue with Podman port mapping and Podman pod networking.

