doc: run-obfs4-bridge
Run an obfs4 bridge on a XmrCloud VPS — tor + obfs4proxy, torrc, firewall, bridge line, distribution
abstract
abstract
Procedure for bringing up an obfs4 pluggable-transport bridge on the smallest offshore XmrCloud VPS. Covers the upstream-repo install of tor and obfs4proxy, the exact bridge torrc (BridgeRelay, ORPort, ServerTransportPlugin, ServerTransportListenAddr, ExtORPort, ContactInfo, Nickname), opening both ports in the firewall, reading the bridge line out of pt_state, and the choice between publishing to BridgeDB or handing the line out privately. A bridge is low-resource and attracts far less abuse than an exit — it fits the vps-1 tier.
Scope and assumptions
This is the operational walk-through for running an obfs4 bridge on a XmrCloud VPS. A bridge is an entry point into the Tor network whose address is NOT listed in the public consensus, so that a censor who blocks every published relay still cannot enumerate and block it. An obfs4 bridge goes one step further: the obfs4 pluggable transport wraps the connection so that on the wire it looks like a featureless stream of random bytes, defeating protocol-fingerprinting DPI that would otherwise flag “this is Tor”. [Tor Project — Bridges]
A bridge is the lowest-stakes contribution an operator can make to the network. It is NOT an exit: it never originates traffic toward the public internet, so it is never the source IP of anything a third party sees. Because a bridge is unlisted and non-exit, it attracts far less abuse than either an exit relay or even a public middle relay — in practice, near zero. If exit-relay liability is what has kept you from contributing, a bridge sidesteps that entirely. Compare the middle-relay path in run-non-exit-tor-relay; a bridge is lighter on every axis.
Baseline assumptions:
- Debian 12 (bookworm) on a XmrCloud VPS. Package names are identical on Ubuntu 22.04 / 24.04 LTS.
- The brand’s
hardened-by-defaultbaseline is in place. Seeharden-sshdandkernel-hardening-checklist. - A bridge is genuinely low-resource — a fraction of a core, a few hundred MB of RAM, and modest bandwidth. The smallest
vps-1tier is sized for exactly this. A dedicated box or a high-egress plan is wasted on a bridge.
Step 1 — confirm the upstream allows a bridge
Bridges are unlisted and non-exit, so they rarely draw upstream attention — but confirm in writing that running one is acceptable before you install anything. The five procurement questions in run-non-exit-tor-relay apply, with one simplification: a bridge’s egress is a small fraction of a public relay’s, so the bandwidth-cap question is far less pressing. The one thing to confirm is that the provider permits inbound connections on two arbitrary high ports (the ORPort and the obfs4 port). XmrCloud’s listed offshore providers all permit this.
Because a bridge is a privacy-infrastructure box, the operator generally wants the VPS itself to be unlinkable to their identity — provisioned without KYC and paid for in a way that leaves no billing trail. That is the entire point of paying in Monero; see /why-monero and /payments for the no-KYC rationale.
Step 2 — install tor and obfs4proxy from the upstream repo
The distro tor package lags upstream. Use the Tor Project’s apt repo (keyring file, not the deprecated apt-key), then install tor and obfs4proxy together:
apt update
apt install -y apt-transport-https gnupg ca-certificates wget
wget -qO- https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc \
| gpg --dearmor > /usr/share/keyrings/tor.gpg
cat > /etc/apt/sources.list.d/tor.list <<'EOF'
deb [signed-by=/usr/share/keyrings/tor.gpg] https://deb.torproject.org/torproject.org bookworm main
deb-src [signed-by=/usr/share/keyrings/tor.gpg] https://deb.torproject.org/torproject.org bookworm main
EOF
apt update
apt install -y tor deb.torproject.org-keyring obfs4proxy tor-geoipdb
tor-geoipdb is worth installing on a bridge: it lets Tor bucket the (anonymised, aggregated) client-country statistics that make the bridge visible on the metrics dashboard as “reaching users in country X”. Confirm both binaries:
Tor version 0.4.8.13. obfs4proxy-0.0.14
Step 3 — write the bridge torrc
This is the canonical obfs4-bridge torrc. Every directive is commented; the two <PORT-A> / <PORT-B> placeholders are the only mandatory edits besides ContactInfo and Nickname.
# /etc/tor/torrc — obfs4 bridge (unlisted entry node, non-exit).
# Replace <PORT-A>, <PORT-B>, <YOUR-CONTACT-EMAIL>, <YOUR-NICKNAME>.
# ---------- bridge role -----------------------------------------------------
# BridgeRelay 1 makes this a bridge: the descriptor goes to the bridge
# authority, NOT the public consensus, so the address stays unlisted.
BridgeRelay 1
# ORPort — the vanilla relay listener. Pick a random port ABOVE 1024 and do
# NOT use 9001: 9001 is the historical relay default and scanning it is one
# cheap way a censor enumerates suspected bridges.
ORPort <PORT-A>
# ---------- obfs4 pluggable transport --------------------------------------
# obfs4proxy wraps the connection so it looks like random bytes on the wire.
# ServerTransportListenAddr is the port CLIENTS actually connect to — it is
# this port, not the ORPort, that appears in the bridge line. Bind 0.0.0.0 so
# it is reachable on the public IPv4.
ServerTransportPlugin obfs4 exec /usr/bin/obfs4proxy
ServerTransportListenAddr obfs4 0.0.0.0:<PORT-B>
# ExtORPort lets tor and obfs4proxy talk over a local extended-OR channel so
# per-client stats survive the transport. `auto` picks a free localhost port.
ExtORPort auto
# ---------- identity / contact ---------------------------------------------
# ContactInfo is PUBLIC in the bridge descriptor. Use a monitored role
# address, never a personal one. Nickname: 1-19 alphanumeric chars.
ContactInfo <YOUR-CONTACT-EMAIL>
Nickname <YOUR-NICKNAME>
# ---------- distribution (see step 7) --------------------------------------
# Default: the bridge authority hands the line out via BridgeDB. To keep the
# bridge fully private and distribute the line yourself, uncomment ONE of the
# two lines below (they are mutually exclusive — read step 7 first).
# BridgeDistribution none
# PublishServerDescriptor 0
# ---------- logging ---------------------------------------------------------
Log notice file /var/log/tor/notice.log
DataDirectory /var/lib/tor
Make sure the log directory exists and is owned by debian-tor:
mkdir -p /var/log/tor
chown debian-tor:debian-tor /var/log/tor
chmod 0750 /var/log/tor
Step 4 — open the firewall for both ports
Per the Tor Project’s bridge guide, both the ORPort and the obfs4 port must be reachable from the outside. Open both <PORT-A> and <PORT-B>: [Tor Project — obfs4 bridge on Debian/Ubuntu]
nft add rule inet filter input tcp dport <PORT-A> accept
nft add rule inet filter input tcp dport <PORT-B> accept
If you chose a privileged port (below 1024, e.g. 443 to blend with HTTPS) for the obfs4 listener, grant obfs4proxy the capability to bind it:
setcap cap_net_bind_service=+ep /usr/bin/obfs4proxy
Step 5 — start Tor and confirm reachability
Restart Tor and watch the journal:
systemctl restart tor
journalctl -u tor@default -f --since '60 seconds ago'
Watch for the milestone lines:
Configuration was valid— the torrc parsesBootstrapped 100% (done)— the bridge has fetched consensusRegistered server transport 'obfs4'— obfs4proxy came up and bound its portSelf-testing indicates your ORPort ... is reachable from the outside. Excellent.— the vanilla ORPort is reachableYour Tor server's identity key fingerprint is '...'— the identity is established
If the ORPort-reachable line never appears, the firewall or the upstream is blocking <PORT-A>. To confirm the obfs4 port is reachable too, use the Tor Project’s bridge reachability scanner or test the port from a third box with nc -z <vps-ip> <PORT-B>.
Step 6 — read the bridge line
On start, obfs4proxy writes a ready-to-use bridge line into the Tor state directory:
# This file is an automatically generated bridge line... Bridge obfs4 <IP ADDRESS>:<PORT> <FINGERPRINT> cert=<CERT> iat-mode=0
The file ships with placeholder <IP ADDRESS> and <PORT> — substitute your VPS’s public IPv4 and the ServerTransportListenAddr port (<PORT-B>). The <FINGERPRINT> is your bridge’s identity-key fingerprint (the 40-hex value, also in /var/lib/tor/fingerprint), NOT the hashed fingerprint and NOT the ed25519 key. The cert= and iat-mode= parameters are obfs4’s own — never hand-edit them. [Tor Project — bridge post-install, bridge-line format]
The finished line looks like:
Bridge obfs4 198.51.100.10:443 095B40360EEE62CD7C5DDA081D1C7AA811513A9E cert=s1PecFWx4/ycKB9U/eT4n7jZB68LumMIJdG5RcXPr9zlOxfZqAz4vY0FBv8iam1gYFITeQ iat-mode=0
That single line is everything a client needs to use your bridge.
Step 7 — choose a distribution method
There are two honest paths, and they are mutually exclusive:
-
Publish to BridgeDB (the default). Leave the two distribution lines in step 3 commented out. Tor uploads the descriptor to the bridge authority, and BridgeDB hands your line out to censored users through its rotation of buckets (HTTPS, Moat, email, Telegram). This is the maximum-impact choice — your bridge reaches strangers who need it — but it also means the line eventually lands in databases a determined censor may scrape. Your bridge’s hashed fingerprint appears on Relay Search a few hours after launch.
-
Keep it private. Set
BridgeDistribution noneto keep the descriptor published (so you still show up in the metrics dataset) but tell BridgeDB never to give the line out — you distribute it yourself, out of band, to specific people. For a fully dark bridge that never uploads a descriptor at all, usePublishServerDescriptor 0instead; you then lose the metrics visibility. [Tor Project — bridge distribution methods]
A private bridge handed to a handful of contacts in a hard-censorship region is often more durable than a BridgeDB-listed one, precisely because it is not in any scrapeable pool. Pick based on who you are trying to help.
Step 8 — post-launch checks
- obfs4 registered:
grep -i 'transport' /var/log/tor/notice.logshould showRegistered server transport 'obfs4' at '0.0.0.0:<PORT-B>'. - Both ports reachable: the ORPort self-test line from step 5, plus an
nc -zfrom a third host against<PORT-B>. - Descriptor uploaded: unless you set
PublishServerDescriptor 0, expectUploaded server descriptorin the log within minutes. - Metrics visibility: search your fingerprint on the Tor Metrics bridge dashboard ~3 hours after launch to confirm the bridge authority sees you.
Closing — what to do next
The bridge is operational. Next steps, in order:
- If you distributed via BridgeDB, do nothing further — the line propagates on its own. If you kept it private, hand the line from step 6 to the people who need it over a channel a censor cannot observe.
- Subscribe the operator’s email to the
tor-relaysmailing list for advisories and transport-deprecation notices. - If your target region is known to block obfs4, stand up a
webtunneltransport alongside it — one box, two circumvention methods. - If you later want to graduate to a public, higher-capacity contribution, read
run-non-exit-tor-relayfor the middle-relay path, orprovision-tor-hidden-serviceto host a service over Tor instead of relaying for it. For how the overlay networks compare, see/vs/i2p-vs-tor-vs-lokinet.
A bridge is the single cheapest, lowest-risk way to put an offshore, no-KYC VPS to work for people who are being censored. Run it on its own vps-1 and leave it alone.