# Stopping nipe removes every departure rule, including yours

> A Perl engine that routes a machine's traffic through Tor using iptables and ip6tables. It blocks all non-local UDP and ICMP, requires root, and its own documentation admits that stopping it removes all departure rules without distinguishing Nipe's from pre-existing ones.

**htrgouvea/nipe** — An engine to make Tor network your default gateway

- Repository: https://github.com/htrgouvea/nipe
- Website: https://heitorgouvea.me/
- Stars: 2,396 · Forks: 342
- Language: Perl
- License: NOASSERTION
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/htrgouvea-nipe

## Stopping removes all departure rules, not only the ones nipe added

This is the first thing to read, and it is stated in the summary rather than buried.

Nipe applies redirection rules with `iptables` for IPv4 and `ip6tables` for IPv6, so the whole machine's outbound traffic is redirected into Tor. Two consequences follow, and the documentation gives both.

The first is conflict on the way in. If rules are already applied to those utilities, the page says conflicts may occur during the start process.

The second is the destructive one. It says that when you stop the Nipe services, all departure rules are removed, not differentiating between the already existing ones and the Nipe rules.

There is no namespacing, no marker chain, and no record of which rules were pre-existing. Anything in the OUTPUT chain when Nipe stops goes with it. That is a different failure mode from a bad routing decision, because it leaves the machine with no outbound filtering rather than with the wrong route.

It also reaches further than `stop`. The command table defines `restart` as restarting the Nipe circuit, and a restart stops before it starts, so it inherits the same wipe. If you keep existing firewall state anywhere on the host, back it up before running either.

## Nothing non-local over UDP or ICMP gets out at all

The routing scope is described precisely, and it has two carve-outs in opposite directions.

Traffic destined for local and loopback addresses is not routed through Tor, which is what you want, since sending local traffic out to a relay would break services on the machine itself.

Then there is the other side: all non-local UDP and ICMP traffic is blocked, and the page attributes this to the Tor project rather than to Nipe. That is the harder constraint. Tor relays carry TCP streams, so UDP has nowhere to go, and ICMP is not proxied either.

The practical effect is that while Nipe is running, `ping` does not work to anything off the machine, DNS resolvers that rely on UDP are affected, and any UDP-based protocol in your stack is dead rather than merely anonymized. Voice and video calls, WireGuard, QUIC-based HTTP, and some game traffic are the obvious casualties.

So the tool's guarantee is narrower than the phrase default gateway suggests. TCP egress is covered; UDP and ICMP are not merely unrouted but blocked. If the workload you want to hide is UDP-heavy, this is not a partial solution, it is no solution.

## The container binds Tor's control port to every interface

The image writes its own Tor drop-in configuration file. It creates the include directory and writes two lines:

```dockerfile
RUN mkdir -p /etc/tor/torrc.d \
  && printf "SocksPort 0.0.0.0:9050\nControlPort 0.0.0.0:9061\n" > /etc/tor/torrc.d/nipe.conf
```

Both lines bind to `0.0.0.0`, and both ports are declared in the image with `EXPOSE 9050 9061`.

The second one deserves attention. Tor's control port is not another proxy endpoint; it is the administrative interface through which a client can query and steer the daemon, including stream and circuit state. Binding it to all interfaces inside a container that is also started with full privilege means that anyone who can reach that port on the published interface can drive the Tor process for that container.

There is no authentication line in the file. The usual mitigations are a `HashedControlPassword` entry or restricting the listener to a local socket, and neither appears in the two lines written here.

The Socks port on 9050 is the intended way in, and binding it broadly is how you reach it from outside the container. The control port was not necessary for that, so if you publish ports, publish 9050 and leave 9061 alone.

## The run command asks for full privilege, then adds a capability

The documented container flow is three commands:

```bash
# Building the container
$ docker build -t nipe .

# Setup the Nipe container
$ docker run -d -it --name nipe-container --privileged --cap-add=NET_ADMIN nipe

# Running commands
$ docker exec -it nipe-container ./nipe.pl <your command>
```

`--privileged` and `--cap-add=NET_ADMIN` appear in the same command. A privileged container already receives all capabilities, so the explicit capability grant adds nothing on top of it. What it documents is intent, which is fair enough: the tool does need to write firewall rules.

The cost of that intent is that a privileged container is a container with effectively host-level authority over its own namespaces. Since the container is doing network manipulation by design, that is harder to avoid than for a typical workload, but it is worth being deliberate about whether the host is one you would mind.

The image entry point is `/bin/bash`, so `docker run` gives you a shell and nothing else happens automatically. Every operation goes through `docker exec`, which is why the third command is part of the flow rather than an afterthought. Nipe also must be run as root outside a container, and the install step is `perl nipe.pl install`.

So the operational shape is: build, start a privileged container, then drive a Perl script inside it for each operation.

## The image skips dependency tests and ships a build library

The base is `debian:bookworm-slim` with the source copied into `/usr/src/nipe`. The dependency step installs Perl modules from the system package manager, then runs cpanm:

```dockerfile
RUN cpanm --notest --installdeps .
```

`--notest` tells cpanm not to run the test suites of the distributions it installs. That is the normal choice for a fast image build and it does mean a dependency that is broken in a way its tests would catch gets installed anyway.

The apt list is worth reading for what it includes beyond the essentials: `bash`, `ca-certificates`, `cpanminus`, `tor`, and `iptables`, plus three build-oriented packages, `libssl-dev`, `libnet-ssleay-perl`, and `libcrypt-ssleay-perl`.

`libssl-dev` is a compiler-grade development package. It is there because the Perl SSL bindings compile against it, so it is a build dependency that stays in the final image rather than being dropped in a multi-stage build.

The two Perl SSL packages are worth a note for Perl users specifically. The canonical distribution names are spelled `Net::SSLeay` and `Crypt::SSLeay` with a capital L, and the lowercase spellings installed here are the older packages. If you are debugging an SSL error inside the container, that difference in spelling is a reasonable first thing to check.

The `chmod +x nipe.pl` on the script is the last build step before the entry point.

## An empty Demo heading and badges with no link text

The README has a section titled Demo and nothing under it. The heading is followed by a horizontal rule and then the next heading, so whatever demonstration belonged there was never written.

The badge block at the top has a related problem. It is a `<p align="center">` element nested inside another `<p align="center">` element, which is invalid nesting, and the two anchor elements for the license and the releases page contain no text at all between their tags, because the images inside them are gone.

So the top of the page renders two invisible links, one of which points at `/releases` for a repository that has no GitHub releases.

The same relative-path style runs through the rest of the page. The license, the contribution guidelines, and the security policy are all linked as root-absolute paths: `/LICENSE.md`, `/.github/CONTRIBUTING.md`, and `/SECURITY.md`. Those resolve on the hosting site and break anywhere else, so a local clone, a mirror, or a render through another tool loses them.

The license itself is worth a note for the same reason the metadata is odd. There is a `LICENSE.md` in the tree and the README says the work is licensed under the MIT License, while the repository's own license field is left unasserted. The file and the page agree with each other, so the terms are clear enough to rely on.

## Five commands, one entry script, and a static analysis config

The whole command surface is five verbs: `install` to install dependencies, `start` to start routing, `stop` to stop routing, `restart` to restart the Nipe circuit, and `status` to see status. All are invoked as `perl nipe.pl <command>`, and the page notes in passing that Nipe must be run as root.

`restart` is described as restarting the circuit, which is a narrower promise than restarting the routing, and as noted it passes through `stop` on the way.

The code layout follows the same minimalism. The entry point is a single script, `nipe.pl`, with the library code in `lib/`, dependencies declared in a `cpanfile` that `cpanm --installdeps .` reads, and documentation in `docs/`.

Standards tooling is present but quiet. There is a `.perlcriticrc`, so Perl::Critic runs as a static analysis pass, and an `.editorconfig` and a `.configs/` directory alongside it. A `SECURITY.md` sits at the root for responsible disclosure, separate from the `.github/` directory that holds the contribution guidelines.

So the project is small enough to read in full, which is the strongest argument for it: if the rule-removal behavior is a problem for your host, you can see exactly what it does rather than having to infer it from behavior.

## Conclusion

Use nipe on a machine whose firewall you own outright, and not on a host where other rules matter, because that is the finding that should gate your decision. The documentation states plainly that stopping the services removes all departure rules without distinguishing Nipe's from rules that were already there, and `restart` goes through the same stop path. On a laptop that is a nuisance; on a server with a hand-written OUTPUT chain it is a security change you did not ask for. Two more things to know before you deploy it. All non-local UDP and ICMP is blocked, so no ping and no UDP-based traffic leaves the machine while it runs. And the container writes a Tor drop-in config that binds the control port to every interface with no password line, so do not publish that port. If you need a system-wide Tor default gateway and can rebuild the firewall from scratch, this is a small, readable Perl codebase. If you need either of those things without giving up existing firewall state, it is the wrong tool.

## FAQ

### What does the nipe engine actually do?

It is a Perl engine that makes the Tor network the default gateway for a machine, routing traffic out through Tor by applying redirection rules with iptables for IPv4 and ip6tables for IPv6. Traffic destined for local and loopback addresses is left off Tor.

### Does stopping nipe remove firewall rules I already had?

Yes, according to its documentation. It states that when the Nipe services stop, all departure rules are removed, without differentiating between the already existing ones and the Nipe rules.

### What traffic can nipe not route through Tor?

All non-local UDP and ICMP traffic is blocked, which the page attributes to the Tor project. That means ping and UDP-based protocols do not work while it is running. Traffic to local and loopback addresses is also not routed through Tor.

### How do I run nipe in a Docker container?

Build with `docker build -t nipe .`, run with `docker run -d -it --name nipe-container --privileged --cap-add=NET_ADMIN nipe`, and execute commands with `docker exec -it nipe-container ./nipe.pl <your command>`. The image entry point is /bin/bash, so nothing runs automatically.

### Which commands does nipe.pl support?

Five: `install` for dependencies, `start` to start routing, `stop` to stop routing, `restart` to restart the Nipe circuit, and `status`. Nipe must be run as root, and dependencies are installed with `cpanm --installdeps .`.

## Sources

- [htrgouvea/nipe on GitHub](https://github.com/htrgouvea/nipe)
- [Issues](https://github.com/htrgouvea/nipe/issues)
- [Project website](https://heitorgouvea.me/)
- [README](https://github.com/htrgouvea/nipe/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/htrgouvea-nipe
