Exposing Privileged Ports with Podman


On Unix-like systems, ports below 1024 are considered privileged, meaning that only the root user can bind to them. Web servers traditionally listen on port 80 for HTTP and port 443 for HTTPS. A key innovation of Podman is not running containers as root to enhance security. This presents challenges when containers need to use privileged ports like 80 or 443 for web server functions.

If you google solutions to this problem there are three common solutions suggested. First, run container as root. Second, use a proxy server like nginx or Caddy running outside a container, as root. Third, redefine where unprivileged ports start to be 80 or below.

Running containers as root to bind directly to privileged ports is the simplest approach but it significantly compromises security. Also, wasn’t getting away from containers require root one of the main reasons to move to podman?

Running a proxy server on the host without any container is what I did at first for docker and podman. It was a good transition from no containers to starting to run some services in containers. Still, that means more stuff that has to be be deployed separately from the containers. If you are going to use containers, it will be nicer to more fully embrace them.

Quite a few posts on various forums recommend redefining where unprivileged ports start to port 80. This means that you no longer need root to run a web server on port 80. It also means that hostile software or users no longer need root to serve on other privileged ports. For the purposes of this post I chose to believe that the people who decided that everything below 1024 is privileged knew what they were doing.

I have come up with a method I think is better for small deploys. Use the host’s or upstream’s firewall capabilities (with tools such as UFW, iptables, or nftables) to redirect traffic from privileged ports to non-privileged ones. This means minimal configuration change to the host OS (possibly none if you can do it with the upstream firewall), and you don’t give up running your containers without root. For now it seems like the best choice. What follows are partial examples for doing it with three firewalls likely to be included in your Linux system.

UFW example:

sudo ufw allow 80/tcp sudo ufw allow 443/tcp

sudo ufw route allow proto tcp from any to any port 8000 sudo ufw route allow proto tcp from any to any port 8443

sudo ufw enable

iptables example:

sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8000 

sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8443

nftables example:

sudo vi /etc/nftables.conf
table ip nat {
    chain prerouting {
        type nat hook prerouting priority 0; policy accept;
        
        # Redirect HTTP (port 80) to port 8000
        tcp dport 80 redirect to 8000
        
        # Redirect HTTPS (port 443) to port 8443
        tcp dport 443 redirect to 8443
    }
}