Correct way to run a simple HTTP server (forwarding port 80 to a higher port)

Hi all,
I’m trying to run a simple HTTP server on my NixOS machine. I understand that port 80 is reserved for root and it doesn’t seem like the convention is to bind to port 80 directly. The internet says to either a) run a reverse proxy (i.e. nginx) to redirect traffic from port 80 to a higher port or b) use an iptables rule to forward port 80 to a higher port.

Most answers create an iptables rule imperatively, but it seems like there’s a way to do this in a declarative way in the firewall configuration. It also seems like iptables is deprecated? not the convention? I am not really sure.

I found this discourse that seems to describe a recent PR replacing iptables with nftables in the NixOS firewall. I also tried following the wiki guide on port forwarding to forward TCP traffic on port 80 to port 8080.

Here’s my full networking configuration:

  networking = {
    firewall = {

      allowedTCPPorts = [ 
        80          # http 
      ];

      allowedUDPPorts = [ 
      ];
    };

    # stuff for web server test
    nftables = {
      enable = true;
      ruleset = ''
        table ip nat {
          chain PREROUTING {
            type nat hook prerouting priority dstnat; policy accept;
            tcp dport 80 dnat to :8080
          }
        }
      '';
    };

    nat = {
      enable = true;
      externalInterface = "wlp5s0";
      forwardPorts = [
        {
          sourcePort = 80;
          proto = "tcp";
          destination = "x.x.x.x:8080";
        }
      ];
    };
  };

where x.x.x.x is my local IPv4 address, and wlp5s0 is my external interface as reported by ifconfig. This configuration doesn’t seem to work. I can access the web application on 0.0.0.0:8080 and x.x.x.x:8080 (when binding the server to 0.0.0.0:8080), but HTTP requests don’t seem to go through on port 80 (i.e. curling the web server on 0.0.0.0:80, x.x.x.x:80, or typing in my local IP address in the browser).

I am sure I’m missing something obvious, but I haven’t been able to piece together a solution yet. I don’t have the best understanding of networking as a whole. Thanks in advance.

I run the following command on startup (although I’m not sure why you wouldn’t just bind port 80 directly, but I understand that you don’t want to; in my case it’s for local reasons):

ssl-proxy -redirectHTTP -from 0.0.0.0:443 -to 127.0.0.1:9711 -domain=whatever.com

and that gets you both HTTP at port 80 and HTTPS at port 443, showing whatever you run on port 9711.

Is binding to port 80 the normal practice? Seems like you can only do it as root user, seems wrong to me… Is there a way to do exactly what you’re doing declaratively?

See also https://discourse.nixos.org/t/what-is-the-correct-way-to-allow-binding-of-port-80-and-443

I recommend setting boot.kernel.sysctl."net.ipv4.ip_unprivileged_port_start" = 0; .

Advantage: You can simply bind your web server process to port 80 without giving it any additional privileges (which can also lead to security issues) or doing any forwarding.

Disadvantage: Normal users can now also bind to that port. This is only a problem if there are untrusted users on that machine, i.e., users that might want to block your webserver by occupying that port before your webserver starts.

See also https://ar.al/2022/08/30/dear-linux-privileged-ports-must-die/