Files
fips/docs/how-to/deploy-tor-onion.md
Johnathan Corgan 5abf9a9325 docs: four-section /docs/ restructure with new-user content, accuracy pass, and gateway feature-set rewrite
Restructures /docs/ by reader purpose (tutorials, how-to,
reference, design), adds the new-user-progression and
operator-recipe content the prior layout lacked, runs an
accuracy pass against current source across the pre-existing
design docs, and rewrites the gateway feature-set documentation
end-to-end around its actual operational profile (a niche
feature designed for systems already serving DHCP/DNS to a
LAN, with two independent halves — outbound LAN→mesh, inbound
mesh→LAN — sharing one nftables table, one binary, and one
control socket). Top-level README and getting-started rewritten
around two equally-weighted deployment modes (overlay on
existing IP networks; ground-up over non-IP transports).

## Additions

- 11 new tutorials in docs/tutorials/: an 8-step new-user
  progression from single-daemon test-mesh peering through
  to a ground-up two-device mesh, an IPv6-adapter side-trip
  walkthrough, an Advanced Tutorials index, and a hand-held
  OpenWrt walk-through for fips-gateway deployment that
  exercises both halves of the feature.
- 12 new how-tos in docs/how-to/: firewall activation,
  Nostr discovery (resolve / advertise / open across five
  scenarios), Tor onion (directory + control_port modes),
  UDP buffer tuning, unprivileged-user setup, persistent
  identity, host aliases, Bluetooth LE peering, MTU
  diagnostics, manual Linux-host gateway deployment (covers
  both halves), gateway troubleshooting (organised by half),
  and a section index.
- 9 new reference docs in docs/reference/: configuration,
  wire formats, control-socket protocol, four CLI references
  (fips, fipsctl, fipstop, fips-gateway), security posture
  matrix, and Nostr events catalog. Configuration and
  wire-formats are renamed-and-extended from prior design/
  versions; the other seven are net-new.
- 6 new design docs: fips-concepts, fips-architecture, and
  fips-prior-work split out of the deleted fips-intro.md;
  consolidated fips-mmp and fips-mtu aggregations; and a
  new generic port-advertisement-and-nat-traversal doc
  (Nostr-signaled port advertisement plus UDP NAT-traversal
  protocol, FIPS as an example implementation, suitable for
  eventual NIP submission).
- Top-level docs/getting-started.md walking through the
  binary-installer-only Install story.
- packaging/common/hosts pre-populated with the eight public
  test-mesh nodes so shortnames resolve out of the box on
  every fresh install.

## Changes

- 23 wire-format diagrams relocated to reference/diagrams/
  alongside the wire-formats move.
- 4 design diagrams corrected against source code
  (fips-protocol-stack, fips-identity-derivation,
  fips-coordinate-discovery, fips-routing-decision).
- 10 pre-existing design docs reconciled with current
  source. Numeric corrections: stale link-MMP report bounds
  (now [1s, 5s] with 200 ms cold-start floor); UDP default
  MTU (now 1280, IPv6 minimum); node_addr formula
  (SHA-256(pubkey)[..16]); Noise patterns (IK at link, XK
  at session); peer-ACL semantics (strict allowlist requires
  ALL in peers.deny); daemon DNS upstream ([::1]:5354);
  on-the-wire bloom-filter size (1,071 bytes); obsolete
  Cargo-feature references (PR #79 dropped them) removed.
- Transport framing tightened across the docs: TCP is for
  UDP-filtered networks (not NAT traversal); Tor is a
  deployment mode (not failover); WebSocket dropped (not a
  shipped FIPS transport); WiFi promoted to Implemented via
  Ethernet in infrastructure mode; classic-Bluetooth row
  removed (BLE is the only Bluetooth-mode transport).
- docs/design/fips-gateway.md rewritten end-to-end to lead
  with the niche-feature framing and the two-halves
  structure. Title moved from "FIPS Outbound LAN Gateway"
  to "FIPS Gateway"; architecture section describes the
  common machinery (the fips-gateway service, the nftables
  table, the control socket) before splitting into separate
  "Outbound Half" and "Inbound Half" sections of equal
  weight; security considerations split per-half; no Future
  Work section (speculative directions live in the project
  tracker, not in protocol design docs). Inbound port
  forwarding is a first-class half rather than a buried
  "Implemented Extensions" subsection.
- Gateway terminology unified across all gateway docs as a
  separate Linux service running alongside the fips daemon
  (its own systemd unit / OpenWrt init script). Container-
  pattern terms (sidecar) are reserved for the
  Docker/Kubernetes sidecar deployment examples — the
  testing/sidecar/ tree, examples/k8s-sidecar/,
  examples/sidecar-nostr-relay/,
  examples/wireguard-sidecar-macos/, and the related
  CHANGELOG / top-level README entries — where the term
  carries its standard container meaning.
- Net-new design body content: rekey section in
  fips-mesh-layer (Noise IK msg1/msg2 over the established
  link, K-bit cutover, drain window, smaller-NodeAddr-wins
  tie-breaker on dual-init); Mesh Size Estimation and
  Antipoison FPR Cap sections in fips-bloom-filters;
  Mesh-Interface Query Filter subsection in
  fips-ipv6-adapter; failure-suppression knobs and clock-
  skew tolerance in fips-nostr-discovery; loop-rejection
  and mid-chain ancestor swap added to spanning-tree
  propagation / stability rules; Priority Chain in
  fips-mesh-operation renumbered to match the
  routing-decision diagram.
- Top-level README: dropped the stale nostr-discovery
  cargo-feature parenthetical. docs/README.md and the four
  section READMEs (tutorials, how-to, reference, design)
  refreshed for the new structure; index rows reflect both
  halves of the gateway feature and the new fips-gateway
  CLI reference.
- Cargo.toml [package.metadata.deb] assets path updated for
  the fips-security.md move; .gitignore /reference/ rule
  anchored to repo root so docs/reference/ is trackable.
- packaging/openwrt-ipk/files/etc/fips/fips.yaml
  configuration-doc URL updated to the new
  docs/reference/configuration.md location.

## Deletions

- docs/design/fips-intro.md (split into the three new intro
  design docs).
- docs/design/document-relationships.svg (orphan, no longer
  referenced).
- docs/proposals/ tree removed; the only proposal it
  contained (the Nostr UDP hole-punch protocol) was
  rewritten as the new generic
  design/port-advertisement-and-nat-traversal.md.
2026-05-08 03:02:12 +00:00

7.9 KiB

Deploy a Tor Onion Service for FIPS

This guide covers running a Tor onion service that accepts inbound FIPS peer connections.

For the Tor transport's design and the bridge-node pattern (running Tor and UDP simultaneously), see ../design/fips-transport-layer.md. For the full transports.tor.* config knob inventory, see ../reference/configuration.md.

Inbound modes

FIPS supports two inbound Tor modes. (A third mode, socks5, is outbound-only and not covered here.)

  • directory mode (recommended). Tor manages the onion service via HiddenServiceDir and HiddenServicePort directives in torrc. FIPS reads the resulting .onion hostname from a file and binds a local TCP listener for Tor to forward inbound connections to. No control-port interaction is required, which makes this mode compatible with Tor's Sandbox 1 seccomp-bpf hardening.
    • torrc requires: HiddenServiceDir + HiddenServicePort.
  • control_port mode. FIPS speaks to Tor's control port to create an ephemeral onion service at startup (ADD_ONION). The onion key lives only for the lifetime of the FIPS daemon's control-port session. This mode is incompatible with Sandbox 1 — the sandbox forbids control-port-driven onion service management.
    • torrc requires: ControlPort (typically the Unix socket /run/tor/control) and a usable auth method (CookieAuthentication 1 is the common choice).

Pick directory unless you have a specific reason to prefer control_port. The rest of this guide covers directory mode end-to-end.

Prerequisites

  • Tor daemon installed and running (Debian/Ubuntu: apt install tor)
  • FIPS daemon configured and able to start
  • Operator access to /etc/tor/torrc (or a drop-in under /etc/tor/torrc.d/)

Step 1: Configure Tor's HiddenServiceDir

Add the following to /etc/tor/torrc:

HiddenServiceDir /var/lib/tor/fips
HiddenServicePort 8443 127.0.0.1:8444

HiddenServiceDir tells Tor where to store the onion service's private key and hostname file. HiddenServicePort declares that inbound TCP traffic to port 8443 of the onion address should be forwarded to 127.0.0.1:8444 on the local host — that is where FIPS will bind its listener.

The external port (8443 here) is what peers will connect to over Tor; the internal target (127.0.0.1:8444) is purely local and is not directly reachable from the network.

Step 2: Reload Tor and read the onion hostname

sudo systemctl reload tor@default     # or `tor` on systems without instance support

After Tor processes the new config, the hostname file appears:

sudo cat /var/lib/tor/fips/hostname
# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.onion

Tor regenerates the onion key only on first run (or if you remove HiddenServiceDir). The hostname value is stable across daemon restarts as long as HiddenServiceDir is preserved.

Step 3: Verify HiddenServiceDir permissions

The directory must be readable only by the Tor user (Tor refuses to start otherwise):

ls -la /var/lib/tor/fips
# drwx------ debian-tor debian-tor ...

With the shipped Debian systemd unit, FIPS runs as root and reads the hostname file directly — no permission adjustment is needed.

Non-default deployments

If you run FIPS as an unprivileged user (custom packaging, hardened deployment, etc.), the FIPS daemon user needs read access to hostname. Options:

  • Add the FIPS user to the debian-tor group and loosen group read on HiddenServiceDir (Tor still requires the directory itself to be 0700, so this typically means making hostname itself group-readable rather than the directory).
  • Read hostname once at startup as root, then drop privileges.
  • Copy the hostname into a path the FIPS user can read, refreshed whenever the onion key changes.

Step 4: Configure the FIPS Tor transport

In /etc/fips/fips.yaml, configure transports.tor with mode: directory:

transports:
  tor:
    mode: directory
    socks5_addr: "127.0.0.1:9050"
    connect_timeout_ms: 120000
    mtu: 1400
    advertised_port: 8443
    directory_service:
      hostname_file: "/var/lib/tor/fips/hostname"
      bind_addr: "127.0.0.1:8444"

The bind_addr must match the target of the HiddenServicePort directive in torrc. The hostname_file path must match HiddenServiceDir plus /hostname.

advertised_port is the virtual onion port peers dial — i.e. the first number on the HiddenServicePort line, not the local target. The default is 443; this guide uses 8443 on both sides to match the HiddenServicePort 8443 127.0.0.1:8444 example above. Setting this explicitly is important if you ever flip advertise_on_nostr: true: the published advert otherwise defaults to tor:<hash>.onion:443, which won't match the actual onion port.

The socks5_addr is the Tor SOCKS5 proxy used for outbound connections to other onion services or clearnet endpoints (separate from inbound onion service handling).

Optional monitoring knobs: control_addr and control_auth (e.g. /run/tor/control and cookie) let the daemon read Tor's status through the control port even in directory mode. They are non-fatal on failure — the onion service still works without them. See ../reference/configuration.md for the full key list and examples.

Step 5: Reload the FIPS daemon

sudo systemctl reload-or-restart fips

At startup the daemon reads the .onion hostname from hostname_file, binds 127.0.0.1:8444, and announces the onion endpoint internally. From this point inbound connections to <your-onion>.onion:8443 arrive at FIPS over Tor.

Step 6: Verify

Check that the FIPS daemon log shows the onion endpoint at startup:

sudo journalctl -u fips -e | grep -i 'onion\|directory'

You should see a line indicating the onion address FIPS will accept inbound connections on, and that the local bind on 127.0.0.1:8444 succeeded.

From another node configured with the Tor transport in socks5 or directory mode, attempt to dial:

fipsctl connect <peer-npub-or-hostname> <your-onion>.onion:8443 tor

A successful fipsctl show peers afterwards on the inbound side shows the new peer with transport=tor.

Optional: advertise the onion endpoint via Nostr discovery

If node.discovery.nostr.enabled: true, set transports.tor.advertise_on_nostr: true so the onion endpoint appears in this node's published advert. See enable-nostr-discovery.md Scenario 2.

Troubleshooting

  • Tor refuses to start with Sandbox 1 and onion-service errors. Sandbox 1 requires directory mode and forbids creating onion services through the control port. Verify your torrc uses HiddenServiceDir (this guide), not ADD_ONION via control port.
  • FIPS daemon fails to bind 127.0.0.1:8444. Another process is already bound to that port. Either stop the conflicting process or pick a different port and update both torrc's HiddenServicePort target and fips.yaml's bind_addr to match.
  • Onion hostname is empty or missing. Check journalctl -u tor for permission errors on HiddenServiceDir. The directory must be owned by the Tor user with mode 0700.
  • FIPS daemon cannot read hostname_file. File is owned by the Tor user and not readable by the FIPS daemon user. Adjust permissions, or copy the hostname into a path the FIPS user can read.

See also