Open-source project
cmars/onionpipe avatar
cmars/onionpipe

onionpipe: forwarding local ports to Tor onion addresses

Onion addresses for anything.

618 stars37 forksGoMIT

At a glance

What is it?
onionpipe is a Go tool that sets up socket forwarding tunnels between local ports and Tor onion addresses, in a syntax borrowed from socat, with client auth for private services and a container image for deployment. It does the plumbing well and leaves the configuration story unfinished, since a YAML config file is still listed as planned.
Who is it for?
onionpipe suits a homelab operator or developer who wants to publish a service over Tor without a public IP address, and who is comfortable with a socat-like forwarding syntax and a tor daemon on the host. It is a poor fit for anyone who needs declarative configuration or a supported release stream, since a YAML config file is still a planned feature and the most recent tag is v1.1.11 from 2024-12-19.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A forwarding syntax borrowed from socat

onionpipe sets up socket forwarding tunnels. The project compares itself to `socat(1)`, for onions, which is the clearest way to understand the design: the arguments describe a source and a destination, and the tool arranges for traffic to move between them through the Tor network.

The basic form takes a local port and exports it to a temporary onion address:

bash
onionpipe 8000

Adding a destination port uses `~` as shorthand between source and destination:

bash
onionpipe 8000~80

And a nickname after `@` turns the temporary address into a persistent one that keeps the same identity across restarts:

bash
onionpipe 8000~80@my-app

Nicknames can be reused in several forwarding expressions, which is how one address can serve two services. The example given is a web forum for a Minecraft server, where the forum and the game port share one nickname:

bash
onionpipe 8000~80@minecraft 25565@minecraft

Forwarding expressions without a nickname all use the same temporary address, and a single expression can carry several ports:

bash
onionpipe 192.168.1.100:8000~80,8080,9000 9090

UNIX sockets work as sources too, which matters for services that were never meant to speak TCP:

bash
onionpipe /run/server.sock~80

The whole expression grammar is compact enough to learn in a minute, and it is the reason the project argues it is more convenient than configuring Tor's own hidden service files by hand.

Anonymity is a flag you can trade away

Every onion address is anonymous by default, in the sense that the service does not need to know who is connecting. The tool also offers a way to step back from that:

bash
onionpipe --anonymous=false 8000

The documentation describes this as exporting to a non-anonymous remote onion service, and frames the trade explicitly: network privacy is exchanged for possibly reduced latency. That is the real difference between a v3 onion service, where the client verifies the service key, and an older style service whose address is tied to a public key without the same verification step, so the connection is faster but the client cannot check it is talking to the party it expects.

That flag is worth understanding before you use it on anything that carries credentials. The default is the safer choice, and the latency argument is real but it applies mostly to interactive traffic rather than to bulk transfers, where Tor's throughput characteristics matter more than the round trip time.

The documentation also shows that onionpipe starts its own tor process, with a log line announcing "starting tor..." followed by the mapping it has created. Anything that stops that tor process stops your tunnels, which matters when you are deciding where the process should live in your supervision setup.

Importing onion services back to a local interface

The direction reverses cleanly. Importing a remote onion's port 80 to localhost port 80:

bash
onionpipe xxx.onion:80

Binding the local side to every interface creates an ingress for that onion on your public network:

bash
onionpipe xxx.onion:80~0.0.0.0:80

This second form is how you put a Tor service in front of something else, or how you reach an onion service from a machine that has no Tor client of its own.

Containers are supported, with one caveat the documentation calls out: it is the container forwarding that applies, so local addresses have to be adjusted accordingly. Forwarding port 80 on the Docker host looks like this:

bash
docker run --rm ghcr.io/cmars/onionpipe:main host.docker.internal:80

With Podman, exposing the host network is the alternative. A detail that makes this practical in orchestrated environments is that local forwarding addresses are DNS resolved, which makes it easy to publish hidden services from inside Docker Compose or Kubernetes. The repository ships a nextcloud example under `examples/nextcloud` with a compose file, and the guidance is to watch the log for the onion address the run produces.

The practical pattern here is that the onion address is discovered at runtime rather than configured in advance, which is what makes it fit a compose or Kubernetes deployment where the address would otherwise change on every restart.

Client auth turns a hidden service into a private one

Onion services are hidden, which is not the same as private: by default anyone with the address can connect. The project points at the Tor community documentation on client auth, and implements the same idea.

The first step creates a client auth key pair and prints the public part as JSON:

bash
onionpipe client new alice
json
{
  "alice": {
    "identity": "p2pof7vumwsrqqavtovfwqqaw6cqzvtqqe7cjvxt754k6j7blufa"
  }
}

That public identity is shared with the person who should be allowed in, and used to create a service only that client can use:

bash
onionpipe --require-auth p2pof7vumwsrqqavtovfwqqaw6cqzvtqqe7cjvxt754k6j7blufa 8000~80@test

The log then shows the generated address and the mapping it created:

bash
2022/02/13 21:25:46 starting tor...
127.0.0.1:8000 => sd6aq2r6jvuoeisrudq7jbqufjh6nck5buuzjmgalicgwrobgfj4lkqd.onion:80

The client side uses the matching private key, which the client already holds from the `client new` step:

bash
onionpipe --auth alice sd6aq2r6jvuoeisrudq7jbqufjh6nck5buuzjmgalicgwrobgfj4lkqd.onion:80~7000

This is the strongest feature in the tool for a personal service, because it removes the need to build your own authentication in front of the application behind the tunnel.

Three ways to get the binary

Installation is documented in three flavours.

The Docker route is the one with the least friction, because each commit into main triggers an automated release that publishes a Docker image. The provided `Dockerfile` builds a minimal image that can run onionpipe in a container with the latest Tor release from the Tor Project, and the build and runtime are Debian-based. The image is built in three stages: a base with the Tor package from the Tor Project apt repository, a Go build stage that runs the test suite with forwarding tests skipped, and a final stage that copies the binary in and runs it as a non-root `onionpipe` user with `/data` as the working volume.

A local build is one make target, and the resulting binary requires a `tor` daemon executable in your `$PATH`. A static standalone binary linked against libtor is also published for Linux and Darwin in the latest release. The documentation says it should theoretically work on Android through gomobile as well, with quirks noted in comments in `tor/init_libtor.go`. Building it locally takes a long time the first time, because it compiles CGO wrappers for Tor and its dependencies, which need the tor, openssl, libevent and zlib C libraries. On NixOS the documentation points at running `nix-shell` in the project directory to get those dependencies into your shell context.

Written in Go, with a YAML config still on the list

The dependency list in `go.mod` tells you how the tool talks to Tor: the module requires `github.com/cretz/bine`, a Go library for controlling a Tor daemon, along with `github.com/urfave/cli/v2` for the command line interface and `github.com/mitchellh/go-homedir` for locating the client key directory. Go 1.22 is the language level. The repository also has a `forwarding` directory, an `app` directory, a `tor` directory, a `secrets` directory and a `config` directory, plus a `Makefile` with targets for the binary, a Docker build, tests, coverage and cleaning.

The planned features section is short and worth reading as a statement of direction. The first item is declaring forwards and operating from a YAML file rather than command line arguments, with the intended invocation shown as `onionpipe --config config.yaml`. Then there is consideration of a fancier TUI, and of a control plane for onionpipe SDN orchestration.

For deployment today that means you must carry the forwarding expressions in whatever supervises the process, such as a systemd unit or a compose command, since the configuration file interface does not exist yet. Anyone planning to run many tunnels through one configuration source is looking at work the project has scheduled but not shipped.

The release picture is the other thing to note. The tagged versions are v1.1.9 and v1.1.10 on 2024-10-15, then v1.1.11 on 2024-12-19, while the last push was on 2026-10-20. The repository is not archived, so work continues, but there has been no release in a long while, which means the container image, which is rebuilt from main on every commit, is the more current artefact to run.

Editorial conclusion

onionpipe suits a homelab operator or developer who wants to publish a service over Tor without a public IP address, and who is comfortable with a socat-like forwarding syntax and a tor daemon on the host. It is a poor fit for anyone who needs declarative configuration or a supported release stream, since a YAML config file is still a planned feature and the most recent tag is v1.1.11 from 2024-12-19. Before you deploy it, read the log output that prints the generated onion address, decide whether you need `--require-auth` for any service, and check that the tor daemon on your host is reachable since the local binary refuses to work without it.

Frequently asked questions

How do I publish a local port as an onion address with onionpipe?

Run `onionpipe 8000` to export localhost port 8000 to a temporary remote onion address. Use `8000~80` to pick a destination port, and add `@my-app` to make the address persistent across restarts.

Does onionpipe need Tor installed on the host?

For a local build, yes: the documentation says the built binary requires a `tor` daemon executable to be in your `$PATH`. The Docker image ships with the latest Tor release from the Tor Project, and a static standalone binary with libtor is published for Linux and Darwin.

Can I restrict an onion service to a specific client?

Yes. Create a client auth key pair with `onionpipe client new alice`, share the printed public identity with the client, and start the service with `--require-auth` and that identity. The client then connects with `--auth alice` using its private key.

What is the difference between onionpipe and socat?

onionpipe is described as being like socat, but for onions: it sets up socket forwarding tunnels where the endpoints are Tor onion addresses rather than plain TCP or UNIX sockets, and it starts a tor process to carry the traffic.

Can onionpipe run inside Docker Compose or Kubernetes?

Yes, because local forwarding addresses are DNS resolved, which makes publishing hidden services from within Docker Compose or Kubernetes straightforward. The repository includes a nextcloud example under `examples/nextcloud`, and the caveat is that container forwarding applies, so local addresses need adjusting.

Official sources

  1. cmars/onionpipe on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cmars-onionpipe.svg)](https://hysenlabs.com/projects/cmars-onionpipe)

Was this useful?