[$ xmrhost] _

$ xmrhost-cli notes show --slug=tor-relay-on-offshore-vps

[$ ] note: tor-relay-on-offshore-vps

// Setting up a Tor relay on an offshore VPS — hardened tor.conf, exit policy, and the abuse mailbox you actually need

// 2026-04-12 · diff=advanced · read=18min · tags=[tor, vps, hardening, iceland, abuse-handling] · by=0xLambda


// ABSTRACT

abstract

A walk-through of what it takes to run a non-exit middle relay, then a reduced-exit relay, on a Monero-paid offshore VPS. Covers the hardened `torrc`, the systemd unit, sysctl tuning for the network stack, the ContactInfo / MyFamily fields the directory authorities will look at, and the abuse-handling mailbox that keeps the upstream provider from null-routing you in week three. RFC- and Tor-spec-cited; assumes you already know what `iptables -A` does.

Why a relay, and why offshore

The Tor network is healthier in proportion to the number of distinct, well-behaved relays it has, weighted by bandwidth and by the autonomous-system diversity of those relays. The Tor Project tracks both the absolute relay count and the AS-level concentration on the consensus pages — and a non-trivial chunk of consensus weight has historically sat in Hetzner, OVH, and a handful of US west-coast networks. Pushing a relay into a less-represented AS is a small but real contribution to network health, and the per-month cost on a hardened offshore VPS is roughly the same as a streaming subscription.

This note is a walk-through of what it takes to run, in this order, (1) a non-exit middle relay, (2) the same node promoted to a reduced-exit relay, on a Monero-paid VPS in Iceland or Romania. Some of it is just the standard Tor Relay Operations setup, copied here for completeness; the rest is the operational stuff that the upstream documentation glosses over. [RFC 9000]QUIC — relevant because Tor's pluggable transports are slowly moving towards UDP

The threat model in one paragraph

Running a middle relay exposes you to almost no operational risk: middle relays only ever see one hop in either direction, so the data they carry is encrypted to keys the operator does not hold. Running an exit relay, even a reduced-exit one, puts your VPS IP in the position of the apparent originator for whatever traffic exits through it — including the small but real fraction of traffic that triggers abuse complaints, ISP notices, and (rarely) law-enforcement contact. The upstream provider’s AUP, the jurisdiction’s case law on intermediary liability, and the operator’s own willingness to triage abuse mail are the three constraints that determine whether you should run an exit at all. [case: Weber v. Fictional ISP — placeholder for the relevant Austrian / German exit-operator cases]

Procurement: the questions you ask the upstream provider before you order

Whatever the marketing page says, the contract is what matters. Before you click “order” on an offshore VPS for relay use, get these in writing — even if the answer is in the AUP, get the question answered explicitly so you have something to point at when the support ticket lands:

  1. Tor middle relay: explicitly allowed, no rate limits below 100 Mbit symmetric, no port restrictions on outbound 9001/9030 (or whatever you’ll use as ORPort / DirPort).
  2. Tor exit relay: explicitly allowed at all, and if so, on what reduced-exit policy. Some providers allow middle but not exit; some allow exit but null-route the box on the first DMCA-format complaint regardless of merit.
  3. The abuse-handling SLA: how complaints reach you, how long you have to respond, whether the provider forwards complaints verbatim or summarises them.
  4. Reverse DNS control: you need to set the PTR record yourself so the relay’s IP resolves to something like tor-exit-01.<your-brand-domain> — this is one of the single most effective abuse-reduction measures because most automated abuse pipelines look at the PTR before escalating.

Skip any of those and the project lasts about three weeks before the box is null-routed and the support ticket reads “policy violation, no further information will be provided”.

The base system — what we assume before any Tor-specific config

This note assumes a freshly provisioned VPS with the brand’s hardened-by-default baseline (the brand spec §3.2): grsec/KSPP-baseline kernel, sshd with PasswordAuthentication no and PermitRootLogin no, fail2ban configured for sshd, unattended-upgrades on, auditd configured. If you’re starting from a less-hardened image, the docs/sshd-hardening and docs/kernel-hardening-checklist entries are the right next read. [linux-doc: admin-guide/sysctl/kernel.rst]kernel.kptr_restrict and friends are referenced from those entries

The walk-through below is for Debian 12 (bookworm); the package names on Ubuntu LTS are identical and the systemd unit shape is the same.

Step 1 — install, but from the Tor Project repo, not the distro repo

The Debian-shipped tor package lags upstream by several minor versions. For relay use the difference matters: bandwidth-accounting bugs, the post-2024 ntor v3 handshake performance fixes, and the changes to MetricsPort formatting all live in upstream releases that the distro hasn’t picked up yet. [Tor relay-spec, MetricsPort section]

Install the upstream apt repo, pin the keyring, install tor and tor-geoipdb:

apt update && apt install -y apt-transport-https gnupg ca-certificates
echo 'deb [signed-by=/usr/share/keyrings/tor.gpg] https://deb.torproject.org/torproject.org bookworm main' > /etc/apt/sources.list.d/tor.list
# Fetch the Tor signing key, store as a keyring file (NOT in apt-key, that's deprecated).
wget -qO- https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc \
  | gpg --dearmor > /usr/share/keyrings/tor.gpg

apt update
apt install -y tor deb.torproject.org-keyring tor-geoipdb

Verify the version is recent:

tor --version
Tor version 0.4.8.13.
This build of Tor is covered by the GNU General Public License (https://www.gnu.org/licenses/gpl-3.0.en.html)
Tor is running on Linux with Libevent 2.1.12-stable, OpenSSL 3.0.16, Zlib 1.2.13, Liblzma 5.4.1, and Libzstd 1.5.5 N/A.

Step 2 — the hardened torrc for a non-exit middle relay

The first deployment is a non-exit middle relay. It carries traffic between other relays in the consensus, never as the exit. This is the safe starting point: you get to learn the operations of running a relay (bandwidth accounting, the descriptor publish cycle, the consensus inclusion timeline) without any of the abuse-handling overhead.

Drop the following into /etc/tor/torrc. Every directive has a comment explaining why it’s there; if you’re tempted to delete the comment, don’t — six months from now you will be glad of it.

# /etc/tor/torrc — non-exit middle relay, hardened defaults.
# Replace placeholder values (Nickname, ContactInfo, MyFamily) before starting.

# ---------- identity --------------------------------------------------------
Nickname              xmrhostRelay01
# Tor Project spec REQUIRES a contact URI on every relay (it is the abuse-
# routing channel for the Tor network). A public mailbox is the standard
# value, but the BridgeAuthority also accepts an https:// URL pointing to
# a contact form — that is the value used here.
ContactInfo           https://<your-brand-domain>/contact?topic=abuse - 0xDEADBEEF (PGP fingerprint, no spaces)
# MyFamily groups your relays so the path-selection algorithm avoids using
# two of yours in the same circuit. List EVERY relay you operate, by RSA key
# fingerprint, separated by commas. Edit when you add or remove a relay.
# MyFamily $ABCDEF0123456789ABCDEF0123456789ABCDEF01

# ---------- ORPort / DirPort -----------------------------------------------
ORPort                9001
# Do NOT run a DirPort if you don't need it; serves cached descriptors and
# adds a small attack surface. Most middle relays do not need it.
# DirPort             9030

# ---------- exit policy: reject everything (middle relay) ------------------
ExitRelay             0
ExitPolicy            reject *:*
ExitPolicyRejectPrivate 1
IPv6Exit              0

# ---------- bandwidth accounting -------------------------------------------
# Throttle to a sane fraction of the line rate. Adjust per provider AUP.
RelayBandwidthRate    10 MBytes
RelayBandwidthBurst   12 MBytes
# Optional monthly cap (uncomment to enforce):
# AccountingMax        4 TB
# AccountingStart      month 1 00:00

# ---------- logging --------------------------------------------------------
# Notice level is the right default. DEBUG/INFO leaks operational info; we
# don't keep info that could correlate clients to circuits anyway, but
# minimising logs is the discipline.
Log                   notice file /var/log/tor/notice.log
# DataDirectory is read/written constantly; make sure it's on the relay's
# own disk, not a shared mount.
DataDirectory         /var/lib/tor

# ---------- safety nets ----------------------------------------------------
DisableDebuggerAttachment 1
HardwareAccel             1
# Disable any client-side functionality — this box is a relay, not a client.
SocksPort                 0
ClientUseIPv4             0
ClientUseIPv6             0
# Cap memory usage so a runaway descriptor / cell flood doesn't OOM the box.
MaxMemInQueues            1024 MB

Restart Tor and tail the log:

systemctl restart tor && journalctl -u tor@default -f
tor[12345]: Tor 0.4.8.13 opening new log file.
tor[12345]: Configuration was valid.
tor[12345]: Bootstrapped 5% (conn): Connecting to a relay
tor[12345]: Bootstrapped 100% (done): Done
tor[12345]: Self-testing indicates your ORPort 9001 is reachable from the outside. Excellent.

The “Self-testing indicates your ORPort N is reachable from the outside” line is the milestone you are looking for. After that the relay publishes its first descriptor, the directory authorities add it to the next consensus, and within ~3 hours the relay will start carrying real traffic. Without that line, the relay isn’t routable from the outside — check the upstream provider’s firewall first, then the local nftables / iptables, then the cloud-firewall / security-group layer if your provider has one.

Step 3 — sysctl tuning for the network stack

A relay handles many concurrent TCP connections (typically 10k-50k for a busy middle relay). The default Linux network-stack settings on most distro images are tuned for a desktop or a low-concurrency server, not for this. The following sysctl drops the keep-alive timer, raises the local port range, increases the connection-tracking tables (if conntrack is in the path), and raises the file-descriptor limit.

# /etc/sysctl.d/99-tor-relay.conf
# Apply: sysctl --system

# Local port range — Tor opens many outbound connections.
net.ipv4.ip_local_port_range = 10000 65535

# TCP keep-alive — drop dead connections faster.
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5

# SYN backlog — prevents drops under bursty connection load.
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 8192

# Conntrack table size — only matters if the relay has nf_conntrack loaded
# (it's loaded by default on Debian even without an active firewall ruleset).
net.netfilter.nf_conntrack_max = 524288

# Receive / transmit buffers — bumped for a 1 Gbit/s relay.
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

# Disable IPv6 router advertisements (we set IPv6 statically — no SLAAC).
net.ipv6.conf.all.accept_ra = 0

# kernel.kptr_restrict from the brand baseline — already set, included for completeness.
kernel.kptr_restrict = 2

Bump the file-descriptor limit for the tor user via a systemd drop-in:

# /etc/systemd/system/tor@default.service.d/limits.conf
[Service]
LimitNOFILE=65536
LimitNPROC=8192

Reload and restart:

systemctl daemon-reload && systemctl restart tor && sysctl --system

Step 4 — the metrics port (if you want grafana later)

Tor exposes Prometheus-style metrics on a configurable port. Enabling it lets a downstream Prometheus scrape connection counts, circuit counts, traffic-by-hour, and the relay’s published bandwidth. Don’t expose it to the public internet — bind to 127.0.0.1 and reach it via SSH tunnel from the metrics host.

# Append to /etc/tor/torrc:
MetricsPort           127.0.0.1:9035 prometheus
MetricsPortPolicy     accept 127.0.0.1

Test from the relay itself:

curl -s 127.0.0.1:9035/metrics | head -20
# HELP tor_uptime_seconds Tor daemon uptime
# TYPE tor_uptime_seconds counter
tor_uptime_seconds 14782
# HELP tor_relay_flag Relay flag from the consensus
# TYPE tor_relay_flag gauge
tor_relay_flag{flag="Fast"} 1
tor_relay_flag{flag="Running"} 1
tor_relay_flag{flag="Stable"} 1
tor_relay_flag{flag="Valid"} 1

Step 5 — promoting to a reduced-exit relay

After the middle relay has been running for at least four weeks with stable bandwidth and no operational surprises, you can consider promoting it to a reduced-exit relay. The reduced exit policy allows a small set of well-behaved ports (HTTPS, IMAP/POP, XMPP, IRC, common gaming ports) and rejects everything else — in particular, it rejects all SMTP-related ports, which removes the spam-relay vector that drives roughly 80% of exit-relay abuse complaints.

The Tor Project publishes a canonical reduced exit policy. Pull it from the spec wiki and paste it verbatim into torrc; do not hand-edit the port list, because the policy has been worked out empirically over years and your edits will likely make abuse worse, not better. [Tor reduced-exit-policy spec]

# Replace the old "ExitRelay 0" / "ExitPolicy reject *:*" block with:
ExitRelay             1
IPv6Exit              1

# Reduced exit policy, copied verbatim from the Tor Project wiki.
# DO NOT hand-edit; the port list is empirically tuned for low abuse.
ExitPolicy            accept *:20-21      # FTP
ExitPolicy            accept *:43         # WHOIS
ExitPolicy            accept *:53         # DNS
ExitPolicy            accept *:79-81      # finger, HTTP
ExitPolicy            accept *:88         # kerberos
ExitPolicy            accept *:110        # POP3
ExitPolicy            accept *:143        # IMAP
ExitPolicy            accept *:194        # IRC
ExitPolicy            accept *:220        # IMAP3
ExitPolicy            accept *:443        # HTTPS
ExitPolicy            accept *:464        # kpasswd
ExitPolicy            accept *:531        # IRC/AIM
ExitPolicy            accept *:543-544    # Kerberos
ExitPolicy            accept *:554        # RTSP
ExitPolicy            accept *:563        # NNTPS
ExitPolicy            accept *:631        # CUPS
ExitPolicy            accept *:636        # LDAPS
ExitPolicy            accept *:706        # SILC
ExitPolicy            accept *:749        # kerberos
ExitPolicy            accept *:873        # rsync
ExitPolicy            accept *:902-904    # VMware
ExitPolicy            accept *:981        # remote HTTPS mgmt
ExitPolicy            accept *:989-990    # FTPS
ExitPolicy            accept *:991        # Netnews admin
ExitPolicy            accept *:992        # TELNETS
ExitPolicy            accept *:993        # IMAPS
ExitPolicy            accept *:994        # IRCS
ExitPolicy            accept *:995        # POP3S
ExitPolicy            accept *:1194       # OpenVPN
ExitPolicy            accept *:1220       # QT Server Admin
ExitPolicy            accept *:1293       # PKT-KRB-IPSec
ExitPolicy            accept *:1500       # VLSI License Manager
ExitPolicy            accept *:1533       # Sametime
ExitPolicy            accept *:1677       # GroupWise
ExitPolicy            accept *:1723       # PPTP
ExitPolicy            accept *:1755       # RTSP
ExitPolicy            accept *:1863       # MSNP
ExitPolicy            accept *:2082       # Infowave Mobility Server
ExitPolicy            accept *:2083       # Secure Radius Service (radsec)
ExitPolicy            accept *:2086-2087  # GNUnet, ELI
ExitPolicy            accept *:2095-2096  # NBX
ExitPolicy            accept *:2102-2104  # Zephyr
ExitPolicy            accept *:3128       # SQUID
ExitPolicy            accept *:3389       # MS WBT
ExitPolicy            accept *:3690       # SVN
ExitPolicy            accept *:4321       # RWHOIS
ExitPolicy            accept *:4643       # Virtuozzo
ExitPolicy            accept *:5050       # MMCC
ExitPolicy            accept *:5190       # ICQ/AIM
ExitPolicy            accept *:5222-5223  # XMPP, XMPP-over-SSL
ExitPolicy            accept *:5228       # Android Market
ExitPolicy            accept *:5900       # VNC
ExitPolicy            accept *:6660-6669  # IRC
ExitPolicy            accept *:6679       # IRC SSL
ExitPolicy            accept *:6697       # IRC SSL
ExitPolicy            accept *:8000       # iRDMI
ExitPolicy            accept *:8008       # HTTP alternate
ExitPolicy            accept *:8074       # Gadu-Gadu
ExitPolicy            accept *:8080       # HTTP Proxies
ExitPolicy            accept *:8087-8088  # Simplify Media SPP, Radan HTTP
ExitPolicy            accept *:8232-8233  # Zcash
ExitPolicy            accept *:8332-8333  # Bitcoin
ExitPolicy            accept *:8443       # PCsync HTTPS
ExitPolicy            accept *:8888       # HTTP Proxies, NewsEDGE
ExitPolicy            accept *:9418       # git
ExitPolicy            accept *:9999       # distinct
ExitPolicy            accept *:10000      # Network Data Management Protocol
ExitPolicy            accept *:11371      # OpenPGP hkp (http keyserver protocol)
ExitPolicy            accept *:19294      # Google Voice TCP
ExitPolicy            accept *:19638      # Ensim control panel
ExitPolicy            accept *:50002      # Electrum Bitcoin SSL
ExitPolicy            accept *:64738      # Mumble
ExitPolicy            reject *:*

Step 6 — the abuse mailbox you actually need

The single most effective abuse-reduction step after the reduced exit policy is having an actual abuse mailbox that is read and answered within 24 hours. The ContactInfo you set in torrc is published in the consensus and is what diligent abuse desks will check before escalating; the PTR record you set on the IP is what less-diligent automated complaint pipelines will check. Both should resolve to a contact that goes to a human.

The minimum viable abuse mailbox: a Postfix or msmtp on a different host that aliases abuse@yourdomain to a real inbox you check daily, plus an autoresponder that explains the IP belongs to a Tor exit relay, links to the Tor Project’s “What is Tor?” page, links to your own /legal/aup, and gives the IP’s reduced exit policy. Most automated abuse pipelines will close the ticket after the autoresponder. The remaining 5-10% are worth handling personally.

Step 7 — what to monitor, what to ignore

A relay that’s running well needs almost no day-to-day attention. The four metrics worth a Prometheus alert on:

  1. up{job="tor-relay"} going to 0 — the relay is down.
  2. tor_relay_flag{flag="Running"} going to 0 — the relay is up but the directory authorities have stopped including it in the consensus.
  3. tor_relay_load_onionskins_dropped_total rising sharply — the box is CPU-bound and dropping client handshakes; either rate-limit the relay further or move to a higher-CPU plan.
  4. node_network_transmit_bytes_total flatlining for >2 hours — the relay is in the consensus but has stopped carrying traffic, almost always a network-level issue at the upstream.

Everything else is noise. Don’t alert on per-circuit anomalies, per-port traffic distributions, or anything else that will fire on a normal weekday. Operator burnout is the most common reason relays get retired, and 90% of it comes from over-monitoring.

Closing — the relay will be in the consensus by morning

After the first descriptor publishes (~5 minutes after systemctl restart tor), the directory authorities take roughly 1 hour to vote, the bandwidth authorities take roughly 4-7 days to settle on a measured bandwidth, and after that the relay carries traffic in proportion to its measured weight.

If you’ve followed this note end-to-end, the relay is in a known-good middle-relay state, the abuse mailbox is in place if you ever promote to exit, and the box is hardened to the brand baseline. The next entries in the docs/ section cover the kernel-hardening checklist, the sshd hardening pass, and the fail2ban / auditd configuration that the brand baseline ships with — all of which apply equally to a relay box and to any other VPS workload you’d put on offshore infrastructure.

// END OF NOTE

$ cd /notes # back to the listing