nginxでOnion-Locationヘッダーを設定する方法
nginxでOnion-Locationヘッダーを公開し、Tor Browserにonionアドレスを表示させる方法です。リダイレクトや外部アセットによる情報漏えいも確認します。
Onion-Location ヘッダーの役割
Onion-Location ヘッダーは、clearnet の vhost に追加する 1 行の設定です。Tor Browser に onion アドレスを通知します。訪問者が https://example.com に Tor 経由でアクセスすると、アドレスバーに .onion available と表示された紫色のボタンが現れます。クリックすると onion service に移動します。これは検出のための仕組みであり、それ以外の機能はありません。このヘッダーによって onion service が作成されることはなく、サイトの情報が隠されることもありません。
このガイドでは、両方の構成がすでに存在することを前提とします。VPS 上で nginx の背後にサイトがあり、そこを指す v3 onion service も動作している状態です。まだ後者を用意していない場合は、先に構築してください。VPS で onion site をホスティングするでは、torrc の設定行と最初の hostname ファイルを説明しています。ここでは、一方の情報がもう一方に漏れないように、2 つを接続する方法を説明します。
Tor Browser がヘッダーを適用するための前提条件
Tor Project は3つの条件を示しています。3つすべてを満たさない限り、ピルは表示されません。
Onion-Locationの値は、http:またはhttps:スキームと.onionホスト名を持つ有効な URL でなければなりません。- ヘッダーを定義する Web ページは HTTPS で提供されなければなりません。
- ヘッダーを定義する Web ページ自体は onion サイトであってはなりません。
2つ目の条件でつまずくことが多く、3つ目の条件から、onion vhost ではこのヘッダーを設定してはならない理由が分かります。文書には記載されていませんが、実装には4つ目のルールがあります。Tor Browser はトップレベルドキュメントのヘッダーに対してのみ動作します。コードは処理を始める前に、読み込み対象とドキュメントを比較します。そのため、stylesheet、画像、API レスポンスで返されたヘッダーは無視されます。
デフォルトでは、ブラウザーはピルを表示し、クリックを待機します。自動的に移動する場合は、Settings、Privacy and Security、Onion Services の順に開き、「Prioritize .onion sites when known」を「Always」に設定します。これはサーバー側から強制できません。このヘッダーはリダイレクトではなく、選択肢の提示として扱ってください。
nginx に Onion-Location ヘッダーを追加する
このヘッダーは、clearnet ドメインの TLS 終端を担う server ブロックに記述します。代わりに port 80 のブロックへ記述しても何も起こりません。そのブロックはリダイレクトだけを返し、要件2により、通常の 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 に関する2つ目の注意点は継承です。設定を誤っても、エラーなしで動作するため気付きにくい問題です。add_header ディレクティブは、現在のレベルに add_header ディレクティブがない場合にだけ、1つ前の設定レベルから継承されます。そのため、location /assets/ { add_header Cache-Control ...; } ブロックがあると、その配下のすべての URL で server レベルの Onion-Location が破棄されます。どこかで location 単位のヘッダーを設定する場合は、それぞれのブロック内に Onion-Location の行を繰り返し記述します。この動作が初めての場合は、nginx が server ブロックと location ブロックを選択する仕組みを一度確認してください。
設定を reload し、通常のページと存在しないページの両方を確認します。
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: の行が出力されるはずです。2つ目のコマンドの出力が、always が機能している証拠です。2つ目のコマンドで何も出力されない場合は、flag が不足しているか、location ブロックによってディレクティブが上書きされています。
ヘッダーを設定できない場合の HTML meta タグ
静的ホストや一部の CDN ダッシュボードでは、任意のレスポンスヘッダーを追加できません。同じ値をドキュメントの head 内の meta 要素として指定できます。ブラウザーは、HTTP 経由で受け取った場合でも http-equiv タグとして記述されている場合でも、同じドキュメントヘッダーデータとして読み取るためです。
<meta http-equiv="onion-location" content="http://<your-onion-address>.onion" />3 つの要件は引き続き適用されます。このタグを含むページは HTTPS でなければならず、onion であってはなりません。異なる点は、サーバー側で展開する変数がないため、タグにパスを含まない固定アドレスを 1 つ指定することです。このタグを含むすべてのページが onion のホームページを提供します。これがフォールバックの代償であるため、サーバーを管理している場合はヘッダーを優先してください。
独自の nginx vhost から onion サイトを配信する
clearnet サイトと onion サイトで、同じ server block を共有してはいけません。Tor Browser は Host: <your-onion-address>.onion を送信します。その名前を受け持つ server block がない場合、nginx はデフォルトの server にフォールバックします。この server は clearnet vhost であるため、その vhost が生成するすべての URL にドメイン名が含まれます。
hidden service の転送先には、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 のアドレスをスキャンする相手がこのサイトを取得し、clearnet 側のコピーとバイト単位で比較することを防げます。absolute_redirect off により、nginx は相対 Location 値を返します。そのため、ディレクトリに対する末尾スラッシュへのリダイレクトは完全な URL ではなく Location: /guides/ を返します。nginx はすでに server_name ではなく Host ヘッダーから絶対リダイレクトを生成します。これは server_name_in_redirect のデフォルト値が off であるためです。ただし、相対リダイレクトを使えば、この問題自体をなくせます。
オニオンページが訪問者をclearnetサイトへ転送するのはなぜですか?
漏えい元がnginxであることはほとんどありません。原因はアプリケーションです。設定されたサイトアドレスから絶対URLを組み立てる処理は、どのvhostがリクエストを処理したかに関係なく、ドメイン名を指定します。
rel="canonical"のリンクタグがhttps://example.com/...を指している。最もよくある原因です。ソースを表示した人に、正確なclearnetページのURLが分かります。- nginxではなくフレームワークが生成するリダイレクト。たとえば、Djangoの
SECURE_SSL_REDIRECT、WordPressのhomeおよびsiteurlオプションです。 og:urlとその他のソーシャルカード用メタタグ。- SitemapとRSSのエントリ。仕様上、絶対URLを使用します。
- アプリケーションのエラーページ。通常は、同じ設定から生成した「ホームページに戻る」リンクを含みます。
修正方法は使用している構成によって異なり、汎用的な方法はありません。ただし、確認方法は共通です。Tor経由でオニオンページを取得し、レスポンス内をドメイン名で検索します。
curl -s --socks5-hostname 127.0.0.1:9050 http://<your-onion-address>.onion/ \
| grep -i 'example\.com'--socks5-hostname は名前をtorのSOCKSポートへ送り、名前解決を依頼します。これは、マシン上のどの処理も .onion 名をローカルで解決できないため必要です。パッケージ版のtorデーモンでは、ポート9050がデフォルトです。結果が空なら問題ありません。結果があれば、そのページがすべてのオニオン訪問者にclearnetドメインを渡しています。まずホームページに対して実行し、次に404を返すURLに対して実行します。
リダイレクトの本文は通常空であるため、リダイレクトチェーンは別に確認します。
curl -sI --socks5-hostname 127.0.0.1:9050 http://<your-onion-address>.onion/guides \
| grep -i '^location'Location の値が example.com を指定している場合、リダイレクトによってオニオンの訪問者がclearnetへ戻されています。訪問者はTor内部にとどまると考えていたリクエストで、exit nodeを経由して転送されます。
clearnet の証明書を onion で提示しない
v3 onion アドレスはサービス自身の公開鍵から導出されます。そのため、HTTP リクエストが送信される前に、Tor はその特定のサービスへの回線を認証し、暗号化します。onion service 内部で平文 HTTP を使用する構成は一般的です。これは、インターネット上で平文 HTTP を使用することとは異なります。
clearnet 用の vhost をコピーして onion 用の vhost を作成すると、ssl_certificate も一緒にコピーされます。その結果、onion は subject alternative names に example.com を列挙した証明書を提示します。ここで 2 つの問題が発生します。URL は onion アドレスであるのに、証明書がそのアドレスをカバーしていないため、ブラウザーに名前の不一致が表示されます。また、リンクをクリックして先に進んだすべての訪問者に対して、これら 2 つのサイトが同じマシンであることを示す署名済みの声明を渡すことになります。onion 用の vhost は専用ファイルに分離し、独自の server_name を使用してください。これにより、Certbot nginx プラグインによる変更も避けられます。このプラグインは、証明書を要求したドメインに一致する server block を編集するためです。
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/nulllsb_release -c で確認したリリースの codename に suite を置き換えて、/etc/apt/sources.list.d/tor.sources を記述します。
Types: deb deb-src
URIs: https://deb.torproject.org/torproject.org/
Suites: noble
Components: main
Signed-By: /usr/share/keyrings/deb.torproject.org-keyring.gpgsudo apt update
sudo apt install tor deb.torproject.org-keyringdeb.torproject.org-keyring パッケージによって署名キーが最新に保たれるため、1 年後もリポジトリの検証が停止しません。ソースは 1 つだけ選び、そのまま使用してください。アーカイブのパッケージとリポジトリのパッケージではバージョンが異なります。両方を有効にすると、apt は更新時に一方から他方へ移行します。
HiddenServiceDir にはサービスの identity が保存されます。そのディレクトリにある hs_ed25519_secret_key ファイルが onion address そのものです。アドレスは、その key pair の public half だからです。このファイルを失うと、アドレスは永久に失われます。再発行できる authority は存在しません。ファイルを不用意な場所にコピーすると、そのコピーを持つ人が onion service を実行できます。
tor は、他のユーザーが読み取れるディレクトリを使用しません。他の確認より先に、mode と owner を確認してください。
sudo ls -ld /var/lib/tor/onion_site
sudo -u debian-tor cat /var/lib/tor/onion_site/hostnameDebian と Ubuntu では、一覧に owner と group debian-tor の drwx------ と表示される必要があります。権限が広すぎる場合、tor は Permissions on directory /var/lib/tor/onion_site/ are too permissive. のような行をログに記録し、service は起動しません。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 で結果を確認します。
このディレクトリは、private key と同じ方法でバックアップしてください。box の外部に保存し、暗号化します。サイトを保持する repository には、決して commit しないでください。同じ box で Tor 経由の管理アクセスも必要な場合は、公開サイトに管理用の経路を公開するより、onion service 経由で SSH に接続するほうが適切に分離できます。
Analytics とサードパーティのアセットはヘッダー以上の情報を漏らします
ここが最も重要な点です。Onion-Location とは関係ありません。ページが参照するサードパーティのアセットはすべて、訪問者のブラウザーが onion から exit node を経由して clearnet へ送信するリクエストになります。公開 CDN のフォント、ホスト型の analytics スクリプト、埋め込み動画プレーヤー、コメントウィジェットなどです。これらはすべて、読者が Tor を介して意図的に接続したセッションで、誰かがあなたのページを読み込んでいることを各サードパーティに知らせます。
結果は2つあります。サードパーティが訪問を把握します。また、clearnet 側のコピーも同じプロバイダーから同じアセットを読み込むため、どちらか一方を確認できる人なら、2つのサイトを容易に関連付けられます。
すべてを同じ origin から配信します。フォントは自分でホストします。ホスト型の analytics タグを削除するか、自分のサーバーへ移してください。VPS 上のセルフホスト analytics にすれば、リクエストを onion 内に留められます。Tor Browser のデフォルト設定では、analytics ツールが収集しようとする情報の多くがブロックまたは制限されることを想定してください。それが正しい結果です。サードパーティスクリプトなしでは動作しないページは、onion で公開しないでください。
ページが実際に取得するものを一覧表示します。
curl -s --socks5-hostname 127.0.0.1:9050 http://<your-onion-address>.onion/ \
| grep -oE '(src|href)="https?://[^"]+"' | sort -uこのコマンドが出力する各行は、ページがブラウザーに取得を要求する絶対 URL です。自分の onion アドレス以外のものはすべて、読者に代わって取得させることになる外向きの clearnet リクエストです。
脅威モデルを明確に示す
Onion-Location は onion service を見つけやすくするだけです。それ以外の機能はありません。運用者としての匿名性は確保されません。clearnet ドメインには、レジストラの記録、DNS レコード、Certificate Transparency ログに公開された証明書、請求情報に紐付いた VPS アカウントが残るためです。onion も匿名化されません。その clearnet ドメインから、2 つのアドレスが同じサイトであることを示す永続的な公開情報を、今まさに公開したからです。利点を得るのは読者です。Tor 経由でアクセスした人は、経路に exit node を置かず、ドメインの DNS lookup も行わずに、Tor 内にとどまれます。誰も接続先を特定できない onion service が目的なら、この header を公開しないでください。また、2 つのコピーを同じマシンで実行しないでください。
ここでは、関連する 2 つの疑問が生じます。それぞれに答えがあります。Tor と VPN の違いによって、自分の traffic に何を使用するかを決めます。これは、何を公開するかとは別の判断です。また、検閲されたネットワークの読者が clearnet サイトにまったく到達できない場合、header も表示されません。そのため、このページの他の何よりも bridges と pluggable transports が重要になります。
セットアップ全体を一度確認する
次のコマンドを順番に実行します。それぞれ、確認できる結果が返ります。
curl -sI https://example.com/ | grep -i onion-locationはヘッダーを出力します。- 404 を返す URL に対して同じコマンドを実行しても、ヘッダーが出力されます。
curl -s --socks5-hostname 127.0.0.1:9050 http://<your-onion-address>.onion/はページを返します。- その出力を clearnet ドメインで grep しても、何も返りません。
https://example.comの Tor Browser に.onion availableのピルが表示されます。
手順 1 から 4 が成功し、手順 5 だけ失敗する場合、原因はほぼ常にヘッダー自体ではなく、ヘッダーの提供元にあります。ブラウザーがキャッシュされたリダイレクトではなく、実際に HTTPS ページを読み込んだことを確認してください。次に、開いた正確な URL に対して curl -sI を実行します。そのパス固有の location ブロックによって、サーバーレベルのディレクティブが破棄されている可能性があります。
FAQ
Tor Browser に「.onion available」ピルが表示されないのはなぜですか?
まず、文書化されている3つの要件を確認してください。値は http: または https: スキームと .onion ホストを含む完全な URL である必要があります。そのため、スキームのないアドレスだけを指定すると失敗しますが、エラーは表示されません。ページは HTTPS 経由で提供する必要があります。したがって、ポート 80 のリダイレクトブロックに設定したヘッダーは読み取られません。また、ページ自体が onion であってはなりません。その後、nginx の設定を確認します。一致する location ブロック内に add_header があると、サーバーレベルで設定したすべての add_header が破棄されます。また、always フラグがないと、404 および 500 応答にヘッダーが含まれません。ブラウザーで読み込んだ正確な URL に対して curl -sI を実行し、実際にヘッダーが送信されていることを確認してください。
onion サイトに TLS 証明書は必要ですか?
いいえ。v3 onion アドレスはサービスの公開鍵から導出されます。そのため、HTTP リクエストが送信される前に、接続経路は特定のサービスに対して認証され、エンドツーエンドで暗号化されます。onion service 内で平文の HTTP を使用する構成は一般的です。避けるべきなのは、clearnet 用の証明書を onion で提示することです。その Subject Alternative Names にはドメインが記載されているため、ブラウザーで名前不一致の警告が表示されます。また、2つのサイトが同じマシン上で稼働していることを、すべての訪問者に示すことになります。
Onion-Location を公開するとサイトは匿名になりますか?
いいえ。このヘッダーは、特定の onion アドレスが自分のものであることを clearnet ドメインから公に示すものであり、誰でも取得できます。得られる利点は読者側にあります。読者は onion に移動することで、経路から exit node と DNS lookup を除外できます。運用者側が匿名になることはなく、2つのアドレスは恒久的に関連付けられます。運用者を特定できないようにする必要がある onion service は、別の場所で公開してください。clearnet サイトと何も共有しないハードウェアを使用します。
HTTP ヘッダーの代わりに meta タグを使用できますか?
はい。静的ホストで一般的に発生するように、レスポンスヘッダーを設定できない場合に使用できます。ドキュメントの head に <meta http-equiv="onion-location" content="http://youraddress.onion" /> を配置してください。同じ3つの要件が適用されるため、ページは HTTPS で提供し、onion であってはなりません。実質的な違いは、タグにはパスのない固定アドレスを指定する点です。一方、nginx のヘッダーでは $request_uri を追加できるため、訪問者にホームページではなく onion 上の同じページを表示できます。