如何在 VPS 上搭建 .onion 站点
在 Ubuntu 上用 Tor 和 nginx 部署 v3 onion 服务,绑定 127.0.0.1 并关闭泄露路径,避免通过配置、日志和公网监听将 .onion 地址关联到 VPS 公网 IP。
正在构建的内容
Onion 站点是一个普通的 Web 服务器,但只通过 Tor 网络响应请求。安装 tor,向 /etc/tor/torrc 添加两行配置,读取 tor 为您生成的地址,然后让 nginx 绑定到 127.0.0.1,这样公网 IP 上就不会有任何服务响应。安装过程需要十分钟。本文其余部分介绍可能泄露的位置,因为 onion 站点通常无法正常运行,是由于自身配置直接指向了运营者。
Tor 最初称为“洋葱路由器”,onion 服务是只能通过 Tor 访问的服务。第 3 版地址包含 56 个字符,后跟 .onion。这些字符由服务的 ed25519 公钥、校验和以及版本字节组成,并使用 base32 编码。第 2 版地址包含 16 个字符,已于 2021 年从网络中移除,因此今天生成的地址都是 v3。地址本身就是密钥,因此有两个重要后果。连接无需证书颁发机构参与,即可在端到端完成加密和身份验证;而丢失密钥文件就意味着永久丢失该地址。
您的服务器不会接受入站连接。Tor 会选择几个中继作为介绍点,将已签名的描述符上传到目录服务器,然后在访问者选择的会合中继处与访问者建立连接。所有这些连接都由您的服务器主动向外发起。无需开放端口,也无需发布 DNS 记录。如果您希望同一台 VPS 向网络提供其他服务,那是另一项工作,其连接方向正好相反,因为 obfs4 网桥会接收来自受审查客户端的入站连接,并且需要一个可访问的端口。
从 Tor Project 软件仓库安装 tor
Ubuntu 在 universe 中提供 tor 软件包,但其版本通常停留在发行版冻结时的当前版本附近。Tor Project 自己的软件源跟踪当前稳定版本;对于负责决定您的地址是否仍归您所有的软件,这正是您需要的版本。某个软件包存在于发行版中,并不表示仍有人维护它。对于您自行托管的任何软件,都应检查这一点,因为Searx 自 2023 年以来没有新的代码提交,而 SearXNG 正在继续推进该项目。
sudo apt update
sudo apt install -y apt-transport-https gnupg wget
KEYURL=https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc
wget -qO- "$KEYURL" | gpg --dearmor | sudo tee /usr/share/keyrings/deb.torproject.org-keyring.gpg >/dev/null软件仓库条目使用 deb822 格式,Suites 必须是您的 Ubuntu 代号。请从 /etc/os-release 中读取该值,不要手动输入。错误的代号会生成一个可以正常解析、但不包含适用于您版本软件包的软件仓库。
. /etc/os-release
sudo tee /etc/apt/sources.list.d/tor.sources >/dev/null <<EOF
Types: deb deb-src
URIs: https://deb.torproject.org/torproject.org/
Suites: $VERSION_CODENAME
Components: main
Signed-By: /usr/share/keyrings/deb.torproject.org-keyring.gpg
EOF
sudo apt update
sudo apt install -y tor deb.torproject.org-keyringdeb.torproject.org-keyring 软件包会自动保持签名密钥为最新,因此密钥轮换不会导致 apt update 在一年后失效。确认 tor 已启动并连接到网络:
tor --version
sudo journalctl -u tor@default -n 20日志末尾应包含 Bootstrapped 100% (done): Done。tor 如果停留在 Bootstrapped 10%,则没有出站路径。请检查服务提供商的网络防火墙和您自己的出站规则:sudo ufw status verbose 应显示 allow (outgoing) 为默认值。
从这里开始需要记住两个名称。该软件包以 debian-tor 用户身份运行 tor,运行中的单元是 tor@default.service,因为 Debian 和 Ubuntu 中的 tor.service 是实例的封装器。按实例名称查看状态和日志,即可始终获取实际进程的信息。
在 torrc 中配置 Onion 服务
在 /etc/tor/torrc 末尾追加两行:
HiddenServiceDir /var/lib/tor/onion_site/
HiddenServicePort 80 127.0.0.1:8080HiddenServiceDir 用于存放 tor 为此服务生成的密钥和地址。不要手动创建该目录。Tor 启动时会按所需的所有者和权限创建它;如果你以 root 创建目录,首先会触发下面故障列表中的第一个错误。
HiddenServicePort 分为两部分,混淆两者是最常见的第一个错误。第一个数字是访问者在隧道内连接的端口,因此 80 符合预期,没有理由修改。第二部分是 tor 将流量转发到的本地地址。单独使用 HiddenServicePort 80 时,流量会转发到 127.0.0.1:80;明确写出地址并使用高位端口,可以避免 onion 虚拟主机与已监听 80 的服务冲突。
sudo systemctl restart tor@default
sudo ls -l /var/lib/tor/onion_site/目录列表中应包含 hostname、hs_ed25519_public_key、hs_ed25519_secret_key,以及一个空的 authorized_clients 目录。
读取您的 .onion 地址
sudo cat /var/lib/tor/onion_site/hostname输出会返回一行内容:56 个 base32 字符和 .onion。该字符串就是此站点的完整身份标识。没有人会为其分配地址,也无法转移该地址;只要您持有密钥文件,任何人都无法夺走它。现在复制该地址,因为下面的每项配置都需要使用它。本指南其余部分将其写作 <your-address>.onion。
让 nginx 仅绑定到 127.0.0.1 提供站点
sudo apt install -y nginx
sudo install -d -m 755 /srv/onion写入 /etc/nginx/sites-available/onion:
server {
listen 127.0.0.1:8080;
server_name <your-address>.onion;
root /srv/onion;
index index.html;
server_tokens off;
etag off;
access_log off;
error_log /var/log/nginx/onion.error.log error;
}echo '<h1>hello from the onion</h1>' | sudo tee /srv/onion/index.html
sudo ln -s /etc/nginx/sites-available/onion /etc/nginx/sites-enabled/onion
sudo nginx -t
sudo systemctl reload nginx现在从服务器验证两点。第一,nginx 会响应 onion 名称。这就是 tor 将发送的准确 Host 标头:
curl -s -H 'Host: <your-address>.onion' http://127.0.0.1:8080/第二,它只在该地址响应,而不会在其他地址响应:
sudo ss -tlnp | grep 8080地址列必须显示 127.0.0.1:8080。如果显示 0.0.0.0:8080 或 *:8080,说明 onion 站点也暴露在公网,这是泄漏清单中的第一项。不带地址的 listen 8080; 行会绑定所有接口,这也是默认行为。
在 Tor Browser 中打开该地址。首次加载需要几秒钟,因为客户端要获取 descriptor 并建立 rendezvous circuit。
Tor Project 的官方文档更推荐使用 unix socket,而不是 loopback 端口:HiddenServicePort 80 unix:/var/run/tor/onion_site.sock,并让 nginx 监听该路径。其他主机完全无法访问 socket,即使该服务器之后增加第二个网络接口也一样。代价是需要处理文件权限,因为 nginx 创建 socket,而 tor 以 debian-tor 身份连接,所以两个用户必须对目录权限达成一致。使用 loopback 并验证 ss 输出更容易配置正确,本指南的其余部分也采用这种方式。
站点使用 loopback 后,服务器完全不需要为它配置入站规则。为自己保留 22 端口,并拒绝其他端口(适合在 VPS 上设置的 ufw 默认规则)。请记住,防火墙无法纠正绑定到 0.0.0.0 的服务;它只能过滤到达防火墙的数据包。容器会让问题更严重,因为发布 Docker 端口会在 ufw 之前写入 iptables 规则,所以 -p 8080:80 会让 onion 后端暴露在公网 IP 上,即使 ufw 仍报告该端口已拒绝。将容器端口发布为 -p 127.0.0.1:8080:80。
会泄露洋葱站点匿名身份的信息
Tor 会隐藏服务器的位置,但不会隐藏服务器发送的内容。以下每一项都是您自己的技术栈对外发布的信息。不要只看隐私工具的声誉,而应确认其实际边界。这种做法也适用于了解自托管 SearXNG 能隐藏什么:它只能向搜索引擎隐藏您的 IP,除此之外没有更多作用。
同一站点通过您的公网 IP 响应
这是最容易被忽略的问题。扫描器会持续索引每个可路由地址的 HTTP 响应,这些结果公开且可搜索。如果您的公网 IP 和 onion 地址提供相同页面,只需一次查询即可将两者关联起来:相同的标题、相同的 favicon 哈希、相同的 ETag,以及相同的响应头顺序。上面的 listen 127.0.0.1:8080; 行可以解决此问题。请从另一台机器验证,而不是在服务器上验证:
curl -sv --max-time 5 http://<your-public-ip>:8080/返回 Connection refused 或超时才是正确结果。只要返回任何 HTML,站点就是公开可访问的。如果该主机还运行 clearnet 站点,请为该 vhost 使用独立根目录,并在公网监听器上保留明确的 default_server 块。这样,不匹配的 Host 请求头就不会落入 onion vhost。
版本标识
curl -sI http://127.0.0.1:8080/ | grep -i '^server'默认 nginx 会返回 Server: nginx/1.24.0。该版本字符串与其他响应头的确切顺序结合后,会形成指纹,从而将您的 onion 站点与 clearnet 主机关联起来。server_tokens off; 会将其缩减为 Server: nginx。它不会删除该响应头。nginx 没有用于删除该响应头的内置指令,因此如果您希望完全移除它,通常使用 headers-more 模块。PHP 会添加 X-Powered-By,直到您设置 expose_php = Off。etag off; 也应列入同一检查清单,因为 nginx 根据文件的修改时间和大小生成 ETag。因此,同一文件复制到两台服务器后,两台服务器会返回相同的 ETag。
指向 clearnet 域名的绝对 URL
一个 rel="canonical" 标签、一个 Open Graph og:url、RSS feed、站点地图、密码重置邮件,或硬编码的 logo URL,都可能在通过 onion 提供的页面中写入 clearnet 站点名称。请使用 /static/logo.svg 这样的根相对路径,并让应用从请求主机读取基础 URL,而不是使用固定值。重定向中的问题相同,只是出现在另一个位置:catch-all 块中的 return 301 https://example.com$request_uri; 会将 onion 访问者发送到您的真实域名,而 Location 响应头会直接提供这一信息。
与 clearnet 站点共用的 TLS 证书
onion 地址本身就是公钥,因此可以自行完成身份验证。通过 onion 连接时,http:// 已经是端到端加密,Tor Browser 也会将其视为安全上下文。在 onion vhost 上安装现有证书会公开两者之间的关联,因为每个受公开信任的证书都会记录在 Certificate Transparency 日志中。这些日志公开、永久保存,并且可以按名称搜索。请将Let's Encrypt 证书保留在 clearnet vhost 上,让 onion vhost 使用普通 HTTP。
第三方字体和分析工具
来自 CDN(内容分发网络)的字体或分析脚本都属于此类资源。访问者的浏览器会直接获取这些资源,因此第三方会知道有人加载了您的页面,通常还会知道加载的是哪个页面。Tor Browser 的更严格安全级别还会阻止这些请求,导致页面布局损坏。请自行托管页面所需的所有资源。
Host 请求头不匹配
如果 server_name 与 tor 发送的 Host 请求头不匹配,nginx 会回退到该监听地址的默认服务器。在只有一个 vhost 的主机上,这个问题通常不可见,因为唯一的 server 块同时也是默认服务器。之后如果添加 clearnet vhost,onion 请求可能会开始落到该 vhost,并返回其中的规范标签和重定向。每次修改 nginx 后都应重新运行 curl -H 'Host: ...' 检查,并使用 grep 搜索真实域名:
curl -s -H 'Host: <your-address>.onion' http://127.0.0.1:8080/ | grep -o 'https\?://[^"]*' | sort -u确认哪个进程占用哪个套接字,是这项工作的大部分内容(Linux 上的端口和监听套接字工作方式)。
日志中还会留下什么
每个请求都来自 127.0.0.1,因此 nginx 没有访问者地址可记录,access_log off; 不会造成任何损失。但上层应用不同:订单、电子邮件地址或上传文件的元数据都需要由您负责处理。您自己的操作习惯也很重要:通过未加固的登录方式管理主机,不属于 Tor 的保护范围。因此,应将同一 VPS 上的 SSH 加固视为本次构建的一部分。
备份私钥,因为它就是该地址
/var/lib/tor/onion_site/hs_ed25519_secret_key 是服务。没有注册商,也没有恢复机制。私钥丢失后,该地址就无法恢复。复制私钥后,持有副本的人可以在你的地址上提供自己的内容,而你无法撤销任何权限。
sudo systemctl stop tor@default
sudo tar -C /var/lib/tor -czf onion-keys.tgz onion_site
sudo chmod 600 onion-keys.tgz
sudo systemctl start tor@default加密该归档(gpg -c onion-keys.tgz),然后将其移出服务器。要在新的 VPS 上恢复,只需准备该归档,并满足 tor 对所有权的要求:
sudo systemctl stop tor@default
sudo tar -C /var/lib/tor -xzf onion-keys.tgz
sudo chown -R debian-tor:debian-tor /var/lib/tor/onion_site
sudo chmod 700 /var/lib/tor/onion_site
sudo systemctl start tor@default
sudo cat /var/lib/tor/onion_site/hostname在新硬件上,tor 重新发布描述符后,原地址会在 1 到 2 分钟内恢复。这就是完整的迁移过程:无需修改 DNS,也无需重新签发证书。
如果站点同时提供公网访问,可使用 Onion-Location
如果 onion 地址用于便捷访问,而不是用于保密,可以在公网虚拟主机中发布它:
add_header Onion-Location http://<your-address>.onion$request_uri;Tor Browser 随后会在地址栏显示 .onion available 按钮,并提供切换选项。只有当公网页面通过 HTTPS 提供服务,且该值是有效的 onion URL 时,浏览器才会接受此响应头。
这里有一条 nginx 规则需要注意。只有在 location 块未声明任何自有指令时,add_header 指令才会被继承。因此,带有自定义 add_header 的 location 会静默丢弃 Onion-Location。应在其中重复配置该响应头,或将所有响应头集中配置在同一处。主动发布此响应头会明确关联这两个站点。对于镜像站点,这是正确做法;对于需要保持独立、不可关联的站点,则不应这样配置。
自定义地址
mkp224o 会不断生成密钥对,直到其中一个生成的地址以您指定的前缀开头。这是暴力搜索。除了设置前缀和可接受的等待时间外,无需进行其他配置。
sudo apt install -y git gcc libc6-dev libsodium-dev make autoconf
git clone https://github.com/cathugger/mkp224o
cd mkp224o
./autogen.sh
./configure --enable-amd64-51-30k
make
./mkp224o -d onionkeys blog每次找到匹配项时,结果都会写入 onionkeys/<address>.onion/,其中包含 hostname 和 hs_ed25519_secret_key。安装其中一个地址时,先停止 tor,将该目录复制到您的 HiddenServiceDir,然后应用与上述恢复操作相同的 chown 和 chmod 700。
前缀长度决定全部成本。地址使用 base32 编码,因此每增加 1 个字符,预期所需的密钥数量就会乘以 32。较短的前缀在笔记本电脑上即可生成。较长的前缀可能在任何设备上都无法完成。自定义前缀还会让读者形成只识别地址前几个字符而不是完整地址的习惯,而钓鱼站点正是利用这种习惯仿冒 onion 站点的。
故障模式及对应的提示字符串
重启后没有 hostname 文件。 Tor 未启动,或拒绝使用该目录。sudo journalctl -u tor@default -n 50 会显示具体原因:
/var/lib/tor/onion_site/ is not owned by this user (debian-tor, 108) but by root (0). Perhaps you are running Tor as the wrong user?这说明该目录是手动创建的。修正所有者和权限,或删除该目录,让 tor 自动创建。
Tor Browser 显示 Onionsite Not Found (0xF0)。 客户端无法获取描述符,因此从网络角度看,该地址没有发布任何内容。确认 tor 正在运行并已完成引导,将你输入的地址与 sudo cat /var/lib/tor/onion_site/hostname 逐字符比对,然后检查系统时钟。Tor 需要准确的时间来发布和验证描述符,timedatectl 应报告 System clock synchronized: yes。
地址可以解析,但页面始终无法加载。 Tor 已完成会合,但在从 tor 到 nginx 的最后一跳失败;该跳转发生在本地,因此 tor 日志不会记录明显信息。在服务器上运行 curl -sI http://127.0.0.1:8080/。Connection refused 表示 nginx 未运行,或监听的地址与 HiddenServicePort 指向的地址不同。
页面可以加载,但每个链接都指向真实域名。 模板中使用了绝对 URL。运行上面的 grep -o 'https\?://[^"]*' 检查,并在向任何人分享该地址前,修正检查输出的问题。
服务可以运行,但重启后停止。 在依赖该站点前,先主动重启一次服务器,然后运行 sudo systemctl status tor@default 和 sudo systemctl status nginx。手动启动的服务在机器重启前看起来与已启用的服务完全相同。
FAQ
是否需要在防火墙中开放端口,才能使用 Tor onion 服务?
不需要。tor daemon 只建立出站连接,连接对象包括目录服务器、介绍点和各个会合中继,因此无需配置入站规则,Web 服务器本身监听 127.0.0.1。将 ufw 的入站流量保持为默认拒绝,仅允许 SSH。由于同样的特性,位于 NAT(网络地址转换)后的计算机即使没有公网 IP,也可以运行 onion 服务。
为什么我无法在 Tor Browser 中访问我的 .onion 地址?
从服务器开始逐层排查。sudo journalctl -u tor@default -n 50 应显示 Bootstrapped 100% (done): Done;然后,服务器上的 curl -sI http://127.0.0.1:8080/ 应返回状态行;接着,将输入的地址与 hostname 文件进行比较,因为只要有一个字符错误,指向的就是另一个服务。Onionsite Not Found (0xF0) 表示找不到该地址对应的描述符,通常说明 tor 未运行,或系统时钟不正确。
我可以将 onion 站点迁移到新服务器并保留相同地址吗?
可以。该地址由 hs_ed25519_secret_key 派生,因此将完整的 HiddenServiceDir 复制到新服务器,设置其所有者为 debian-tor,权限设为 700,然后启动 tor。描述符重新发布后,该地址即可恢复使用,无需更新 DNS 记录。如果丢失该文件,地址将无法恢复,因此应在创建当天将其加密备份到服务器之外。
onion 站点需要 HTTPS 证书吗?
不需要。这个 56 个字符的地址就是该服务的公钥,因此连接已经实现端到端加密和身份验证;对于使用 .onion 名称的 http://,Tor Browser 会将其视为安全上下文。将普通互联网域名的证书复用于 onion 虚拟主机,反而比完全不使用证书更糟,因为 Certificate Transparency 日志是公开的,并且会永久记录哪些名称共用同一证书。为 .onion 名称购买证书的唯一理由,是获得签发此类证书的 CA 提供的品牌证明;而该关联本来就是公开的。