Ubuntu VPS如何搭建.onion站点并避免IP泄露
在 Ubuntu VPS 上用 Tor 和 nginx 部署 v3 onion service,绑定回环地址并检查配置,避免公网 IP、端口和密钥泄露。文中涵盖 Tor Project 软件源、systemd 实例及常见错误。
您要构建的内容
Onion 站点是一个只通过 Tor 网络响应请求的普通 Web 服务器。安装 tor,向 /etc/tor/torrc 添加两行配置,读取 tor 为您生成的地址,然后让 nginx 绑定到 127.0.0.1,这样公网 IP 上就不会有任何服务响应。安装过程需要十分钟。其余内容都是泄露风险清单,因为 Onion 站点通常不是 Tor 本身失效,而是自身配置直接暴露了运营者。
Tor 最初称为“洋葱路由器”。Onion service 是只能通过 Tor 访问的服务。第 3 版地址包含 56 个字符,后面接 .onion。这些字符是服务的 ed25519 公钥、校验和及版本字节,经过 base32 编码。第 2 版地址包含 16 个字符,已于 2021 年从网络中移除,因此现在生成的地址都是 v3。地址本身就是密钥,这会带来两个后果。连接由端到端加密和身份验证保护,不涉及证书颁发机构;如果丢失密钥文件,就会永久失去该地址。
您的服务器不会接受入站连接。Tor 会选择几个中继作为介绍点,将带签名的描述符上传到目录服务器,然后在访问者选择的会合中继处与访问者建立连接。所有这些连接都由您的服务器主动向外发起。无需开放端口,也无需发布 DNS 记录。
从 Tor Project 软件源安装 tor
Ubuntu 在 universe 中提供 tor 软件包,但其版本通常停留在发行版冻结时的版本。Tor Project 自己的软件源会跟踪当前稳定版本。对于决定您的地址是否仍归您使用的软件,这是您需要的版本。
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 中打开该地址。首次加载需要几秒钟,因为客户端要获取您的描述符并建立会合电路。
Tor Project 的官方文档更推荐使用 Unix 套接字,而不是回环端口:HiddenServicePort 80 unix:/var/run/tor/onion_site.sock,并让 nginx 监听该路径。其他主机完全无法访问套接字,即使服务器之后新增第二个网络接口也一样。代价是文件权限管理,因为 nginx 创建套接字,而 tor 以 debian-tor 身份连接该套接字,因此两个用户必须对目录权限达成一致。使用回环地址并验证 ss 输出更容易配置正确,本指南其余部分也采用这种方式。
网站使用回环地址后,服务器完全不需要为它设置入站规则。为自己保留 22 端口,并拒绝其余端口(适合在 VPS 上设置的 ufw 默认规则)。请注意,防火墙无法改变绑定到 0.0.0.0 的服务;它只能过滤到达防火墙的数据包。容器会使这个问题更加明显,因为 发布 Docker 端口会在 ufw 之前写入 iptables 规则,所以 -p 8080:80 会让您的 onion 后端绑定到公网 IP,即使 ufw 仍报告该端口被拒绝。将容器端口发布为 -p 127.0.0.1:8080:80。
去匿名化洋葱站点的信息泄露
Tor 会隐藏服务器的位置,但不会隐藏服务器返回的内容。下面列出的每一项,都是您自己的技术栈主动发布的信息。
公网 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 网站,请为该虚拟主机设置独立的网站根目录,并在公网监听器上保留明确的 default_server 块,这样不匹配的 Host 请求头就不会落入 onion 虚拟主机。
版本标识
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,而不是使用固定常量。重定向也存在同样的问题,只是发生在另一个位置:捕获所有请求的块中的 return 301 https://example.com$request_uri; 会将 onion 访问者重定向到您的真实域名,而 Location 响应头会直接将该域名交给访问者。
与 clearnet 网站共用的 TLS 证书
onion 地址本身就是公钥,因此可以对自身进行身份验证。因此,在 onion 连接上使用 http:// 时,连接已经是端到端加密的,Tor Browser 也会将其视为安全上下文。在 onion 虚拟主机上安装现有证书,会公开两个站点之间的关联,因为每个受公开信任的证书都会记录在 Certificate Transparency 日志中,而这些日志公开、永久保存,并且可以按名称搜索。请将 Let's Encrypt 证书保留在 clearnet 虚拟主机上,并让 onion 虚拟主机使用普通 HTTP。
第三方字体和分析脚本
例如来自 CDN(内容分发网络)的字体,或分析脚本。访问者的浏览器会直接获取这些资源,因此第三方会知道有人加载了您的页面,通常还会知道具体加载了哪个页面。同时,Tor Browser 更严格的安全级别会阻止这类请求,导致页面布局损坏。请自行托管页面所需的全部资源。
Host 请求头不匹配
如果 server_name 与 Tor 发送的 Host 请求头不匹配,nginx 会回退到该监听地址的默认服务器。在只有一个虚拟主机的服务器上,这个问题不明显,因为唯一的 server 块同时也是默认服务器。之后如果添加 clearnet 虚拟主机,onion 请求可能开始落入该虚拟主机,并返回其中的规范标签和重定向。每次修改 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/hostnametor 重新发布 descriptor 后,原地址会在新硬件上恢复,通常只需等待 1 到 2 分钟。这就是完整的迁移过程:无需修改 DNS,也无需重新签发证书。
如果网站也提供公网访问,请配置 Onion-Location
如果 onion 服务只是为了方便访问,而不是为了保密,可以在公网虚拟主机中发布其地址:
add_header Onion-Location http://<your-address>.onion$request_uri;此后,Tor Browser 会在地址栏显示 .onion available 按钮,并提供切换选项。只有在公网页面通过 HTTPS 提供服务,且该值是有效的 onion URL 时,Tor Browser 才会接受此响应头。
这里有一条 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 编码,因此每增加一个字符,预期所需的密钥数量就会增加 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。由于具有相同特性,onion 服务也可以运行在 NAT(网络地址转换)后的计算机上,甚至完全不需要公网 IP。
为什么我无法在 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) 表示找不到该地址对应的 descriptor,通常说明 tor 未运行,或系统时钟不正确。
我可以将 onion 站点迁移到新服务器并保留相同地址吗?
可以。该地址由 hs_ed25519_secret_key 派生,因此请将完整的 HiddenServiceDir 复制到新服务器,将其所有者和权限模式设置为 debian-tor 和 700,然后启动 tor。descriptor 重新发布后,该地址即可恢复使用,而且无需更新 DNS 记录。如果丢失该文件,就无法恢复该地址。因此,应在创建当天将其加密备份到服务器之外。
onion 站点需要 HTTPS 证书吗?
不需要。这个 56 字符的地址就是该服务的公钥,因此连接已经实现端到端加密和身份验证;Tor Browser 会将 .onion 名称上的 http:// 视为安全上下文。在 onion 虚拟主机上重复使用 clearnet 证书反而比不使用证书更糟,因为 Certificate Transparency 日志是公开的,并且会永久记录哪些名称共用同一证书。为 .onion 名称购买证书的唯一理由,是获得签发此类证书的 CA 提供的品牌保障,而该关联本身就是公开的。