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 trackingchronyc 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 syslogExitRelay 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=65535The 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@defaultNote 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/fingerprintThird, 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 -lNearly 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
| Thing | Answer |
|---|---|
| Restarting | Costs you a few hours of reputation. Batch your changes. |
| Upgrades | apt upgrade then restart. Relays running old versions get flagged and eventually dropped. |
| Moving sites | Copy /var/lib/tor/keys and the fingerprint follows the relay. Consensus weight does not reset. |
| Complaints | A middle relay generates none. If you receive one, something is exiting and you should check. |
| Shutting down | Set 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.