How to temporarily open a TCP port in NixOS?

I’m looking for a way to temporarily open a port in NixOS. I want to test a web app I develop on my mobile phone. The app is served from my laptop using miniserve, like this:

miniserve --index=index.html dist

I can access it from the laptop at http://localhost:8080/ and the IP address of the WIFI, i.e. http://192.168.1.56:8080/. But the same address on my phone doesn’t respond. I’m pretty sure it’s because the firewall settings on my NixOS laptop.

I could open the ports in /etc/nixos/configuration.nix and run sudo nixos-rebuild switch, but that’s not convenient and also not secure (I would most likely forget to close the port later). Is there a way to temporarily open a port from the command line? Preferably without super user access. Perfect solution would be to integrate it with Direnv, but I’ll appreciate anything more convenient than nixos-rebuild switch.

7 Likes

Use iptables to add or remove firewall rules imperitively. I’m fairly certain NixOS doesn’t automatically save/restore iptables rules, so any rules you create this way will not persist across reboots.

  • List firewall rules: sudo iptables -L
  • Open port 8080: sudo iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
  • Identify firewall rule number: sudo iptables -L INPUT --line-numbers
  • Remove firewall rule: sudo iptables -D INPUT <RULE_NUM>

I know of no method to alter firewall rules without root privileges.

2 Likes

If you’re trying to avoid typing in a password just to enable or disable the firewall rule, you can use a script to temporarily enable the port.

# file: ~/bin/run-with-port

#!/usr/bin/env bash
# Usage: sudo run-with-port <port> <cmd> <args...>

set -ueo pipefail

open-port() {
  local port=$1
  iptables -A INPUT -p tcp --dport $port -j ACCEPT
}

close-port() {
  local port=${1:-0}
  iptables -D INPUT -p tcp --dport $port -j ACCEPT
}


if [[ -z "$1" ]]; then
  echo "Port not given" >&2
  exit 1
fi

PORT=$1
shift;  # Drop port argument

if [[ 0 -eq $# ]]; then
  echo "No command given" >&2
  exit 1
fi

open-port $PORT

# Ensure port closes if error occurs.
trap "close-port $PORT" EXIT

# Run the command as user, not root.
runuser -u $SUDO_USER -- "$@"

# Trap will close port.

I just wrote this, tested that it opens and closes the port, but not much more thoroughly than that.

You can update your sudoer config to allow running the run-with-port script without a password, and you now have:

  1. No need to type password to open port.
  2. Can run any single command while port is open.
  3. Port is closed after command stops, even if command errors.
4 Likes

The much easier option: pick a port range (10100 to 10110 as an example) and open that in the firewall from your configuration.nix with a nice comment explaining why. When you are working on a project that requires external access, have it listen on one (or more) of the ports in the range. There is no harm leaving a port open in the firewall if there is nothing listening.

3 Likes

Thanks. Thinking about it a little more I realized that important aspect is easy collaboration with other developers. I want the development environment to be automatically reproduced with with as much detail as possible. That’s one of the big advantages of using Nix. So asking people to change global system configuration is not appealing.

But I guess it’s a safe assumption that every developer will be a /sudoer/ on their development computer. So using sudo in a script should be fine.

Is there a sudo-less solution for other distros?
Just to be clear, http://192.168.1.56:8080/ is not accessible from your phone (which is on the same WiFi network)? This workflow works in Ubuntu and Windows at least…

AFAIK Ubuntu desktop simply has no firewall enabled by default. That’s why your workflow works out of the box.

I think your best bet for convenience is allowing all traffic from the local network.

3 Likes

Another option might be to set up tailscale, and set tailscale0 as a trusted interface. I haven’t tested this, but I think it should work. I just have certain common ports whitelisted in my firewall settings in configuration.nix.

5 Likes

after opening port 8080, you need reload firewall service to take effect

sudo iptables -A INPUT -p tcp --dport 8080 -j ACCEPT

systemctl reload firewall

2 Likes

A little trick I use: you define a firewall rule that allows traffic to TCP ports listed in an ipset and similarly for UDP.

By default the sets are empty, but you can then use the ipset command to dynamically add ports to either set as required without having to reload your full set of firewall rules.

2 Likes

So here do you mean that you just pass nixos a config like

networking.firewall.allowedTCPPorts = [];

…and then you use ipset to set them dynamically?

That specific option only deals with ports.

You need to use networking.firewall.extraCommands and set the rule in there. There’s a brief example here: https://serverfault.com/a/946207

3 Likes

Thanks I should have realized this

I could not make your script work on my NixOS. I have modified it like so:

#!/usr/bin/env bash
# Usage: sudo run-with-port <port> <cmd> <args...>

set -ueo pipefail

ip46tables() {
  iptables "$@"
  ip6tables "$@"
}

open-port() {
  local port=$1
  ip46tables -I nixos-fw 1 -p tcp --dport $port -j nixos-fw-accept
}

close-port() {
  local port=${1:-0}
  ip46tables -D nixos-fw -p tcp --dport $port -j nixos-fw-accept
}

if [[ -z "$1" ]]; then
  echo "Port not given" >&2
  exit 1
fi

PORT=$1
shift;  # Drop port argument

if [[ 0 -eq $# ]]; then
  echo "No command given" >&2
  exit 1
fi

open-port $PORT

# Ensure port closes if error occurs.
trap "close-port $PORT" EXIT

# Run the command as user, not root.
runuser -u $SUDO_USER -- "$@"

# Trap will close port.

You need to show what your tables look like with iptables -L after you’ve added a port for anyone to be able to comment on what’s going on. But the easiest workaround the issue if the port doesn’t matter, is just to pre-allow some random high range and then pick a port from there when you need something exposed.

from NixOS 24.05 manual | Nix & NixOS

“The iptables firewall module now installs the nixos-firewall-tool which allows the user to easily temporarily open ports through the firewall.”

usage looks like this:

nixos-firewall-tool

Can temporarily manipulate the NixOS firewall

Open TCP port:
 nixos-firewall-tool open tcp 8888

Show all firewall rules:
 nixos-firewall-tool show

Open UDP port:
 nixos-firewall-tool open udp 51820

Reset firewall configuration to system settings:
 nixos-firewall-tool reset
14 Likes