For historical reasons there is a restriction in most Linux distributions, even in most *nix operating systems, that requires a process to have root privileges to be able to open a listening socket on ports below 1024. The idea made sense back when a machine had many untrusted users, but on my own dev box every user is me anyway.
I keep hitting this when I run a dev server, or a rootless container, as my own user. The service wants port 80 or 443, starts as an ordinary user, and gets EPERM on bind().
The kernel decides all this with the net.ipv4.ip_unprivileged_port_start sysctl. By default it is set to 1024, and lowering it (all the way to 0) is all that is needed. There is a way to reconfigure this:
| |
Dropping the file into /etc/sysctl.d/ makes the change survive a reboot, and sysctl --system applies it right away.
To verify, read the value back and then start a listener on a privileged port as an ordinary user:
| |
A plain python3 process binding port 80 without root — that proves it works.
The per-binary alternative
If opening up every port for every user feels too blunt, grant the capability cap_net_bind_service to just the binary in question:
| |
Two caveats to keep in mind. The capability is stored as an extended attribute on the binary itself, so it silently disappears whenever a package upgrade replaces the file. And it has no effect on interpreted scripts, since the capability would have to be set on the interpreter instead. When the process runs under systemd it can be granted in the unit file instead with AmbientCapabilities=CAP_NET_BIND_SERVICE, which also survives upgrades.
Wrapping up
Lowering ip_unprivileged_port_start to 0 removed the privileged-port restriction machine-wide with two commands, and a regular user can now run services on port 80 and 443. If that is broader than you need, reach for setcap (or ambient capabilities) instead.