xmrcloud
menu

doc: run-obfs4-bridge

Run an obfs4 bridge on a XmrCloud VPS — tor + obfs4proxy, torrc, firewall, bridge line, distribution

published 2026-07-14 updated 2026-07-14 intermediate 14 min read

  • tor
  • bridge
  • obfs4
  • censorship
  • vps

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-default baseline is in place. See harden-sshd and kernel-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-1 tier 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 && /usr/bin/obfs4proxy -version
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 parses
  • Bootstrapped 100% (done) — the bridge has fetched consensus
  • Registered server transport 'obfs4' — obfs4proxy came up and bound its port
  • Self-testing indicates your ORPort ... is reachable from the outside. Excellent. — the vanilla ORPort is reachable
  • Your 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:

cat /var/lib/tor/pt_state/obfs4_bridgeline.txt
# 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:

  1. 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.

  2. Keep it private. Set BridgeDistribution none to 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, use PublishServerDescriptor 0 instead; 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.log should show Registered server transport 'obfs4' at '0.0.0.0:<PORT-B>'.
  • Both ports reachable: the ORPort self-test line from step 5, plus an nc -z from a third host against <PORT-B>.
  • Descriptor uploaded: unless you set PublishServerDescriptor 0, expect Uploaded server descriptor in 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:

  1. 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.
  2. Subscribe the operator’s email to the tor-relays mailing list for advisories and transport-deprecation notices.
  3. If your target region is known to block obfs4, stand up a webtunnel transport alongside it — one box, two circumvention methods.
  4. If you later want to graduate to a public, higher-capacity contribution, read run-non-exit-tor-relay for the middle-relay path, or provision-tor-hidden-service to 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.

cd /docs