CertGrid CertGrid
Hands-on Lab·Podman

Podman Low-Port Publishing and sysctl

`-p 80:80` rootless fails with `pasta failed ... Listen failed for HOST TCP port */80: Permission denied`. One sysctl fixes it, and the failed container is still left behind for you to remove.

Networking Guide 27 of 47 Beginner

Written against the versions above. Podman follows the distribution here rather than a vendor repository, so the version you get is the one Ubuntu shipped. The commands are stable across 5.x.

Every command on this page ran on podman01.
Server NameIP AddressOSRolesCPURAMHDD
PODMAN01192.168.0.21Ubuntu 26.04 LTSPrimary Container Host2 Core4 GB50 GB

Before you start

  1. The refusal, and what it names

    The default threshold is the traditional Unix one:

    net.ipv4.ip_unprivileged_port_start = 1024

    So -p 80:80 fails:

    Error: pasta failed with exit code 1:
    Listen failed for HOST TCP port */80: Permission denied
    Couldn't listen on requested TCP ports

    Read who is complaining: pasta, the userspace network process from guide 53. Publishing a port rootless means pasta opens a listening socket as your user, and your user may not bind below 1024. The container never started; nothing about the image or nginx is involved.

    -p 8080:80 works immediately, and podman port confirms the mapping. This is why every guide on this path publishes a high port.

    bash Example session
    sysctl net.ipv4.ip_unprivileged_port_startnet.ipv4.ip_unprivileged_port_start = 1024podman run -d --name low -p 80:80 docker.io/library/nginx:alpineError: pasta failed with exit code 1:Listen failed for HOST TCP port */80: Permission deniedCouldn't listen on requested TCP ports [exit 126]podman run -d --name high -p 8080:80 docker.io/library/nginx:alpinec7cfb4b643d51ebe0a6fecfe1ac7d1f20b068721c2bdbd926fac41edfe3512a7podman port high80/tcp -> 0.0.0.0:8080podman rm -f highhigh

    Expected resultA threshold of 1024, a pasta permission error on port 80, and a working 8080.

    Success conditionYou have the exact error and know which process produced it.

  2. A failed run still leaves a container

    Worth knowing before the next step, because it cost this guide a capture.

    The -p 80:80 run failed - but it failed after creating the container's storage and before starting it. So a container named low exists, in created state, and the obvious retry gives you:

    Error: creating container storage: the container name "low" is already in use

    Which looks like a completely different problem and sends people looking in the wrong place.

    The general lesson: podman run is not atomic. A failure part-way through can leave a container behind, so podman ps -a is worth a look after any failed run. --replace avoids the collision, and is what the next step uses.

    bash Example session
    podman ps -a --format 'table {{.Names}} {{.Status}}'NAMES       STATUS

    Expected resultAn empty list, after the leftover container was removed.

    Success conditionYou know to check for a leftover container after a failed run.

  3. Lower the threshold, and decide whether you should

    One sysctl, and -p 80:80 works - podman port shows 0.0.0.0:80 and curl returns http=200:

    sudo sysctl net.ipv4.ip_unprivileged_port_start=80

    Two things about this that matter more than the command.

    It is host-wide. You have not granted Podman anything - you have told the kernel that *every* unprivileged process on this machine may bind ports from 80 up. Any user, any program. On a single-purpose server that is a reasonable trade; on a shared host it is a real reduction in a boundary that exists for a reason.

    It does not persist. sysctl sets the running value only, so a reboot puts it back and your service stops publishing. For anything permanent, a file in /etc/sysctl.d/.

    The alternatives, roughly in order of how much most people should prefer them:

    • Publish a high port and put a reverse proxy in front. The proxy runs as root or has CAP_NET_BIND_SERVICE, and your containers stay unprivileged.
    • A rootful container, which has the capability already - at the cost of everything in guide 3.
    • setcap CAP_NET_BIND_SERVICE on the pasta binary, which is narrower than the sysctl but a package upgrade silently removes it.
    • The sysctl, which is the simplest and the bluntest.

    The threshold was returned to 1024 after this capture.

    bash Example session
    sudo -n sysctl net.ipv4.ip_unprivileged_port_start=80net.ipv4.ip_unprivileged_port_start = 80podman run -d --replace --name low -p 80:80 docker.io/library/nginx:alpinea3d56c6e60581e33065eff8a21ff355bf89bb2ae9f9d3da8c45c2fb30873caaapodman port low80/tcp -> 0.0.0.0:80curl -s -o /dev/null -w 'http=%{http_code}\n' http://localhost:80http=200podman rm -f lowlowsudo -n sysctl net.ipv4.ip_unprivileged_port_start=1024net.ipv4.ip_unprivileged_port_start = 1024

    Expected resultThe threshold at 80, a container publishing port 80, http=200, and the threshold restored.

    Success conditionYou published a privileged port rootless and put the setting back.

Troubleshooting

Official sources