PSA: WebTunnel bridge on Debian 12 the "easy" way

After the call for more WebTunnel bridges I was setting up a few and would like to suggest an “easier” way for Debian 12 without Docker (the Docker image is outdated and has some flaws imho).

I assume a freshly installed Server with Debian 12 and a domain with at least a DNS A-record pointing to your servers IP address (AAAA should also be present, if you have an IPv6)

Become root if not already logged in as root:
sudo su -
and change directory to /root:
cd

Create the file update.sh with the following content:

apt autoremove
apt-get clean
apt-get update
apt-get upgrade
apt-get dist-upgrade

Execute the script to update your server: sh update.sh

Reboot if necessary (e.g. kernel-update) and login again as root.

Add the following to your /etc/apt/sources.list:

#### backports 
deb http://deb.debian.org/debian bookworm-backports main contrib non-free non-free-firmware
deb-src http://deb.debian.org/debian bookworm-backports main contrib non-free non-free-firmware

Run the update.sh script again.

Install nginx and certbot:
apt-get install certbot python3-certbot-nginx

Edit /etc/nginx/sites-available/default

Search for the line with server_name and replace the underscore _ with your domain name.

server_name _;server_name your.domain.tld;

Reload the nginx:
systemctl reload nginx

Check if your nginx is reachable via the domain in a Browser or else: http://your.domain.tld/
If not, check the firewall ufw status and use ufw allow 'Nginx Full' to unblock it, if ufw is installed.

Get a certificate and let certbot configure your nginx to use it:
certbot --nginx --register-unsafely-without-email -d your.domain.tld

Check in the browser again and see if everything is reachable via https://your.domain.tld now.

Now continue with instructions from Tor Project | WebTunnel Bridge 4.1:

Generate the random string:

echo $(cat /dev/urandom | tr -cd "qwertyuiopasdfghjklzxcvbnmMNBVCXZLKJHGFDSAQWERTUIOP0987654321"|head -c 24)

Take the code snipped below and replace $PATH with the random string created:

location = /$PATH {
        proxy_pass http://127.0.0.1:15000;
        proxy_http_version 1.1;

        ### Set WebSocket headers ###
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        ### Set Proxy headers ###
        proxy_set_header        Accept-Encoding   "";
        proxy_set_header        Host            $host;
        proxy_set_header        X-Real-IP       $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto $scheme;
        add_header              Front-End-Https   on;

        proxy_redirect     off;
        access_log  off;
        error_log off;
}

Put the code snippet in your /etc/nginx/sites-available/default
right below this block (watch for closing all brackets):

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }

#Your snipped goes here#

Reload the nginx again:

systemctl reload nginx

Now the instructions follow Tor Project | Compile and run WebTunnel from the source but tor is installed from debian backports and not the tor repository:

apt-get install golang git
git clone https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/webtunnel
cd webtunnel/main/server
go build
cp server /usr/local/bin/webtunnel
apt-get install tor/bookworm-backports

Edit /etc/apparmor.d/system_tor find /var/lib/tor/** r, and put /usr/local/bin/webtunnel ix, below it:

  # During startup, tor (as root) tries to open various things such as
  # directories via check_private_dir().  Let it.
  /var/lib/tor/** r,
  /usr/local/bin/webtunnel ix,

apparmor_parser -r /etc/apparmor.d/system_tor

Edit your Tor config file, located at /etc/tor/torrc and replace its contents with:

  • url=https://your.domain.tld/your_random_string with your WebTunnel url including your domain and secret path;
  • <address@example.com> with your contact email address;
  • Nickname WebTunnetTest with the nickname of your bridge.
  • Uncomment the last line, if you have IPv6 connectivity.
BridgeRelay 1
ORPort 127.0.0.1:auto
AssumeReachable 1
ServerTransportPlugin webtunnel exec /usr/local/bin/webtunnel
ServerTransportListenAddr webtunnel 127.0.0.1:15000
ServerTransportOptions webtunnel url=https://your.domain.tld/your_random_string
ExtORPort auto
ContactInfo <address@example.com>
Nickname WebTunnelTest
SocksPort 0
Log notice file /var/log/tor/notices.log
#ORPort [::1]:auto
systemctl enable --now tor.service
systemctl restart tor.service

Now you might follow the official version again from section 7. Monitor your logs Tor Project | Compile and run WebTunnel from the source, but your logs will be here:
/var/log/tor/notices.log

Hope it helps to get certificates and renewals easier (a cron-job is automatically added) and makes the installation of tor less error prone. Yes, you can complain Debian backports are always behind official repo, but who ships tor 0.4.8.10 in Docker should not throw stones :wink:

5 Likes

Thank you!

On my second attempt, I now also have a WebTunnel with nginx 1.26.2 and Tor 0.4.8.13 running without Docker. What I overlooked the first time was the adaptation of /etc/apparmor.d/system_tor :man_facepalming:

1 Like

I’ve put together a simple bash script to deploy the reverse proxy + WebTunnel docker image on Debian – feel free to give it a try and see if it works for you:

usage:

# curl -fsSL https://gitlab.torproject.org/-/snippets/212/raw/main/webtunnel.sh | bash -s -- --domain example.org --contact contact@example.org --nickname bridgenickname

or:

git clone https://gitlab.torproject.org/snippets/212.git webtunnel
# ./webtunnel.sh --domain example.org --contact contact@example.org --nickname bridgenickname
2 Likes

@gus Sorry, but the script does not work for me (Debian 12, fresh install with IPV6) Most likely it is just the Docker container. The Docker never gets into the state of a bridge correctly. It is behind a “Docker-NAT”, which is good, but the ORPort is forwarded (and even in a wrong way to work).

After this fix Your server has not managed to confirm reachability for its ORPort - #6 by atari it works without problems.

docker-compose.yml has to look like this imho:

services:
    webtunnel-bridge:
        restart: always
        environment:
            - NICKNAME=$BRIDGE_NICKNAME
            - PT_PORT=15000
            - OR_PORT=127.0.0.1:auto
            - EMAIL=$OPERATOR_EMAIL
            - WEBTUNNEL_URL=$URL
            - WEBTUNNEL_ENABLE_ADDITIONAL_VARIABLES=1
            - WEBTUNNELV_AssumeReachable=$WEBTUNNELV_AssumeReachable
        volumes:
            - 'webtunnel-tor-state:/var/lib/tor'
        user: debian-tor
        ports:
            - '127.0.0.1:15000:15000'
        container_name: webtunnelBridge
        image: 'thetorproject/webtunnel-bridge:latest'
        labels:
            - "com.centurylinklabs.watchtower.enable=true"
    watchtower:
        restart: always
        image: containrrr/watchtower
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock
            - /etc/timezone:/etc/timezone:ro
        environment:
            - WATCHTOWER_CLEANUP=true
            - WATCHTOWER_LABEL_ENABLE=true
            - WATCHTOWER_INCLUDE_RESTARTING=true
        labels:
            - "com.centurylinklabs.watchtower.enable=true"
volumes:
  webtunnel-tor-state:

and .env should be setup like this:

$ truncate --size 0 .env
$ echo "URL=https://yourdomain/and/path" >> .env
$ echo "OPERATOR_EMAIL=your@email.org" >> .env
$ echo "BRIDGE_NICKNAME=WTBr$(cat /dev/urandom | tr -cd 'qwertyuiopasdfghjklzxcvbnmMNBVCXZLKJHGFDSAQWERTUIOP0987654321'|head -c 10)" >> .env
$ echo "WEBTUNNEL_ENABLE_ADDITIONAL_VARIABLES=1" >> .env
$ echo "WEBTUNNELV_AssumeReachable=1" >> .env


The solution does not use IPv6 for tor connections, but this might be achieved differently:

See WebTunnel/Docker: “Unable to find IPv6 address for ORPort” - #2 by atari for explanation.
In general there should be a better working Docker image, this is just dirty fixing making it even harder do understand what is going on…

Also does not work on a freshly installed Debian 12 without IPv6. But it is only the docker, otherwise the script seems to work.

Fixing it this way for IPv4 only machines makes it work:

docker-compose.yml

services:
    webtunnel-bridge:
        restart: always
        environment:
            - NICKNAME=$BRIDGE_NICKNAME
            - PT_PORT=15000
            - OR_PORT=127.0.0.1:auto
            - EMAIL=$OPERATOR_EMAIL
            - WEBTUNNEL_URL=$URL
        volumes:
            - 'webtunnel-tor-state:/var/lib/tor'
        user: debian-tor
        ports:
            - '127.0.0.1:15000:15000'
        container_name: webtunnelBridge
        image: 'thetorproject/webtunnel-bridge:latest'
        labels:
            - "com.centurylinklabs.watchtower.enable=true"
    watchtower:
        restart: always
        image: containrrr/watchtower
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock
            - /etc/timezone:/etc/timezone:ro
        environment:
            - WATCHTOWER_CLEANUP=true
            - WATCHTOWER_LABEL_ENABLE=true
            - WATCHTOWER_INCLUDE_RESTARTING=true
        labels:
            - "com.centurylinklabs.watchtower.enable=true"
volumes:
  webtunnel-tor-state:

I guess docker was changed in the meantime. If you use - OR_PORT=$GENEDORPORT tor will try to also listen on IPv6. In this weird state created by - '$GENEDORPORT:$GENEDORPORT' (IPv4) everything gets mixed up and does not work…

With this solution the following will appear in the logs:
Dec 03 10:50:14.000 [notice] Now checking whether IPv4 ORPort XX.XX.XX.XX:YYYYY is reachable… (this may take up to 20 minutes – look for log messages indicating success)

So AssumeReachable should be set. I don’t know the requirements to have the bridge-descriptor published. But for me it would make sense, not to publish it before establishing reachability. With AssumeReachable 1 they get published. Without, I don’t know…

Have a question regarding

#[::1]:auto

Is the line complete and correct? Had assumed it had to look like this:

ORPort [::1]:auto
1 Like

Correct. Fixed it, thank you :slight_smile:

I tried setting up a Webtunnel following your instructions. It works, but it does not appear on bridges.torproject.org (from the log link). Is your bridge available?

Here is my log:

Dec 04 10:39:31.000 [notice] Tor 0.4.8.12 opening log file.
Dec 04 10:39:31.713 [notice] We compiled with OpenSSL 300000b0: OpenSSL 3.0.11 19 Sep 2023 and we are running with OpenSSL 300000f0: 3.0.15. These two versions should be binary compatible.
Dec 04 10:39:31.733 [notice] Tor 0.4.8.12 running on Linux with Libevent 2.1.12-stable, OpenSSL 3.0.15, Zlib 1.2.13, Liblzma 5.4.1, Libzstd 1.5.4 and Glibc 2.36 as libc.
Dec 04 10:39:31.742 [notice] Tor can't help you if you use it wrong! Learn how to be safe at https://support.torproject.org/faq/staying-anonymous/
Dec 04 10:39:31.743 [notice] Read configuration file "/usr/share/tor/tor-service-defaults-torrc".
Dec 04 10:39:31.744 [notice] Read configuration file "/etc/tor/torrc".
Dec 04 10:39:31.758 [notice] Based on detected system memory, MaxMemInQueues is set to 1344 MB. You can override this by setting MaxMemInQueues by hand.
Dec 04 10:39:31.761 [notice] Opening OR listener on 127.0.0.1:0
Dec 04 10:39:31.762 [notice] OR listener listening on port 30007.
Dec 04 10:39:31.762 [notice] Opened OR listener connection (ready) on 127.0.0.1:30007
Dec 04 10:39:31.762 [notice] Opening OR listener on [::1]:0
Dec 04 10:39:31.762 [notice] OR listener listening on port 30001.
Dec 04 10:39:31.762 [notice] Opened OR listener connection (ready) on [::1]:30001
Dec 04 10:39:31.762 [notice] Opening Extended OR listener on 127.0.0.1:0
Dec 04 10:39:31.763 [notice] Extended OR listener listening on port 30008.
Dec 04 10:39:31.763 [notice] Opened Extended OR listener connection (ready) on 127.0.0.1:30008
Dec 04 10:39:33.000 [notice] Parsing GEOIP IPv4 file /usr/share/tor/geoip.
Dec 04 10:39:33.000 [notice] Parsing GEOIP IPv6 file /usr/share/tor/geoip6.
Dec 04 10:39:33.000 [notice] Configured to measure statistics. Look for the *-stats files that will first be written to the data directory in 24 hours from now.
Dec 04 10:39:34.000 [notice] Your Tor server's identity key fingerprint is 'SSS XXX…'
Dec 04 10:39:34.000 [notice] Your Tor bridge's hashed identity key fingerprint is 'SSS XXX…'
Dec 04 10:39:34.000 [notice] Your Tor server's identity key ed25519 fingerprint is 'SSS xxx…'
Dec 04 10:39:34.000 [notice] You can check the status of your bridge relay at https://bridges.torproject.org/status?id=XXX…
Dec 04 10:39:34.000 [notice] Bootstrapped 0% (starting): Starting
Dec 04 10:39:35.000 [notice] Starting with guard context "default"
Dec 04 10:39:43.000 [notice] Signaled readiness to systemd
Dec 04 10:39:43.000 [warn] Managed proxy "/usr/local/bin/webtunnel" wrote a STATUS line without TRANSPORT: "TYPE=version IMPLEMENTATION=\"webtunnel\" VERSION=\"0.0.1\""
Dec 04 10:39:43.000 [notice] Registered server transport 'webtunnel' at '[xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]:443'
Dec 04 10:39:44.000 [notice] Bootstrapped 5% (conn): Connecting to a relay
Dec 04 10:39:44.000 [notice] Opening Control listener on /run/tor/control
Dec 04 10:39:44.000 [notice] Opened Control listener connection (ready) on /run/tor/control
Dec 04 10:39:44.000 [warn] The IPv4 ORPort address 127.0.0.1 does not match the descriptor address xxx.xxx.xxx.xxx. If you have a static public IPv4 address, use 'Address <IPv4>' and 'OutboundBindAddress <IPv4>'. If you are behind a NAT, use two ORPort lines: 'ORPort <PublicPort> NoListen' and 'ORPort <InternalPort> NoAdvertise'.
Dec 04 10:39:44.000 [warn] The IPv6 ORPort address ::1 does not match the descriptor address xxxx:xxxx:xxxx:xxxx::. If you have a static public IPv4 address, use 'Address <IPv6>' and 'OutboundBindAddress <IPv6>'. If you are behind a NAT, use two ORPort lines: 'ORPort <PublicPort> NoListen' and 'ORPort <InternalPort> NoAdvertise'.
Dec 04 10:39:44.000 [notice] Bootstrapped 10% (conn_done): Connected to a relay
Dec 04 10:39:44.000 [notice] Bootstrapped 14% (handshake): Handshaking with a relay
Dec 04 10:39:44.000 [notice] Bootstrapped 15% (handshake_done): Handshake with a relay done
Dec 04 10:39:44.000 [notice] Bootstrapped 75% (enough_dirinfo): Loaded enough directory info to build circuits
Dec 04 10:39:44.000 [notice] Bootstrapped 90% (ap_handshake_done): Handshake finished with a relay to build circuits
Dec 04 10:39:44.000 [notice] Bootstrapped 95% (circuit_create): Establishing a Tor circuit
Dec 04 10:39:44.000 [notice] Bootstrapped 100% (done): Done
Dec 04 12:10:44.000 [warn] The IPv4 ORPort address 127.0.0.1 does not match the descriptor address xxx.xxx.xxx.xxx. If you have a static public IPv4 address, use 'Address <IPv4>' and 'OutboundBindAddress <IPv4>'. If you are behind a NAT, use two ORPort lines: 'ORPort <PublicPort> NoListen' and 'ORPort <InternalPort> NoAdvertise'.
Dec 04 12:10:44.000 [warn] The IPv6 ORPort address ::1 does not match the descriptor address xxxx:xxxx:xxxx:xxxx::. If you have a static public IPv4 address, use 'Address <IPv6>' and 'OutboundBindAddress <IPv6>'. If you are behind a NAT, use two ORPort lines: 'ORPort <PublicPort> NoListen' and 'ORPort <InternalPort> NoAdvertise'.
Dec 04 13:40:45.000 [warn] The IPv4 ORPort address 127.0.0.1 does not match the descriptor address xxx.xxx.xxx.xxx. If you have a static public IPv4 address, use 'Address <IPv4>' and 'OutboundBindAddress <IPv4>'. If you are behind a NAT, use two ORPort lines: 'ORPort <PublicPort> NoListen' and 'ORPort <InternalPort> NoAdvertise'.
Dec 04 13:40:45.000 [warn] The IPv6 ORPort address ::1 does not match the descriptor address xxxx:xxxx:xxxx:xxxx::. If you have a static public IPv4 address, use 'Address <IPv6>' and 'OutboundBindAddress <IPv6>'. If you are behind a NAT, use two ORPort lines: 'ORPort <PublicPort> NoListen' and 'ORPort <InternalPort> NoAdvertise'.
Dec 04 13:56:45.000 [notice] Our directory information is no longer up-to-date enough to build circuits: We're missing descriptors for 1/3 of our primary entry guards (total microdescriptors: 7995/8010). That's ok. We will try to fetch missing descriptors soon.
Dec 04 13:56:45.000 [notice] I learned some more directory information, but not enough to build a circuit: We're missing descriptors for 1/3 of our primary entry guards (total microdescriptors: 7995/8010). That's ok. We will try to fetch missing descriptors soon.
Dec 04 13:56:46.000 [notice] We now have enough directory information to build circuits.

The system needs some time before the bridge is shown.

You can also look at https://bridges.torproject.org/status?id=yourhash.
Replace yourhash with the hashed fingerprint of your bridge.

I know, and I’ve been waiting for 12 hours.

@gus, your script correctly installs socat, as needed for the steps described in the how-to. At the same time, the installation of socat is missing in the how-to.

Your script also installs a cronjob to reload refreshed certificates in nginx. That’s missing in the how-to.

Your script sets some settings differently or not at all compared to the how-to:

  • ssl_session_timeout
  • ssl_ciphers
  • ssl_prefer_server_ciphers
  • ssl_session_cache shared
  • ssl_session_tickets
  • proxy_set_header Accept-Encoding
  • add_header Front-End-Https

Should also be noted that the recommended format for

    listen [::]:443 ssl http2;
    listen 443 ssl http2;

in newer versions (>= 1.25.1?) of nginx is

    listen [::]:443 ssl;
    listen 443 ssl;
    http2 on;
1 Like

What does https://onionoo.torproject.org/details?lookup=$hashed_fingerprint say to your bridge?

BTW: For new bridges I have the same problem at the moment.

Guess https://bridges.torproject.org/status?id=$hashed_fingerprint is broken. Can’t find a descriptor there younger than 30h…

This site has information about my bridge, then it says unavailable, although it is not.

…
"running":false,
"flags":["V2Dir","Valid"],
…

webtunnel will always report as non running, if you decide not to have a public ORPort.
but it should not matter: can we distribute bridges with unrechable ORPort? (#154) · Issues · The Tor Project / Anti-censorship / rdsys · GitLab

Update:
@meskio said on IRC something was broken with https://bridges.torproject.org/status?id=$hashed_fingerprint since yesterday - bridgestrap ↔ rdsys related…

backlog should be done in about an hour

2 Likes

Thanks for the updates!

1 Like

I experimented yesterday and commented out the lines in torrc:

#ExtORPort auto
#ORPort [::1]:auto

Although I do have IPv6.

This appeared today on bridges.torproject.org:

Bridge XXX... advertises:

* webtunnel: functional
  Last tested: 2024-12-05 04:47:17.152524544 +0000 UTC (48m11.018669677s ago)

And onionoo.torproject.org is:

"running":true,
"flags":["Running","V2Dir","Valid"],

Did it stay like this on metrics?

On the metrics site it looks like this:

image

The V2Dir flag is also gone. I don’t know what that means.

"running":true,
"flags":["Running","Valid"],

then I guess it is needed to make it IPv6 working correctly or do you see IPv6 connections from tor otherwise?

ORPort [::1]:auto

No, I only see IPv4 connections. Let it be as it is. I’m afraid the bridge will stop working altogether.