Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for Podman By James Joyner IV · · 9 min read Last reviewed Jul 2026

Podman Error: 'bind: permission denied' Publishing a Privileged Port Rootless

Quick answer

Fix rootless Podman's 'bind: permission denied' on ports below 1024: tune net.ipv4.ip_unprivileged_port_start, grant CAP_NET_BIND_SERVICE, or redirect from a high port.

  • #podman
  • #containers
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this Podman error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Exact Error Message

$ podman run -d -p 80:80 docker.io/library/nginx:alpine
Error: rootlessport cannot expose privileged port 80, you can add
'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf (currently 1024),
or choose a larger port number (>= 1024): listen tcp 0.0.0.0:80:
bind: permission denied

Older or differently-configured setups surface the raw bind failure instead:

Error: cannot listen on the TCP port: listen tcp4 0.0.0.0:443: bind: permission denied

What It Means

On Linux, TCP and UDP ports below 1024 are reserved. Historically only processes running as UID 0 could bind them; today the boundary is controlled by the net.ipv4.ip_unprivileged_port_start sysctl, which defaults to 1024, and by the CAP_NET_BIND_SERVICE capability. Rootless Podman runs entirely as your unprivileged user, so the process that actually opens the host-side listening socket has neither root nor that capability by default. The bind is refused with EACCES before any container even starts.

The important detail is which process does the binding. In rootless mode Podman does not publish ports from inside the container’s network namespace — it runs a helper on the host (rootlessport, backed by pasta or slirp4netns for the namespace’s outbound traffic) that listens on the host port and forwards into the container. That helper inherits your user’s privileges. So granting a capability to the container, or setting --privileged, changes nothing: the restriction applies to the host-side listener owned by your login user. This also explains why the identical podman run command succeeds under sudo — the rootful path binds as root and is unaffected by the unprivileged port floor.

Common Causes

  • Publishing a well-known port (-p 80:80, -p 443:443, -p 53:53) as a non-root user with the default sysctl of 1024.
  • Copying a docker run line from documentation that assumed a root-owned Docker daemon.
  • A Quadlet or systemd user unit inheriting a PublishPort=80 from a rootful example.
  • net.ipv4.ip_unprivileged_port_start lowered interactively with sysctl -w but never persisted, so it reverts at reboot.
  • A container image whose entrypoint binds a low port inside the namespace without CAP_NET_BIND_SERVICE in its bounding set.
  • Another process already holds the port and the resulting error is misread as a privilege problem.

Diagnostic Commands

Check the current unprivileged port floor and whether it is persisted:

sysctl net.ipv4.ip_unprivileged_port_start
grep -r ip_unprivileged_port_start /etc/sysctl.conf /etc/sysctl.d/ 2>/dev/null

Confirm you are actually running rootless and which port-forwarding helper is in use:

podman info --format '{{.Host.Security.Rootless}}'
podman info --format '{{.Host.NetworkBackend}}'
id -u

Rule out a genuine port conflict before changing any sysctls:

ss -tulpn | grep -E ':(80|443)\b'

Inspect the capabilities the container process would receive:

podman run --rm docker.io/library/alpine:latest sh -c 'grep Cap /proc/1/status'

Reproduce the bind outside Podman to prove it is a host-level restriction:

python3 -c "import socket;s=socket.socket();s.bind(('0.0.0.0',80))"

Step-by-Step Resolution

  1. Decide the lowest port your users genuinely need. Lowering the floor to 80 permits any local user to bind 80 and above; lowering it to 1 removes the protection entirely. Test the change first:
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80
podman run -d -p 80:80 docker.io/library/nginx:alpine
  1. Persist it so it survives a reboot. Use a dedicated drop-in file rather than editing /etc/sysctl.conf:
echo 'net.ipv4.ip_unprivileged_port_start=80' | \
  sudo tee /etc/sysctl.d/99-podman-rootless-ports.conf
sudo sysctl --system
sysctl net.ipv4.ip_unprivileged_port_start
  1. If you would rather not change a global sysctl, grant CAP_NET_BIND_SERVICE to the Podman binary itself so only Podman-launched listeners are affected:
sudo setcap cap_net_bind_service=+ep "$(command -v podman)"
getcap "$(command -v podman)"

Note that file capabilities are lost on package upgrade, so re-apply them or manage them with a configuration-management rule.

  1. The safest pattern is to publish to a high port and redirect at the firewall. With nftables:
podman run -d -p 8080:80 docker.io/library/nginx:alpine
sudo nft add table ip nat
sudo nft 'add chain ip nat prerouting { type nat hook prerouting priority -100 ; }'
sudo nft add rule ip nat prerouting tcp dport 80 redirect to :8080

Or with firewalld, which persists the rule for you:

sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080
sudo firewall-cmd --reload
  1. If the failure is inside the container — the host bind succeeded but the entrypoint cannot open the low port in its own namespace — add the capability to the container instead of the host:
podman run -d --cap-add=NET_BIND_SERVICE -p 8080:80 my-image:latest
  1. For Quadlet-managed services, encode the high-port publish in the unit so the behaviour is reproducible:
[Container]
Image=docker.io/library/nginx:alpine
PublishPort=8080:80

[Service]
Restart=always

[Install]
WantedBy=default.target

Reload and start it as a user unit:

systemctl --user daemon-reload
systemctl --user start nginx.service

If the unit itself refuses to come up rather than failing on the bind, see Podman error: Quadlet unit failed to start. And if publishing works but the container has no connectivity at all, the rootless network stack is the more likely culprit — see Podman error: netavark IO error.

Prevention

  • Standardise on high ports (8080/8443) for rootless workloads and terminate 80/443 at a reverse proxy or the firewall.
  • Ship net.ipv4.ip_unprivileged_port_start as a managed /etc/sysctl.d/ drop-in so hosts do not drift.
  • Prefer a firewall redirect over lowering the sysctl to 1, which lets any local user impersonate a well-known service.
  • Re-apply setcap in your package-upgrade automation, since capabilities are cleared when the binary is replaced.
  • Keep rootless and rootful examples clearly separated in internal docs so -p 80:80 is never copied into a user unit.
  • Check ss -tulpn in your deploy scripts to distinguish a conflict from a privilege denial up front.
  • bind: address already in use — the port is occupied, not restricted; find the holder with ss -tulpn.
  • rootlessport cannot expose privileged port — the same restriction reported by the helper rather than the raw syscall.
  • Error: cannot setup namespace using "/usr/bin/newuidmap" — a user-namespace setup failure, covered in Podman error: newuidmap exit status 1.
  • slirp4netns failed / pasta failed — the rootless network helper could not start, which blocks all publishing regardless of port number.

Frequently Asked Questions

Why doesn’t --privileged fix this? --privileged affects the container’s own capability set, but the socket on port 80 is opened by the rootlessport helper running on the host as your unprivileged user. No container-side flag can grant that host process the right to bind a reserved port.

Is lowering ip_unprivileged_port_start a security risk? It is a real trade-off. Setting it to 80 means any local user can bind 80–1023, so a compromised account could squat on a well-known port and impersonate a service. On a single-admin host that is usually acceptable; on a shared host, prefer the firewall-redirect approach.

Does setcap on the podman binary work for every case? It works for the host-side listener, which is the common failure. It does not help when the containerised process itself needs CAP_NET_BIND_SERVICE inside its namespace — use --cap-add=NET_BIND_SERVICE for that. Remember the capability is wiped on upgrade.

Why does it work under sudo podman but not rootless? Rootful Podman binds as root and is exempt from the unprivileged port floor entirely. That is not a fix so much as a different, more privileged execution model — the containers it creates are root-owned and invisible to your rootless Podman. For more rootless container fixes, see the Podman guides.

Free download · 368-page PDF

Fixed it? Get 500 Podman & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.