Fix Podman Port Mapping Not Working

Tested on Red Hat Enterprise Linux 10.2 (Coughlan)
Package podman-5.8.2-5.el10_2.x86_64
netavark-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.

IMPORTANT
This article troubleshoots reachability after you already used port publishing. For -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:

bash
podman port CONTAINER

On a healthy nginx publish in the lab:

output
80/tcp -> 0.0.0.0:18086

That 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:

bash
podman inspect --format '{{json .NetworkSettings.Ports}}' CONTAINER

Sample output for the same mapping:

output
{"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:

bash
curl -v --connect-timeout 3 http://127.0.0.1:HOST_PORT/

Sample output when nothing accepts the forwarded traffic:

output
*   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 server

Work through these checks in order — each eliminates a layer:

  1. Is the container running? (podman ps versus podman ps -a)
  2. Does podman port show the mapping you expect?
  3. Is the application listening inside the container?
  4. Is the published container port the port the app actually uses?
  5. Is the host bind address limited to loopback when your client uses a LAN IP?
  6. 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:

bash
curl -v --connect-timeout 3 http://127.0.0.1:18093/
output
*   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 peer

On 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:

bash
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:

output
8080/tcp -> 0.0.0.0:18080

Test from the host:

bash
curl -v --connect-timeout 3 http://127.0.0.1:18080/
output
* 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 server

Inside the container, the listener is loopback-only:

bash
podman exec port-bind-test ss -lntp
output
State  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:

bash
podman rm -f port-bind-test

Recreate the container with 0.0.0.0 as the bind address:

bash
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:

bash
podman exec port-bind-test ss -lntp
output
State  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:

bash
curl -sS -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:18080/
output
HTTP 200

Fix the application configuration — bind address, Listen directive, or framework default — not Podman's -p flag. Remove the lab container when finished:

bash
podman rm -f port-bind-test

Wrong 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:

bash
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:

output
80/tcp -> 0.0.0.0:18081

Host curl still refuses:

bash
curl -v --connect-timeout 3 http://127.0.0.1:18081/
output
* 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 server

Compare the application listener:

bash
podman exec port-wrong ss -lntp
output
LISTEN 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.

bash
podman rm -f port-wrong

Container 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:

bash
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:

bash
podman stop port-exited

List all containers:

bash
podman ps -a --filter name=port-exited
output
CONTAINER 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-exited

Exited means the publish line in podman ps -a is historical — nothing is listening now:

bash
curl -v --connect-timeout 3 http://127.0.0.1:18082/
output
curl: (7) Failed to connect to 127.0.0.1 port 18082 after 1 ms: Could not connect to server

Start the container again, or diagnose why it exited. For exit codes, State.ExitCode, and detached-mode pitfalls, see Fix container exits immediately.

bash
podman rm -f port-exited

Host 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:

bash
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:

output
8080/tcp -> 127.0.0.1:18083

On the host, ss confirms the listener is not on 0.0.0.0:

bash
ss -lntp | grep 18083
output
LISTEN 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.

bash
podman rm -f port-loopback

Host 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:

bash
firewall-cmd --list-all

Look 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:

bash
podman network reload CONTAINER

For many containers after a maintenance window:

bash
podman network reload --all

That 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:

bash
podman pod create --name webpod -p 18084:80

Try to add another mapping on a member container:

bash
podman run --pod webpod -d -p 18085:80 docker.io/library/nginx:alpine

Podman 5.8.2 on the lab host returns:

output
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 pod

Older releases sometimes quoted a shorter variant:

text
Error: cannot set port bindings on an existing container network namespace

Both 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.

bash
podman pod rm -f webpod

Rootless 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:

output
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:

bash
podman inspect --format '{{.HostConfig.NetworkMode}}' CONTAINER

When 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:

bash
ausearch -m AVC -ts recent

No 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:

text
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 denials

Troubleshooting

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


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.


Frequently Asked Questions

1. Why does curl show connection refused when Podman port mapping looks correct?

Connection refused usually means nothing is accepting TCP on the published path — the container is stopped, the mapping targets the wrong container port, or the application listens only on 127.0.0.1 inside the namespace. Run podman ps, podman port, and podman exec CONTAINER ss -lntp before you change host firewall rules.

2. What is the difference between connection refused and connection reset by peer in Podman?

Connection refused means the TCP handshake never reached a listener on the forwarded path. Connection reset by peer means a listener accepted the connection but closed it without a valid response — often a protocol mismatch, a crashing handler, or a service that accepts then drops clients. Prove the listener with ss inside the container before blaming Podman forwarding.

3. Why cannot I reach my Podman container when the app binds to 127.0.0.1?

Port forwarding delivers traffic to the container network interface, not to loopback-only sockets. A process listening on 127.0.0.1:8080 inside the container is unreachable through a normal -p publish. Configure the application to listen on 0.0.0.0 or the container interface address instead.

4. Can I add -p to podman run inside an existing pod?

No. Pod members share one network namespace. Publish ports on podman pod create with -p. Podman rejects container-level -p on podman run --pod because the pod network is already created.

5. Does firewall-cmd --reload break published Podman ports?

On rootful Podman networks, firewall-cmd --reload can remove Podman-managed firewall rules while containers keep running, causing published ports or other container connectivity to fail. Run podman network reload CONTAINER or podman network reload --all to restore the network configuration after legitimate firewall changes. See the container no Internet guide for the full egress story.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)