Onion Transport (optional)

This tutorial covers running an Ogmara node behind a Tor hidden service. It is entirely optional — most operators do not need it. Onion transport is meant for operators in jurisdictions where public-IP discoverability is risky, or who want their node reachable from inside the Tor network even when their public clearnet address is blocked.

If you are setting up your first Ogmara node, skip this page. The default clearnet setup in Run a Node is the right starting point. Come back here only if you specifically need Tor support.

Why use onion transport?

What works in this release (l2-node 0.46.9+)

l2-node 0.46.9 ships the inbound side of onion transport: you can host a Tor hidden service and other Tor-aware peers can dial you over onion. The outbound side — dialing other operators’ /onion3/... multiaddrs from your node — lands in l2-node 0.46.10. Until then, your node only uses Tor to accept connections; outbound dials still go over clearnet.

1 Prerequisites

Onion transport uses an external Tor daemon that you manage separately from the l2-node process. No embedded Tor library is shipped — this keeps the audit surface small.

What you need:

  • A working Ogmara node (per Run a Node) — clearnet setup must be functional first.
  • Root access on the same host (to install Tor and edit /etc/tor/torrc).
  • l2-node binary version 0.46.9 or newer.

2 Install Tor

Install the Tor daemon from your distribution’s repository. The version that ships with current Debian / Ubuntu (Tor 0.4.7+) supports v3 hidden services, which is the only kind l2-node accepts.

# Debian / Ubuntu
sudo apt update
sudo apt install -y tor

# Verify the daemon starts:
sudo systemctl status tor
# Should show: active (running)

v2 onions are not supported. Tor deprecated v2 hidden services in 2021 (Tor 0.4.6). l2-node refuses any hostname that is not exactly 56 base32 characters + .onion at config-load.

3 Configure the Hidden Service

Edit /etc/tor/torrc and append a HiddenServiceDir + HiddenServicePort pair pointing at the loopback TCP port you want l2-node to listen on. We use 41720 to match the standard libp2p port on both sides — pick a different loopback port if 41720 is already in use.

# Add to the bottom of /etc/tor/torrc:
HiddenServiceDir /var/lib/tor/ogmara_hs/
HiddenServicePort 41720 127.0.0.1:41720

The two numbers do different things:

  • First 41720 — the virtual port clients use when they dial your /onion3/<host>:41720 multiaddr. This is what gets published on chain.
  • Second 127.0.0.1:41720 — where Tor forwards the inbound traffic on your machine. l2-node listens here.

Restart Tor to apply the change. The first restart generates the v3 onion keypair and writes the hostname:

sudo systemctl restart tor

# Wait a few seconds, then read the generated hostname:
sudo cat /var/lib/tor/ogmara_hs/hostname
# Output: a 56-character base32 string ending in .onion, e.g.
# abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuvwx.onion

Save this hostname. You will paste it into ogmara.toml in the next step. The hostname is stable across Tor restarts — do not delete /var/lib/tor/ogmara_hs/ or you will get a new .onion and have to re-publish your metadata on chain.

4 Configure ogmara.toml

Open your ogmara.toml and add (or update) the [network.tor] block:

[network.tor]
# Enable onion transport.
enabled = true

# Local Tor SOCKS proxy — loopback only.
socks_proxy = "127.0.0.1:9050"

# The loopback TCP port from your torrc HiddenServicePort line above.
listen_onion_port = 41720

# The .onion hostname from /var/lib/tor/ogmara_hs/hostname
listen_onion_hostname = "abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuvwx.onion"

# When true, your onion multiaddr is appended to the setNodeMetadata
# calldata builder so other Tor-aware nodes can discover you.
# You still have to click "Publish multiaddrs" in the dashboard to
# actually broadcast (no proxy-signing — you control your wallet).
advertise_onion_in_metadata = true

l2-node validates the hostname format at config-load. If you typo the base32 string or accidentally include the .onion twice, the node refuses to start with a clear error message. The socks_proxy must be a loopback address — 127.0.0.1 or ::1 — non-loopback proxies are rejected for safety.

Restart l2-node to pick up the config:

sudo systemctl restart ogmara-node

# Watch the startup log for the "Onion inbound listener up" line:
sudo journalctl -u ogmara-node -f | grep -i onion

You should see a log entry like:

INFO Onion inbound listener up (Tor hidden service forwards here)
  onion_hostname="abcdef...uvwx.onion"
  loopback="/ip4/127.0.0.1/tcp/41720"

5 Publish your onion multiaddr

Open the node dashboard at https://your-node/admin/dashboard and scroll to On-chain metadata (peer discovery). The Effective multiaddrs row should now include your onion multiaddr alongside any clearnet ones:

/dns4/your-node.example/tcp/41720/p2p/...
/dns4/your-node.example/udp/41720/quic-v1/p2p/...
/onion3/abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuvwx:41720/p2p/...

Click Publish multiaddrs. The dashboard hands you calldata; sign it with your Klever extension. After the transaction confirms, the Currently on-chain row will include your onion multiaddr, and the In sync row will turn green.

No proxy signing. The node never holds your Klever wallet key. Every setNodeMetadata call is signed by you in the browser extension — the node just builds the calldata.

6 Verify the setup

From any host that has Tor installed (your laptop, another node), use the torify wrapper to test that the hidden service forwards correctly:

# From a different host with Tor installed:
torify nc -zv abcdef...uvwx.onion 41720
# Output: Connection to ...onion 41720 port [tcp/*] succeeded!

On the node itself, the mesh-stats endpoint will show the onion listener as part of the swarm transports once l2-node 0.46.10 ships outbound onion dial. For 0.46.9, the inbound side is already live and operators on the network with Tor support can reach you.

7 Security properties — what is enforced

The SOCKS5 dialer module that powers outbound onion transport (landing in 0.46.10) ships now in 0.46.9 with audited invariants:

  • No DNS leak. .onion hostnames are sent to the local Tor daemon as bytes via SOCKS5 ATYP=0x03 (DOMAINNAME). Your node never calls getaddrinfo() on a .onion name.
  • No IP fallthrough. If the Tor proxy is unreachable, returns the wrong protocol version, or refuses the CONNECT, the dial fails. The node never falls back to a direct TCP connect.
  • Loopback-only proxy. The validator refuses any socks_proxy that is not 127.0.0.1 or ::1. A remote SOCKS proxy would route every onion dial through that server, which inverts the deanonymization model.
  • Loopback-only inbound listener. The TCP socket binds 127.0.0.1 only. Even if you mis-configure Tor to forward to a different port, the listener will not accept clearnet connections.
  • v3 hostname validation. 56 lowercase base32 characters plus .onion exactly. Non-ASCII, wrong length, missing port pairing all reject at config-load.

Full audit invariants and the SOCKS5 wire-format details are in spec 13 §6.4.1.

Troubleshooting

The dashboard does not show my onion multiaddr. Check that advertise_onion_in_metadata = true in [network.tor], that listen_onion_hostname is set, and that listen_onion_port matches the second number in your HiddenServicePort line. Restart the node after any config change.

l2-node refuses to start with a hostname error. The validator requires exactly 56 base32 characters (a-z, 2-7, lowercase) followed by .onion. Open /var/lib/tor/ogmara_hs/hostname and paste the contents verbatim — no quotes, no trailing newline, no https:// prefix.

Tor logs say “Connection refused” to 127.0.0.1:41720. Your HiddenServicePort points at a loopback port l2-node is not listening on. Check that the [network] listen_port in ogmara.toml matches the second number in your HiddenServicePort line.

Other nodes still cannot reach me. Until l2-node 0.46.10 ships outbound onion dial, other operators need to be running a Tor-enabled libp2p stack themselves to use your onion multiaddr. Until then, onion is best understood as a parallel discoverability surface: published on chain, audited, ready for the v0.46.10 rollout. Your clearnet multiaddrs continue to serve as the primary peering path.

Next steps