nginx에 Onion-Location 헤더 설정하여 Tor 연결하기
nginx 설정에 Onion-Location 헤더를 추가하여 Tor Browser 사용자가 onion 주소로 쉽게 전환하도록 유도하는 방법을 설명합니다. 헤더 인식 조건과 보안을 유지하며 리다이렉트 및 외부 리소스를 처리하는 실무 가이드를 제공합니다.
Onion-Location 헤더의 역할
Onion-Location 헤더는 클리어넷 vhost에 추가하는 한 줄의 설정으로, Tor Browser에 onion 주소를 알리는 역할을 합니다. Tor를 통해 https://example.com에 접속한 방문자는 주소창에서 .onion available이라고 적힌 보라색 알약 모양 아이콘을 보게 되며, 이를 클릭하면 즉시 onion 서비스로 이동합니다. 이는 단순한 발견 메커니즘일 뿐입니다. 이 헤더는 onion 서비스를 생성하지 않으며, 사용자의 정보를 숨겨주지도 않습니다.
이 가이드는 두 가지 요소가 이미 준비되어 있다고 가정합니다. nginx를 사용하는 VPS에 사이트가 있고, 이를 가리키는 v3 onion 서비스가 정상적으로 작동 중이어야 합니다. 만약 onion 서비스가 아직 없다면 먼저 구축해야 합니다. VPS에서 onion 사이트 호스팅하기 문서에서 torrc 설정과 첫 번째 hostname 파일 생성 방법을 다룹니다. 이어지는 내용은 두 서비스를 서로 노출하지 않으면서 연결하는 방법에 관한 것입니다.
Tor Browser가 헤더를 인식하기 위한 조건
The Tor Project는 세 가지 조건을 명시합니다. 이 세 가지가 모두 충족되어야만 알림 표시(pill)가 나타납니다.
Onion-Location값은http:또는https:스킴과.onion호스트 이름을 포함하는 유효한 URL이어야 합니다.- 헤더를 정의하는 웹 페이지는 반드시 HTTPS를 통해 제공되어야 합니다.
- 헤더를 정의하는 웹 페이지 자체가 onion 사이트여서는 안 됩니다.
두 번째 조건은 사용자가 자주 놓치는 부분이며, 세 번째 조건은 왜 onion vhost에 이 헤더를 설정하면 안 되는지를 설명합니다. 문서에는 명시되어 있지 않지만 구현상 존재하는 네 번째 규칙이 있습니다. Tor Browser는 최상위 문서(top-level document)에 대해서만 해당 헤더를 처리합니다. 브라우저는 로드 대상과 문서를 비교한 뒤 동작하므로, 스타일시트, 이미지, API 응답에서 반환된 헤더는 무시됩니다.
기본적으로 브라우저는 알림을 표시하고 사용자의 클릭을 기다립니다. 자동 전환을 원하는 사용자는 Settings, Privacy and Security, Onion Services 순으로 이동하여 "Prioritize .onion sites when known" 설정을 "Always"로 변경할 수 있습니다. 서버 측에서 이를 강제할 수는 없습니다. 이 헤더는 리다이렉트가 아니라 제안으로 간주해야 합니다.
nginx에 Onion-Location 헤더 추가하기
이 헤더는 클리어넷 도메인의 TLS를 종료하는 server 블록에 위치해야 합니다. 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 ...; } 블록은 그 하위의 모든 URL에 대해 서버 레벨의 Onion-Location 설정을 무시합니다. 위치별 헤더를 어디든 설정했다면, 해당 블록마다 Onion-Location 라인을 반복해서 작성해야 합니다. 이 동작이 생소하다면 nginx가 서버 및 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 메타 태그
정적 호스트와 일부 CDN 대시보드는 임의의 응답 헤더를 추가하는 기능을 제공하지 않습니다. 동일한 값을 문서 헤드 내의 메타 요소로 사용해도 브라우저가 HTTP를 통해 전달받은 데이터와 동일한 문서 헤더 데이터로 인식하므로 문제없이 동작합니다. 이는 http-equiv 태그와 같은 원리입니다.
<meta http-equiv="onion-location" content="http://<your-onion-address>.onion" />세 가지 요구 사항은 여전히 유효합니다. 태그가 포함된 페이지는 반드시 HTTPS여야 하며, onion 주소가 아니어야 합니다. 차이점은 서버 측에서 확장할 변수가 없으므로 태그에 경로가 없는 고정 주소 하나만 담아야 한다는 것입니다. 해당 태그를 포함한 모든 페이지는 onion 홈페이지를 제공하게 됩니다. 이것이 대체 방식의 제약 사항이므로, 서버를 직접 제어할 수 있는 환경이라면 항상 헤더 방식을 우선적으로 사용하십시오.
Onion 서비스를 별도의 nginx vhost로 제공하기
Clearnet 사이트와 onion 사이트는 서버 블록을 공유해서는 안 됩니다. Tor Browser는 Host: <your-onion-address>.onion을 전송합니다. 해당 이름을 요구하는 서버 블록이 없으면 nginx는 기본 서버(default 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로 설정되어 있기 때문이지만, 상대 경로 리다이렉트를 사용하면 이 문제를 완전히 제거할 수 있습니다.
왜 onion 페이지가 여전히 방문자를 클리어넷 사이트로 보내나요?
nginx가 유출의 원인인 경우는 드뭅니다. 대부분 애플리케이션이 원인입니다. 설정된 사이트 주소를 기반으로 절대 URL을 생성하는 모든 요소는 어떤 vhost가 요청을 처리했는지와 관계없이 도메인 이름을 노출합니다.
rel="canonical"에 지정된https://example.com/...을 가리키는 링크 태그. 이는 가장 흔한 사례이며, 소스 보기를 하는 누구에게나 정확한 클리어넷 페이지 주소를 노출합니다.- nginx가 아닌 프레임워크에서 생성하는 리다이렉트. Django의
SECURE_SSL_REDIRECT나 WordPress의home및siteurl옵션 등이 이에 해당합니다. og:url및 기타 소셜 카드 메타 태그.- 사이트맵 및 RSS 항목. 사양상 절대 경로를 사용합니다.
- 애플리케이션 오류 페이지. 일반적으로 동일한 설정에서 생성된 "홈페이지로 돌아가기" 링크를 포함합니다.
해결 방법은 사용하는 스택에 따라 다르며 공통된 해결책은 없습니다. 다만 점검 방법은 동일합니다. 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 이름을 해석할 수 없으므로 이 과정이 필수적입니다. 포트 9050은 패키지로 설치된 tor 데몬의 기본값입니다. 결과가 비어 있으면 통과입니다. 결과가 하나라도 나오면 onion 방문자에게 클리어넷 도메인을 노출하는 페이지가 있다는 뜻입니다. 홈페이지를 대상으로 실행한 뒤, 404 오류를 발생시키는 URL을 대상으로도 실행해 보십시오.
리다이렉트 체인은 별도로 확인하십시오. 리다이렉트 응답 본문은 대개 비어 있기 때문입니다.
curl -sI --socks5-hostname 127.0.0.1:9050 http://<your-onion-address>.onion/guides \
| grep -i '^location'example.com을 가리키는 Location 값은 리다이렉트가 onion 방문자를 Tor 내부에서 처리될 것이라 믿었던 요청을 출구 노드를 통해 클리어넷으로 다시 내보내고 있음을 의미합니다.
Onion 서비스에서 클리어넷 인증서를 제시하지 마십시오
v3 onion 주소는 서비스 자체의 공개 키에서 파생되므로, Tor는 HTTP 요청이 전송되기 전에 해당 특정 서비스에 대한 회선을 인증하고 암호화합니다. Onion 서비스 내부에서 일반 HTTP를 사용하는 것은 일반적인 구성이며, 이는 인터넷을 통한 일반 HTTP와는 다릅니다.
클리어넷 vhost를 복사하여 onion vhost를 구축하면 ssl_certificate도 함께 복사됩니다. 이제 onion 서비스는 주체 대체 이름(Subject Alternative Names) 목록에 example.com이 포함된 인증서를 제시하게 됩니다. 여기서 두 가지 문제가 발생합니다. 첫째, URL은 onion 주소인데 인증서가 이를 포함하지 않으므로 브라우저에 이름 불일치 오류가 표시됩니다. 둘째, 클릭하여 접속하는 모든 방문자는 이 두 사이트가 동일한 머신이라는 서명된 증명을 받게 됩니다. Onion vhost는 고유한 server_name를 사용하여 별도의 파일로 유지하십시오. 이렇게 하면 Certbot nginx plugin의 영향도 받지 않게 됩니다. 해당 플러그인은 인증서를 요청하는 도메인과 일치하는 서버 블록을 수정하기 때문입니다.
어떤 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에서 확인한 릴리스 코드네임으로 스위트를 대체하여 /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년 뒤에도 저장소 검증이 중단되지 않습니다. 하나의 소스만 선택하여 유지하십시오. 아카이브 패키지와 저장소 패키지는 버전이 다르며, 두 소스가 모두 활성화되어 있으면 업그레이드 시 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에서 소유자와 그룹이 debian-tor인 drwx------로 표시되어야 합니다. 권한이 이보다 넓게 설정되어 있으면 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 네트워크 밖으로 나가 출구 노드를 통해 클리어넷(clearnet)으로 요청을 보내는 것을 의미합니다. 공개 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 주소가 아닌 모든 것은 당신이 독자에게 대신 수행하도록 요청하는 외부 클리어넷 요청입니다.
위협 모델에 대한 명확한 설명
Onion-Location은 onion 서비스를 쉽게 찾을 수 있도록 돕는 기능이며, 그 역할은 그것이 전부입니다. 운영자인 귀하를 익명화하지는 않습니다. 귀하의 클리어넷 도메인에는 여전히 등록 기관 기록, DNS 기록, Certificate Transparency 로그에 게시된 인증서, 그리고 결제 정보가 포함된 VPS 계정이 연결되어 있기 때문입니다. 또한 onion 서비스 자체를 익명화하지도 않습니다. 귀하는 이미 해당 클리어넷 도메인을 통해 두 주소가 같은 사이트라는 사실을 공개적으로 선언했기 때문입니다. 이 기능의 이점은 사용자에게 돌아갑니다. Tor를 통해 접속한 사용자는 경로상에 exit node를 거치지 않고, 도메인에 대한 DNS 조회 없이도 Tor 네트워크 내부에서 머무를 수 있습니다. 누구도 연결할 수 없는 onion 서비스를 운영하는 것이 목표라면, 이 헤더를 게시하지 말고 한 대의 서버에서 두 개의 복사본을 실행하지 마십시오.
이와 관련하여 두 가지 질문이 제기되며 각각에 대한 답변은 다음과 같습니다. Tor와 VPN의 차이는 귀하의 트래픽을 위해 무엇을 사용할지 결정하는 문제이며, 이는 귀하가 무엇을 게시할지와는 별개의 결정입니다. 검열이 심한 네트워크의 사용자가 클리어넷 사이트에 전혀 접속할 수 없는 경우, 해당 사용자는 헤더를 볼 수 없습니다. 이러한 상황에서는 이 페이지의 그 어떤 내용보다 브리지와 플러그형 전송(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" 알림이 나타나지 않습니까?
먼저 문서화된 세 가지 요구 사항을 확인하십시오. 값은 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 서비스 내부에서 일반 HTTP를 사용하는 것이 일반적인 구성입니다. 주의할 점은 onion 사이트에서 클리어넷 인증서를 제시하지 않는 것입니다. 인증서의 주체 대체 이름(Subject Alternative Names)에 도메인이 나열되어 있으면 브라우저에서 이름 불일치 경고가 발생하며, 방문자에게 두 사이트가 동일한 서버에서 운영되고 있음을 알리게 됩니다.
Onion-Location을 게시하면 사이트가 익명화됩니까?
아니요. 해당 헤더는 특정 onion 주소가 귀하의 소유라는 것을 클리어넷 도메인에서 공개적으로 선언하는 것이며, 누구나 이를 가져갈 수 있습니다. 이점은 사용자가 onion으로 이동하여 경로에서 출구 노드와 DNS 조회를 제거할 수 있다는 점에 있습니다. 운영자로서 귀하는 익명성을 얻지 못하며, 두 주소를 영구적으로 연결하게 됩니다. 귀하의 신원과 연결되어서는 안 되는 onion 서비스는 클리어넷 사이트와 어떠한 자원도 공유하지 않는 별도의 하드웨어에서 게시해야 합니다.
HTTP 헤더 대신 메타 태그를 사용할 수 있습니까?
네, 정적 호스트와 같이 응답 헤더를 설정할 수 없는 상황에서 사용할 수 있습니다. 문서 헤드에 <meta http-equiv="onion-location" content="http://youraddress.onion" />을 삽입하십시오. 동일한 세 가지 요구 사항이 적용되므로 페이지는 반드시 HTTPS여야 하며 onion 사이트가 아니어야 합니다. 유일한 차이점은 태그는 경로가 없는 고정 주소만 담을 수 있는 반면, nginx 헤더는 $request_uri를 추가하여 방문자에게 홈 페이지가 아닌 동일한 페이지를 onion 주소로 제공할 수 있다는 점입니다.