Twelve builds

Running a Tor relay within the acceptable use policy

A non-exit relay with IPv6, bandwidth accounting, a hardened systemd unit and a verification pass that confirms your fingerprint reached the consensus.

What this builds

A middle relay: it accepts connections from clients and other relays, forwards traffic inside the network, and never opens a connection to a destination on the ordinary internet. Nobody receives an abuse complaint about a middle relay, because from the outside it exchanges encrypted traffic with a published list of other relays and nothing else.

That distinction is the whole reason this guide exists. Exit relays are a separate conversation: complaints about them arrive at our abuse desk rather than yours, so read the acceptable use policy and open a ticket before you configure one. Everything below deliberately turns exiting off, twice.

Before you start

  • An R-4. Tor is single-threaded per connection and network-bound; four dedicated cores will saturate a link long before the CPU notices.
  • A site with bandwidth you are happy to donate. BUH-01 and HEL-01 are the cheapest terabytes we sell, and both have the transit to back it.
  • A working clock. Relays with skewed time are dropped from the consensus, and this is the single most common cause of a relay that starts perfectly and never appears anywhere.

1. Base system

apt update && apt full-upgrade -y
apt install -y tor nyx chrony nftables
timedatectl set-timezone UTC
chronyc tracking

chronyc tracking should report a system time offset in the microseconds. If it reports seconds, stop and fix that first; nothing downstream will work.

2. The relay configuration

Replace /etc/tor/torrc with the following. Read every line, because two of them are the ones keeping you inside the policy:

Nickname exampleRelay
ContactInfo [email protected]

ORPort 9001
ORPort [2001:db8:1a2b::2]:9001
SocksPort 0
ControlPort 0

ExitRelay 0
ExitPolicy reject *:*
IPv6Exit 0

RelayBandwidthRate 40 MBytes
RelayBandwidthBurst 60 MBytes
AccountingMax 40 TBytes
AccountingStart month 1 00:00

MetricsPort 127.0.0.1:9035
MetricsPortPolicy accept 127.0.0.1

User debian-tor
DataDirectory /var/lib/tor
Log notice syslog

ExitRelay 0 and ExitPolicy reject *:* are belt and braces. Either would do on its own; both together mean that no future package default, and no half-remembered edit at midnight, quietly turns you into an exit.

Substitute your instance’s real IPv6 address in the second ORPort line. Every instance ships with a routed /64, so there is no reason to run a v4-only relay, and v6-capable relays are still in shorter supply than v4 ones.

Bandwidth accounting deserves a thought rather than a copy-paste. Traffic on our network is unmetered under fair use, but a relay left uncapped will use precisely as much as the network can find for it, forever. Forty megabytes per second with a forty-terabyte monthly ceiling is a generous, well-behaved relay. When accounting is reached, the relay hibernates until the period rolls over instead of being cut off mid-circuit.

If you run more than one relay anywhere, add a MyFamily line listing every fingerprint including this one, comma-separated. Clients use it to avoid building a circuit that passes through two machines under the same control, and omitting it is the most common configuration complaint aimed at operators.

3. Hardening the unit

The packaged unit is decent; these lines make it considerably less interesting to anyone who finds a bug in it.

systemctl edit tor@default
[Service]
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
MemoryDenyWriteExecute=yes
ReadWritePaths=/var/lib/tor /var/log/tor /run/tor
LimitNOFILE=65535

The file descriptor limit matters more than it looks. A busy relay holds thousands of simultaneous sockets, and the default limit produces a relay that works for a week and then starts refusing connections with nothing obvious in the log.

4. Firewall

flush ruleset

table inet filter {
  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    ct state invalid drop
    iif lo accept
    ip protocol icmp accept
    ip6 nexthdr icmpv6 accept
    tcp dport 22 accept
    tcp dport 9001 accept
  }
  chain forward {
    type filter hook forward priority filter; policy drop;
  }
}
systemctl enable --now nftables
systemctl restart tor@default

Note that the metrics port is bound to the loopback address and therefore needs no rule. Reach it through an SSH tunnel when you want it, and never expose it; relay metrics are interesting to more people than you.

Verify it

This is the part that takes hours rather than minutes, so start it and go away.

First, within about twenty minutes of starting, the log should say the relay tested its own port from outside and got through:

journalctl -u tor@default --since "20 min ago" | grep -i "self-testing"

You want the line reading that your ORPort is reachable from the outside. If it never appears, the firewall is blocking inbound 9001 or your v6 address in the torrc does not match the instance.

Second, take your fingerprint and keep it:

cat /var/lib/tor/fingerprint

Third, confirm the relay is doing work rather than merely running:

nyx
curl -s http://127.0.0.1:9035/metrics | grep -E "tor_relay_traffic|tor_relay_connections"

The traffic counters should be climbing within an hour of the self-test passing. Directory authorities need roughly three hours to vote your fingerprint into the consensus, and clients only start sending meaningful volume after that. The Guard flag arrives around the eighth day if your uptime holds, and that is when the traffic graph goes from a trickle to the cap you set.

Fourth, and most importantly for this particular build, prove you are not exiting. From a second machine, check that nothing but relay traffic leaves:

ss -tn state established | wc -l
ss -tn state established sport = :9001 | wc -l

Nearly every established connection should involve the ORPort. A large number of outbound connections to arbitrary ports would mean an exit policy you did not intend, at which point stop the daemon and re-read step two.

Operating it

ThingAnswer
RestartingCosts you a few hours of reputation. Batch your changes.
Upgradesapt upgrade then restart. Relays running old versions get flagged and eventually dropped.
Moving sitesCopy /var/lib/tor/keys and the fingerprint follows the relay. Consensus weight does not reset.
ComplaintsA middle relay generates none. If you receive one, something is exiting and you should check.
Shutting downSet AccountingMax to a small value first so circuits drain, or just stop it and accept the reputation loss.

Back up /var/lib/tor/keys somewhere off the instance the day you build it. The identity key is what makes this relay that relay; lose it and you start again at zero consensus weight, which is eight days of nobody using you. Our position on what we log while your relay runs is on the logging page, and it is short.

Ready when you are

Pick a city. Pick a size. Pay in coin.

No forms about who you are, no wait for a human to approve you, no phone call to verify anything. The invoice clears and the credentials land in your inbox.