Offering an onion address

On a monitored network (a workplace, a school), a visit to OpenWhistle can be seen even though the report stays private. Tor Browser hides the visit too. Run a Tor hidden service on the same host, aimed at the onion-only nginx entry on 127.0.0.1:8080. It is published there only (the ports: entry in docker-compose.prod.yml).

bash
# 1. Install tor on the host, then add to /etc/tor/torrc:
HiddenServiceDir /var/lib/tor/openwhistle/
HiddenServicePort 80 127.0.0.1:8080

# 2. Restart tor, then read the generated address:
$ systemctl restart tor
$ cat /var/lib/tor/openwhistle/hostname
abc...xyz.onion

# 3. Set ONION_LOCATION in .env and restart the app:
ONION_LOCATION=http://abc...xyz.onion
$ docker compose up -d app

With ONION_LOCATION set:

  • Every HTML response carries an Onion-Location header; Tor Browser offers to switch.
  • The submit page shows the address, except to a visitor already on the onion service.
  • A request through the onion listener gets no HSTS and no Secure cookie flag. That connection was never TLS (Tor encrypts it), and a browser can refuse a Secure cookie over plain HTTP.
The app trusts nginx, not the client, to say "this is the onion listener"

Never the Host header: any client on the HTTPS listener could send Host: <anything>.onion. Instead the bundled nginx/nginx.conf sets X-OW-Onion: 1 only in the onion (port 8080) server block and clears it in the regular (443) one. It strips incoming IP-forwarding headers the same way.

So the app's own port (4009) must stay reachable only through this nginx, as IP stripping already requires. A deployment that exposes the app directly, or through another proxy, must set or clear X-OW-Onion itself. Otherwise neither protection holds.

Without ONION_LOCATION the app ignores X-OW-Onion, so a path that forwards it from the client, such as the Helm chart's ingress, is safe. With ONION_LOCATION behind ingress-nginx, clear it there with the annotation nginx.ingress.kubernetes.io/configuration-snippet: proxy_set_header X-OW-Onion ""; (the controller needs allow-snippet-annotations).

/var/lib/tor/openwhistle/ holds the address's private key

hs_ed25519_secret_key in that directory is the onion address. Lose it and the address changes for good, breaking every link a reporter was given. Whoever holds it can impersonate the service.

  • Back the directory up before any host migration or volume change.
  • Keep it root/tor-only: Tor sets 0700 on creation, but a bind-mount or a backup step can widen that. Check it.
  • Never commit or publish it, least of all wherever this how-to is shared.
Rate limiting on the onion listener

HiddenServicePort forwards every Tor visitor to nginx from 127.0.0.1, so per-IP limits cannot tell visitors apart. The onion server block therefore has its own zone, sized for several concurrent reporters. Heavy concurrent use still shares that one budget and can see a 429. If that happens, raise the zone's rate/burst in nginx/nginx.conf.

Edit this page on GitHub