SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-21

如何在 nginx 中添加 Onion-Location 标头

在 nginx 的 HTTPS 明网 vhost 中发布 Onion-Location,让 Tor Browser 提示您的 onion 地址,并修复重定向和第三方资源造成的信息泄露。

Onion-Location header 的作用

Onion-Location header 是 clearnet vhost 中的一行配置,用于向 Tor Browser 公布您的 onion 地址。通过 Tor 访问 https://example.com 的访客会在地址栏看到一个写有 .onion available 的紫色按钮,单击后即可进入您的 onion service。它只用于发现服务。该 header 不会创建 onion service,也不会隐藏您的任何信息。

本指南假设两部分已经存在。您在 nginx 后有一个运行于 VPS 的站点,并且已有一个指向该站点且正常工作的 v3 onion service。如果第二部分尚未配置,请先完成配置:在 VPS 上托管 onion 站点介绍 torrc 配置行和第一个 hostname 文件。下面将介绍如何将两者关联,同时避免一方的信息泄露到另一方。

Tor Browser 认可此标头前的要求

Tor Project 记录了3个条件。3个条件必须同时满足,否则不会显示提示按钮。

  • Onion-Location 值必须是有效 URL,并使用 http:https: 方案,且包含 .onion 主机名。
  • 定义该标头的网页必须通过 HTTPS 提供。
  • 定义该标头的网页本身不能是 onion 站点。

第二个条件最容易被忽略,第三个条件解释了为什么不能在 onion vhost 上设置此标头。实现中还有第4条规则,文档没有明确说明:Tor Browser 只处理顶级文档中的此标头。代码会先将加载目标与文档进行比较,然后才执行后续操作。因此,样式表、图像或 API 响应返回的标头会被忽略。

默认情况下,浏览器会显示提示按钮,并等待用户点击。希望自动跳转的用户可以依次打开 Settings、Privacy and Security、Onion Services,然后将 "Prioritize .onion sites when known" 设置为 "Always"。服务器端无法强制启用此行为。应将此标头视为建议,而不是重定向。

在 nginx 中添加 Onion-Location 标头

该标头应放在为明网域终止 TLS 的 server 块中。如果将其放在 port 80 块中,则不会生效,因为该块只负责发出重定向,而要求二排除了在普通 HTTP 页面上定义的标头。

server {
    listen 443 ssl;
    server_name example.com;

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

    add_header Onion-Location http://<your-onion-address>.onion$request_uri always;

    root /srv/example.com/public;
}

$request_uri包含路径和查询字符串,因此 https://example.com/guides/tor 上的读者会被引导到 onion 站点上的相同路径。如果省略它,所有访问者都会进入 onion 主页,而不是他们正在阅读的页面。

always很重要,因为 nginx 有一项已记录的限制。add_header仅在响应代码为 200、201、204、206、301、302、303、304、307 或 308 时添加该字段。您的 404 页面可能是搜索结果中的实际入口;如果没有 always,该页面根本不会携带标头。

nginx 的第二个常见问题是继承,而且它不会显示错误。只有在当前配置级别没有 add_header 指令时,add_header 指令才会从上一个配置级别继承。因此,location /assets/ { add_header Cache-Control ...; } 块会丢弃 server 级别的 Onion-Location,使其对该块下的所有 URL 都不生效。如果您在任何位置设置了单独的位置标头,请在每个此类块中重复 Onion-Location 行。如果您不熟悉这种行为,建议阅读一次nginx 如何选择 server 和 location 块

重新加载配置,并检查一个正常页面和一个不存在的页面:

sudo nginx -t && sudo systemctl reload nginx
curl -sI https://example.com/ | grep -i onion-location
curl -sI https://example.com/no-such-page | grep -i onion-location

两条命令都应输出一行 onion-location:。第二条命令证明 always 正在生效。第二条命令没有输出,表示缺少该标志,或者某个 location 块覆盖了该指令。

无法设置响应头时使用 HTML meta 标签

静态主机和部分 CDN 控制面板不允许添加任意响应头。相同的值也可以作为文档 head 中的 meta 元素使用,因为无论数据通过 HTTP 还是作为 http-equiv 标签到达,浏览器都会通过相同的文档头数据读取它。

<meta http-equiv="onion-location" content="http://<your-onion-address>.onion" />

这 3 项要求仍然适用。包含该标签的页面必须使用 HTTPS,且不能是 onion 站点。区别在于,该标签只能包含一个不带路径的固定地址,因为没有服务器端变量可供展开。每个包含该标签的页面都会提供 onion 主页。这就是回退方案的代价,因此只要您能控制服务器,就应优先使用响应头。

让 onion 使用独立的 nginx vhost

明网站点和 onion 不得共用同一个 server block。Tor Browser 会发送 Host: <your-onion-address>.onion。如果没有 server block 声明接收该名称,nginx 就会回退到默认 server。默认 server 是你的明网 vhost,因此该 vhost 生成的每个 URL 都会使用你的域名。

将隐藏服务指向一个仅由 loopback 接口响应的端口:

HiddenServiceDir /var/lib/tor/onion_site/
HiddenServicePort 80 127.0.0.1:8080

然后为该端口配置独立的 vhost:

server {
    listen 127.0.0.1:8080;
    server_name <your-onion-address>.onion;

    absolute_redirect off;
    port_in_redirect off;

    root /srv/example.com/public;
}

listen 127.0.0.1:8080 可使此 vhost 不绑定到你的公网 IP,因此扫描 VPS 地址的人员无法获取它,也无法将其与明网副本逐字节比较。absolute_redirect off 会让 nginx 生成相对的 Location 值,因此目录的尾部斜杠重定向会返回 Location: /guides/,而不是完整 URL。nginx 已经会根据 Host header,而不是 server_name 生成绝对重定向,因为 server_name_in_redirect 默认为 off;但使用相对重定向可以完全避免这个问题。

为什么 onion 页面仍会将访问者重定向到明网站点?

通常不是 nginx 泄露了信息,而是您的应用。凡是根据已配置的站点地址构建绝对 URL 的组件,无论请求由哪个 vhost 提供服务,都会使用您的域名。

  • 指向 https://example.com/...rel="canonical" link 标签。这是最常见的情况,任何查看源代码的人都能看到确切的明网页面地址。
  • 由框架而不是 nginx 生成的重定向,例如 Django 的 SECURE_SSL_REDIRECT,或 WordPress 的 homesiteurl 选项。
  • og:url 以及其他社交卡片元标签。
  • Sitemap 和 RSS 条目,因为它们按规范必须使用绝对 URL。
  • 应用错误页面。它们通常包含一个“返回主页”链接,而该链接由同一个设置生成。

修复方法取决于您的技术栈,没有通用方案。检查方法是通用的。通过 Tor 获取 onion 页面,并在响应中搜索您的域名。

curl -s --socks5-hostname 127.0.0.1:9050 http://<your-onion-address>.onion/ \
  | grep -i 'example\.com'

--socks5-hostname 将该名称发送到 tor 的 SOCKS 端口进行解析。这是必需的,因为您本机上的任何组件都无法在本地解析 .onion 名称。对于通过软件包安装的 tor daemon,默认端口是 9050。没有输出表示检查通过。任何匹配结果都表示该页面会将您的明网域名提供给每一位 onion 访问者。先对主页运行检查,再对会生成 404 的 URL 运行检查。

单独检查重定向链,因为重定向响应通常为空:

curl -sI --socks5-hostname 127.0.0.1:9050 http://<your-onion-address>.onion/guides \
  | grep -i '^location'

包含 example.comLocation 值表示某个重定向正在将 onion 访问者送回明网,并通过出口节点完成此请求;而访问者原本认为请求会一直留在 Tor 内部。

不要在 onion 地址上提供 clearnet 证书

v3 onion 地址由服务自身的公钥派生。因此,在发送任何 HTTP 请求之前,Tor 已针对该特定服务对电路完成身份验证和加密。在 onion 服务内部使用纯 HTTP 属于正常配置,这与在互联网中传输纯 HTTP 不同。

如果通过复制 clearnet 虚拟主机来构建 onion 虚拟主机,也会一并复制 ssl_certificate。此时,onion 地址会提供一张证书,其主题备用名称列出 example.com。这会导致两个问题。浏览器会显示名称不匹配,因为 URL 是 onion 地址,而证书不包含该地址。每个选择继续访问的用户还会收到一份经过签名的声明,表明这两个站点位于同一台机器上。将 onion 虚拟主机单独保存到自己的文件中,并使用独立的 server_name。这样也能避免与 Certbot nginx 插件混淆,因为该插件会修改与所请求证书域名匹配的服务器块。

Tor 软件包选择与服务密钥安全

Ubuntu 软件仓库中的 tor 软件包可满足此需求,无需额外配置。它落后于当前稳定版本系列。因此,如果服务需要长期运行,请使用 Tor Project 自己的 Debian 软件仓库,并让 apt 与其他软件一起升级。截至 2026 年 8 月,文档记录的步骤如下:

sudo apt install apt-transport-https
wget -qO- https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc \
  | gpg --dearmor \
  | sudo tee /usr/share/keyrings/deb.torproject.org-keyring.gpg >/dev/null

写入 /etc/apt/sources.list.d/tor.sources,将 suite 替换为 lsb_release -c 中的发行版代号:

Types: deb deb-src
URIs: https://deb.torproject.org/torproject.org/
Suites: noble
Components: main
Signed-By: /usr/share/keyrings/deb.torproject.org-keyring.gpg
sudo apt update
sudo apt install tor deb.torproject.org-keyring

deb.torproject.org-keyring 软件包会持续更新签名密钥,因此一年后软件仓库仍可通过验证。请选择一个来源并持续使用。软件仓库中的软件包与该仓库中的软件包版本不同。如果同时启用两者,apt 在升级时会在它们之间切换。

HiddenServiceDir 保存服务身份。该目录中的 hs_ed25519_secret_key 文件就是您的 onion 地址,因为该地址是密钥对的公钥部分。丢失此文件后,地址将永久失效,因为没有任何机构可以重新签发它。将该文件复制到不安全的位置后,持有副本的任何人都可以运行您的 onion 服务。

tor 拒绝使用其他用户可读取的目录。先检查权限和所有者:

sudo ls -ld /var/lib/tor/onion_site
sudo -u debian-tor cat /var/lib/tor/onion_site/hostname

在 Debian 和 Ubuntu 上,列表应显示为 drwx------,所有者和组应为 debian-tor。如果权限范围更宽,tor 会记录类似 Permissions on directory /var/lib/tor/onion_site/ are too permissive. 的日志行,并且服务不会启动。使用 sudo chown -R debian-tor:debian-tor /var/lib/tor/onion_site 修复权限,再执行 sudo chmod 700 /var/lib/tor/onion_site,使用 sudo systemctl restart tor 重启,然后通过 sudo journalctl -u tor@default -n 30 查看结果。

请像备份私钥一样备份该目录:将备份保存到本机之外,并进行加密。切勿将其提交到保存站点内容的仓库中。如果同一台服务器还需要通过 Tor 进行管理访问,那么通过 onion 服务访问 SSH比在公网网站上暴露管理路径更容易隔离。

分析服务和第三方资源泄露的信息不止是请求头

这是最重要的部分,而且与 Onion-Location 无关。页面引用的每个第三方资源,都会使访问者的浏览器通过出口节点离开 onion 网络,并向明网发起请求。公共 CDN 中的字体、托管的分析脚本、嵌入式视频播放器、评论组件,都会向相应的第三方表明:有人正在加载您的页面,而且该访问会话是读者有意通过 Tor 路由的。

这会产生两个后果。第三方会知道有人访问了该页面。由于您的明网页面副本从相同的提供商加载相同的资源,能够看到任一侧的人都可以轻易将这两个站点关联起来。

从同一个源提供所有资源。自行托管字体。移除托管的分析标签,或将其迁移到您自己的服务器上;这样,在 VPS 上自行托管分析服务可以让请求留在 onion 网络内。应当预期 Tor Browser 的默认设置会阻止或弱化许多分析工具尝试收集的数据,这正是正确的结果。如果页面无法在没有第三方脚本的情况下运行,就不要在 onion 网络上发布该页面。

列出页面实际加载的资源:

curl -s --socks5-hostname 127.0.0.1:9050 http://<your-onion-address>.onion/ \
  | grep -oE '(src|href)="https?://[^"]+"' | sort -u

该命令输出的每一行都是页面要求浏览器获取的绝对 URL。任何不是您自己的 onion 地址的 URL,都是您要求读者代您发起的明网出站请求。

威胁模型:直截了当的说明

Onion-Location 让 onion 服务更容易被发现,仅此而已。它不会将您这名运营者匿名化,因为您的明网域名仍然关联着注册商记录、DNS 记录、发布在 Certificate Transparency 日志中的证书,以及一个背后包含您账单信息的 VPS 账户。它也不会将 onion 地址匿名化,因为您刚刚通过该明网域名发布了一项持久的公开声明,说明这两个地址属于同一个站点。读者可以从中受益:通过 Tor 访问的用户可以继续留在 Tor 网络中,路径上没有出口节点,也无需查询您域名的 DNS。如果您的目标是运行一个任何人都无法据此连接到您的 onion 服务,请不要发布此标头,也不要在同一台计算机上运行这两个副本。

这里会出现两个相关问题,而且它们各有答案。Tor 与 VPN 的区别决定您如何处理自己的流量,这是与发布内容不同的单独决策。如果受审查网络中的读者根本无法访问明网站点,他们就永远看不到此标头;在这种情况下,网桥和可插拔传输比本页的其他内容都更加重要。

完整验证一次配置

按以下顺序运行这些命令。每一步都会产生可查看的结果。

  1. curl -sI https://example.com/ | grep -i onion-location 输出响应头。
  2. 对返回 404 的 URL 运行同一命令,也会输出响应头。
  3. curl -s --socks5-hostname 127.0.0.1:9050 http://<your-onion-address>.onion/ 返回您的页面。
  4. 对该输出中的明网域名执行 grep,不会返回任何结果。
  5. Tor Browser 在 https://example.com 上显示 .onion available 标记。

如果第 1 步到第 4 步都通过,但第 5 步未通过,原因几乎总是响应头的提供位置,而不是响应头本身。确认浏览器确实加载了 HTTPS 页面,而不是使用了缓存的重定向。然后对您打开的确切 URL 运行 curl -sI,因为该路径上的 location 块可能会丢弃服务器级指令。

FAQ

为什么 Tor Browser 不显示“.onion available”提示?

首先检查文档中列出的3项要求。该值必须是完整 URL,使用 http:https: 方案,并包含 .onion 主机名。因此,不含方案的裸地址会失败,且不会输出错误。页面必须通过 HTTPS 提供服务,因此在端口 80 重定向块中设置的标头不会被读取。页面本身也不能是 onion 页面。完成这些检查后,再查看 nginx:匹配的 location 块中如果存在 add_header,就会丢弃服务器级别的所有 add_header;如果没有 always 标志,则 404 和 500 响应不会包含该标头。对浏览器中实际加载的确切 URL 运行 curl -sI,确认该标头确实已通过网络发送。

onion 站点需要 TLS 证书吗?

不需要。v3 onion 地址由服务的公钥派生,因此在发送任何 HTTP 请求之前,连接电路就已针对该特定服务完成身份验证,并进行端到端加密。在 onion 服务中使用普通 HTTP 是标准配置。必须避免的是在 onion 站点上提供 clearnet 证书。该证书的主题备用名称列表中包含您的域名,会在浏览器中触发名称不匹配警告,并向每位访问者确认两个站点运行在同一台机器上。

发布 Onion-Location 会使我的站点匿名吗?

不会。该标头表示您的 clearnet 域名公开声明某个 onion 地址属于您,任何人都可以获取它。受益的是访问者:他们可以切换到 onion 服务,从访问路径中移除出口节点和 DNS 查询。作为运营者,您不会因此获得匿名性,而且两个地址会被永久关联。不得让他人追踪到您的 onion 服务,应发布在其他位置,并使用与 clearnet 站点完全无共享的硬件。

可以使用 meta 标签代替 HTTP 标头吗?

可以。在无法设置响应标头时,通常静态主机会遇到这种情况。将 <meta http-equiv="onion-location" content="http://youraddress.onion" /> 放入文档头部。同样需要满足上述3项要求,因此页面必须使用 HTTPS,且不能是 onion 页面。唯一真正的区别是,该标签包含不带路径的固定地址,而 nginx 标头可以追加 $request_uri,让访问者在 onion 上打开同一个页面,而不是主页。