SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor

Приватный мост Tor WebTunnel на VPS: обход блокировок

Публичные мосты obfs4 и WebTunnel в России живут недолго. Поднимем свой мост WebTunnel за nginx на VPS, не попадающий в раздачу Tor, и дадим строку моста только своим.

Что такое приватный мост WebTunnel на VPS

Приватный мост WebTunnel на VPS нужен, когда Tor Browser в вашей сети больше не подключается через публичные мосты obfs4 и WebTunnel. Вы арендуете сервер за пределами России, ставите на него обычный сайт на nginx и прячете на этом сайте мост Tor по секретному адресу. Строку моста знаете только вы и люди, которым вы её дали, поэтому в публичные списки она не попадает.

Публичные мосты перестают работать по понятной причине. Tor Project раздаёт их через сайт, Telegram-бота, электронную почту и прямо в Tor Browser. Цензор получает адреса теми же способами, что и обычный пользователь, и вносит их в списки блокировки. Чем больше людей получили адрес, тем быстрее он перестаёт работать. Как устроены мосты и чем транспорты отличаются друг от друга, подробно разобрано в статье мосты Tor и подключаемые транспорты obfs4, Snowflake и WebTunnel.

WebTunnel упаковывает соединение Tor в WebSocket внутри обычного HTTPS. Снаружи это TLS-соединение (TLS, transport layer security) с вашим доменом на порту 443. Если кто-то откроет домен в браузере, он увидит ваш сайт. Мост отвечает только по одному пути на этом сайте, и этот путь знает только клиент.

Мост не является выходным узлом. Ваш сервер принимает соединения от клиентов и передаёт их другим узлам сети Tor. Сайты, которые открывают пользователи, видят адрес выходного узла, а не адрес вашего VPS. Поэтому мост спокойно работает на обычном VPS, в отличие от выходного узла Tor на VPS, на который приходят жалобы на чужой трафик. Tor сам по себе является обычной открытой сетевой инфраструктурой.

Инструкция построена на официальной документации Tor Project по WebTunnel, сверенной 1 октября 2026 года. Все команды выполняются на вашем сервере. Там, где команды отличаются от официальных, я объясняю причину.

Что понадобится

  • VPS за пределами России с Ubuntu 24.04 и публичным адресом IPv4.
  • Домен, в DNS которого вы можете создавать записи A и TXT.
  • Доступ по SSH и пользователь с правами sudo.
  • Около получаса времени. Большую часть занимает ожидание DNS.

Мосту не нужно много процессора и памяти. Главный расход здесь трафик: всё, что ваши люди загружают через Tor, проходит через сервер в обе стороны. Посмотрите, сколько трафика входит в тариф.

Про выбор хостера скажу честно. Никто не может обещать, что адреса какого-то провайдера, включая наш, не заблокированы в России. На нашей русскоязычной странице мы пишем только то, что наблюдали сами: у небольших хостеров результаты были лучше, чем у OVH, Hetzner, Linode и DigitalOcean. Это наблюдение, а не гарантия. Проверьте подключение из своей сети до того, как отдавать строку моста другим.

Если вы ещё выбираете между мостом, своим VPN и другими вариантами, сравнение есть в статье что запустить на своём сервере, когда VPN блокируют. А о том, чем Tor отличается от VPN по защите, написано в разборе Tor или VPN: что выбрать.

Шаг 1. Почему нужен сертификат wildcard через DNS-01

Каждый сертификат Let's Encrypt публикуется в журналах Certificate Transparency (CT, открытые журналы всех выданных сертификатов). Любой человек может открыть crt.sh, ввести ваш домен и увидеть все имена, на которые выпускались сертификаты. Если выпустить сертификат на cdn7.example.com, имя моста навсегда окажется в публичном журнале. Цензор читает эти журналы так же, как и все остальные.

Сертификат wildcard на *.example.com решает эту проблему. В журнал попадают только example.com и *.example.com, а конкретное имя поддомена моста не попадает. Let's Encrypt выдаёт wildcard-сертификаты только через проверку DNS-01, то есть через запись TXT в DNS вашего домена.

Выберите для моста имя поддомена, которое выглядит буднично, например cdn7 или static. В примерах ниже основной домен example.com, а мост живёт на cdn7.example.com. Создайте у регистратора или DNS-провайдера запись A для cdn7.example.com с IP-адресом вашего VPS. Затем получите сертификат:

sudo apt update
sudo apt install -y certbot bind9-dnsutils
sudo certbot certonly --manual --preferred-challenges dns -d example.com -d '*.example.com'

Certbot попросит создать запись TXT с именем _acme-challenge.example.com и покажет её значение. Для двух имён он покажет два значения. Нужны обе записи одновременно, поэтому вторую добавьте рядом с первой, а не вместо неё. Перед тем как нажать Enter, проверьте из второго окна терминала, что записи уже видны:

dig +short TXT _acme-challenge.example.com

Команда должна вывести оба значения. Если вывод пустой, DNS ещё не обновился, и проверка Let's Encrypt не пройдёт. Подождите минуту и повторите. После успешной выдачи команда sudo certbot certificates покажет сертификат с Domains: example.com *.example.com и путь /etc/letsencrypt/live/example.com/fullchain.pem.

Ручной режим --manual не продлевает сертификат сам, потому что certbot не умеет без вас менять записи DNS. По состоянию на октябрь 2026 года сертификат Let's Encrypt по умолчанию живёт 90 дней. Когда он истечёт, клиенты моста получат ошибку TLS и не подключатся. Автоматическое продление через API вашего DNS-провайдера описано в статье wildcard-сертификат Let's Encrypt через certbot и DNS-01. Настройте его сразу.

Шаг 2. Обычный сайт на nginx и секретный путь

Мост должен стоять за настоящим сайтом. Любой, кто откроет https://cdn7.example.com/, должен увидеть обычную страницу, а не ошибку и не стандартную заглушку nginx. Положите туда что-то правдоподобное: личную страницу, заметки или документацию к какому-нибудь проекту.

sudo apt install -y nginx ufw
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo mkdir -p /srv/site
echo '<h1>Заметки</h1>' | sudo tee /srv/site/index.html

Порт Tor открывать не нужно. Tor будет слушать только локальный адрес, а снаружи к мосту ведёт только порт 443 через nginx.

Теперь создайте секретный путь. Только он отличает запрос к мосту от запроса к сайту:

openssl rand -hex 16

Команда выведет 32 шестнадцатеричных символа, например 9f2c4e7a1b8d3f60c5e2a9b7d4f1e083. Дальше в тексте это значение обозначено как SECRET. Подставьте своё значение везде, где встретите это слово. Создайте файл /etc/nginx/sites-available/webtunnel-vhost:

server {
    listen 80;
    listen [::]:80;
    server_name cdn7.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name cdn7.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    root /srv/site;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

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

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        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 /dev/null;
    }
}

Блок location = /SECRET взят из документации Tor Project. Знак = означает точное совпадение. Любой другой путь получит страницу сайта или обычную ошибку 404.

Строки Upgrade и Connection обязательны. Соединение WebSocket начинается как обычный запрос HTTP/1.1 с заголовком Upgrade: websocket. Эти заголовки действуют только на одном участке пути, и nginx по умолчанию не передаёт их дальше. Без этих двух строк webtunnel получит обычный запрос, переключения на WebSocket не будет, и Tor Browser не подключится. Как nginx обращается с заголовками при проксировании, подробно разобрано в статье конфигурация обратного прокси nginx построчно.

Строки access_log off и error_log /dev/null нужны, чтобы nginx не записывал IP-адреса людей, которые подключаются к мосту. Обратная сторона: ошибки этого блока тоже никуда не пишутся. При отладке временно замените /dev/null на обычный путь к журналу.

Включите сайт и проверьте конфигурацию:

sudo ln -s /etc/nginx/sites-available/webtunnel-vhost /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

nginx -t должен вывести syntax is ok и test is successful. Теперь проверьте оба пути:

curl -sI https://cdn7.example.com/ | head -1
curl -sI https://cdn7.example.com/SECRET | head -1

Первая команда должна вернуть HTTP/1.1 200 OK. Вторая сейчас вернёт HTTP/1.1 502 Bad Gateway. Так и должно быть: на 127.0.0.1:15000 пока никто не слушает, nginx получает отказ в соединении и отвечает ошибкой 502. После запуска Tor эта ошибка исчезнет.

Шаг 3. Сборка webtunnel с фиксированной версией

Серверная часть WebTunnel собирается из исходников на Go. Сначала проверьте, какую версию Go ставит Ubuntu:

sudo apt install -y golang git
go version

На Ubuntu 24.04 вывод начинается с go version go1.22. Теперь скачайте исходники в домашний каталог и переключитесь на тег релиза:

cd ~
git clone https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/webtunnel
cd webtunnel
git tag --sort=-v:refname | head -5
git checkout v0.0.5
grep '^go ' go.mod

Официальная инструкция собирает ветку по умолчанию. Я собираю тег, потому что ветка меняется между сборками, а тег нет. Через полгода вы сможете собрать ровно ту же версию на новом сервере. По состоянию на октябрь 2026 года последний релиз, который упакован в Debian, это v0.0.5. Если git tag показывает более новый тег, возьмите его и снова проверьте go.mod.

Для v0.0.5 команда grep выводит go 1.18. Это минимальная версия Go, а Ubuntu 24.04 ставит 1.22, этого достаточно. Если будущий тег потребует версию новее, чем в Ubuntu, Go либо остановится с ошибкой, в которой есть go.mod requires go >=, либо попробует скачать новый toolchain. Что именно произойдёт, зависит от значения go env GOTOOLCHAIN. Соберите и установите бинарный файл:

cd main/server
go build
sudo install -m 755 server /usr/local/bin/webtunnel
ls -l /usr/local/bin/webtunnel

go build скачает одну зависимость, библиотеку goptlib от Tor Project. Последняя команда должна показать исполняемый файл с правами -rwxr-xr-x.

Шаг 4. Tor из репозитория deb.torproject.org

Tor ставим из репозитория самого Tor Project, а не из архива Ubuntu. В архиве Ubuntu версия фиксируется на момент выхода релиза дистрибутива, а Tor Project выпускает исправления чаще и публикует их сразу в своём репозитории.

sudo apt install -y apt-transport-https lsb-release gnupg wget
wget -qO- https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --dearmor | sudo tee /usr/share/keyrings/tor-archive-keyring.gpg >/dev/null
CODENAME=$(lsb_release -cs)
echo "deb [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org $CODENAME main" | sudo tee /etc/apt/sources.list.d/tor.list
sudo apt update
sudo apt install -y tor deb.torproject.org-keyring
apt-cache policy tor

В официальной инструкции файл tor.list создаётся через cat <<EOF > /etc/apt/sources.list.d/tor.list. Под обычным пользователем это даёт Permission denied, потому что перенаправление > выполняет ваша оболочка без прав root. Поэтому здесь запись идёт через sudo tee.

В выводе apt-cache policy tor у установленной версии должен стоять источник https://deb.torproject.org/torproject.org noble/main. Если там только archive.ubuntu.com, файл tor.list не подхватился. Проверьте его содержимое и повторите sudo apt update.

Шаг 5. Разрешить webtunnel в AppArmor

Пакет tor устанавливает профиль AppArmor /etc/apparmor.d/system_tor. Профиль ограничивает, какие программы может запускать процесс tor. Файла /usr/local/bin/webtunnel в нём нет, поэтому без правки ядро запретит tor запустить транспорт.

Откройте профиль командой sudo nano /etc/apparmor.d/system_tor. Внутри фигурных скобок профиля, рядом с другими правилами, добавьте строку:

  /usr/local/bin/webtunnel ix,

Флаг ix разрешает запуск файла, и запущенная программа наследует профиль tor. Перезагрузите профиль:

sudo apparmor_parser -r /etc/apparmor.d/system_tor

Успешная команда ничего не выводит. При ошибке синтаксиса она выводит номер строки. Чаще всего причина в пропущенной запятой в конце правила.

Шаг 6. torrc для моста, которого нет в раздаче

Замените содержимое /etc/tor/torrc (sudo nano /etc/tor/torrc) на этот блок. Подставьте свой домен и свой SECRET:

BridgeRelay 1
BridgeDistribution none
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://cdn7.example.com/SECRET
ExtORPort auto
Nickname WebTunnelPrivate
SocksPort 0

Что делает каждая строка:

  • BridgeRelay 1 превращает tor в мост. Мост не попадает в публичный список узлов сети.
  • BridgeDistribution none запрещает раздачу. Tor по-прежнему сообщает о мосте авторитету мостов Tor Project, но система раздачи никому не выдаёт его адрес. Этой строки нет в официальной инструкции, а для приватного моста она главная.
  • ORPort 127.0.0.1:auto открывает основной порт Tor только на локальном адресе. Снаружи к нему не подключиться.
  • AssumeReachable 1 отключает проверку доступности ORPort снаружи. Порт закрыт намеренно, поэтому проверка здесь не нужна.
  • ServerTransportPlugin и ServerTransportListenAddr говорят tor запустить /usr/local/bin/webtunnel и слушать на 127.0.0.1:15000. Это тот же адрес, куда проксирует nginx.
  • ServerTransportOptions задаёт URL моста. Он должен совпадать с доменом и путём в nginx до символа.
  • ExtORPort auto открывает внутренний порт, через который транспорт передаёт соединения в tor.
  • Nickname может быть любым. Не пишите туда своё имя.
  • SocksPort 0 выключает клиентскую часть: сервер не служит прокси для собственных программ.

В официальном примере есть ещё ContactInfo с адресом почты. Эта строка необязательна и попадает в дескриптор моста, поэтому для приватного моста я её не ставлю. Проверьте конфигурацию до запуска:

sudo -u debian-tor tor --verify-config -f /etc/tor/torrc

В конце вывода должно быть Configuration was valid. Если в имени параметра опечатка, tor назовёт этот параметр и сообщит, что он неизвестен.

Шаг 7. Запуск и проверка моста

sudo systemctl enable --now tor
sudo systemctl restart tor
sudo journalctl -u tor@default -e

На Ubuntu служба tor только обёртка. Настоящий процесс работает в службе tor@default, поэтому журнал смотрим именно у неё. restart нужен, потому что пакет запустил tor сразу после установки, ещё со старым torrc.

В журнале ищите две строки. Registered server transport 'webtunnel' at '127.0.0.1:15000' означает, что tor запустил транспорт. Bootstrapped 100% (done): Done означает, что мост подключился к сети Tor. Затем проверьте порт и nginx:

sudo ss -ltnp | grep 15000
curl -sI https://cdn7.example.com/SECRET | head -1

ss должен показать процесс webtunnel на 127.0.0.1:15000. Ответ curl больше не должен быть 502 Bad Gateway, потому что на порту теперь есть кому ответить. Сам код ответа здесь неважен: обычный запрос без WebSocket мосту не нужен.

Шаг 8. Строка моста и подключение Tor Browser

Клиенту нужен отпечаток моста. Он лежит в /var/lib/tor/fingerprint:

sudo cat /var/lib/tor/fingerprint

Вывод выглядит так: имя моста, пробел, 40 шестнадцатеричных символов. Рядом лежит файл hashed-fingerprint. Он не подходит: это хеш отпечатка, а клиенту нужен сам отпечаток. Соберите строку моста:

FP=$(sudo awk '{print $2}' /var/lib/tor/fingerprint)
echo "webtunnel 10.0.0.2:443 $FP url=https://cdn7.example.com/SECRET"

Адрес 10.0.0.2:443 взят из документации Tor Project, и менять его не нужно. Для WebTunnel это формальное поле строки. Реальное соединение клиент устанавливает с хостом и портом из url=.

В Tor Browser откройте about:preferences#connection. В разделе «Мосты» выберите добавление моста вручную, вставьте строку целиком и сохраните. Затем нажмите «Подключиться». Если всё настроено верно, Tor Browser подключится к сети и откроет стартовую страницу.

Передавайте строку только по каналу, которому доверяете, и только людям, которых знаете. Строка содержит всё нужное для подключения. Любой, у кого она есть, может пользоваться мостом и передать его дальше.

От чего защищает приватный мост, а от чего нет

Что видит цензор. Цензор видит TLS-соединение с IP-адресом вашего VPS на порту 443. В начале соединения открытым текстом передаётся имя cdn7.example.com (поле SNI, server name indication). Содержимое и путь SECRET зашифрованы. Если цензор сам зайдёт на этот домен, он увидит ваш сайт. Без пути он не может проверить, что за сайтом стоит мост.

Что видно в Certificate Transparency. При wildcard-сертификате в журналах есть example.com и *.example.com, но нет cdn7. Проверьте это сами поиском по вашему домену на crt.sh. Имя поддомена всё же не секрет в полном смысле. Запись A публична, и любой, кто угадает имя, получит адрес. Кроме того, DNS-запросы ваших клиентов проходят через чужие резолверы, и некоторые из них собирают статистику запрошенных имён.

Что видит хостинг-провайдер. Ваш VPS постоянно соединяется с узлами Tor, а их список публичен. Провайдер может сопоставить адреса и понять, что на сервере работает Tor. Мост не может скрыть это от провайдера и не пытается. Для моста это не проблема, но учитывайте это при выборе хостера.

Что зависит от людей. Строку моста нельзя отозвать у одного человека. Если она попала к посторонним, поменяйте SECRET в nginx и в torrc, перезапустите оба сервиса и раздайте новую строку своим. Если же цензор заблокировал сам IP-адрес, новый путь не поможет, потому что соединение блокируется по адресу ещё до того, как сервер получит путь. Тогда нужен новый адрес или новый сервер.

Мост не делает Tor анонимнее. Он решает только задачу подключения. Анонимность по-прежнему обеспечивает цепочка из трёх узлов Tor, и правила осторожности в браузере остаются прежними.

Почему мост не работает: частые ошибки

Tor не запускает webtunnel. В журнале tor@default есть строка Managed proxy at '/usr/local/bin/webtunnel' failed at launch. Обычно причина в AppArmor. Проверьте журнал ядра командой sudo journalctl -k | grep DENIED. Строка с apparmor="DENIED", operation="exec" и именем /usr/local/bin/webtunnel означает, что правило из шага 5 не добавлено или профиль не перезагружен.

nginx отвечает 502 на секретный путь. На 127.0.0.1:15000 никто не слушает. Значит, tor не запущен или транспорт упал при старте. Проверьте sudo ss -ltnp | grep 15000 и журнал tor. Помните, что error_log /dev/null скрывает подробности nginx для этого блока.

Tor Browser долго подключается и сдаётся. Сравните URL в torrc, путь в location = /SECRET и URL в строке моста. Если путь в строке отличается хотя бы одним символом, nginx отдаёт запрос сайту, и webtunnel его не видит. Журнал подключения в Tor Browser открывается кнопкой просмотра журнала в тех же настройках соединения.

Из-за границы работает, из России нет. Значит, мост исправен, а блокируется адрес или имя. Сначала проверьте, открывается ли из России сам сайт https://cdn7.example.com/. Если не открывается и сайт, причина в адресе сервера, а не в настройках моста.

Мост перестал работать через три месяца. Скорее всего, истёк сертификат, выданный в ручном режиме. sudo certbot certificates покажет дату окончания.

Обновления и резервная копия

Tor обновляется вместе с системой через sudo apt upgrade. Webtunnel обновляйте вручную, тем же способом, что и при установке:

cd ~/webtunnel
git fetch --tags
git tag --sort=-v:refname | head -3
git checkout v0.0.5
grep '^go ' go.mod
cd main/server
go build
sudo install -m 755 server /usr/local/bin/webtunnel
sudo systemctl restart tor

Вместо v0.0.5 подставьте новый тег из вывода git tag. Перед сборкой снова проверьте строку go в go.mod.

Отпечаток моста зависит от ключей в /var/lib/tor/keys. Если вы потеряете эти ключи, отпечаток изменится, и всем придётся выдать новую строку. Сделайте копию и храните её не на сервере:

sudo tar czf tor-keys.tgz -C /var/lib/tor keys
sudo chmod 600 tor-keys.tgz

Если позже вы захотите помогать не только своим, а всем пользователям Tor, это уже другая задача. Для неё подойдёт обычный публичный мост или узел Tor на VPS.

FAQ

Почему публичные мосты obfs4 и WebTunnel в России быстро перестают работать?

Tor Project раздаёт публичные мосты через сайт, Telegram-бота, почту и прямо в Tor Browser. Цензор получает адреса теми же способами и добавляет их в списки блокировки. Приватный мост с BridgeDistribution none в раздачу не попадает, поэтому его адрес знают только люди, которым вы дали строку.

Видно ли имя моста WebTunnel в журналах Certificate Transparency?

Да, если выпустить сертификат на конкретное имя, например cdn7.example.com. Каждый сертификат Let's Encrypt попадает в публичные журналы CT, и их можно найти на crt.sh. В wildcard-сертификате, полученном через проверку DNS-01, указаны только example.com и *.example.com, поэтому имя поддомена моста в журналах не появляется.

Может ли хостинг-провайдер узнать, что на сервере работает Tor?

Да. Мост постоянно соединяется с узлами сети Tor, а их адреса публичны, поэтому провайдер может это заметить. Мост скрывает подключение клиента от цензора в его сети, но не скрывает работу Tor от владельца сервера. Мост не является выходным узлом, поэтому чужой трафик в интернет с вашего адреса не выходит.

Что делать, если строка моста попала к посторонним?

Сгенерируйте новый секретный путь командой openssl rand -hex 16, замените его в location nginx и в ServerTransportOptions в /etc/tor/torrc, затем выполните sudo systemctl reload nginx и sudo systemctl restart tor. Старая строка перестанет работать. Раздайте новую только своим. Если заблокирован уже IP-адрес сервера, новый путь не поможет, и нужен новый адрес.

Какая версия Go нужна, чтобы собрать webtunnel на Ubuntu 24.04?

Для тега v0.0.5 файл go.mod требует Go 1.18 или новее. Ubuntu 24.04 ставит из пакета golang версию 1.22, поэтому сборка проходит без дополнительных шагов. Перед сборкой нового тега проверьте его требование командой grep '^go ' go.mod.