SSH 포트 없이 Tor onion 서비스로 서버 접속하기
VPS의 22번 포트를 닫고 Tor onion 서비스를 통해 SSH에 접속하는 방법을 설명합니다. v3 클라이언트 인증 설정과 부팅 시 자동 시작 구성, 그리고 접속 차단을 방지하기 위한 단계별 검증 순서를 상세히 안내합니다.
Tor onion 서비스를 통한 SSH 연결의 변화
Tor onion 서비스를 통해 SSH를 사용하면 어떠한 포트로도 인바운드 연결을 허용하지 않는 VPS를 관리할 수 있습니다. 서버는 Tor 네트워크로 아웃바운드 연결을 시도하고 해당 연결을 유지합니다. SSH 세션은 이 연결을 통해 전달되므로, 공인 IP 주소에서 대기(listen) 중인 포트가 없어도 됩니다.
로그에 나타나는 효과는 즉각적입니다. 공인 SSH 포트가 열려 있는 서버는 스캐너로부터 매일 수천 건의 비밀번호 무차별 대입 시도를 기록합니다. sshd을 onion 서비스 뒤로 옮기고 방화벽에서 인바운드 트래픽을 차단하면, /var/log/auth.log에는 사용자가 직접 시작한 세션만 기록됩니다.
단점은 모든 관리자 세션 경로에 tor가 위치한다는 점입니다. 이는 사용자 공간 데몬이므로, 재부팅 후 로그인하기 전에 반드시 시작되고 부팅(bootstrap) 과정을 거쳐야 합니다. 포트를 닫기 전에 이 점을 고려하십시오. 물리적으로 접근할 수 없는 장비에 대한 접속 권한을 잃는 것이 이 방식의 실패 모드이기 때문입니다.
작업 전 복구 경로 확보하기
SSH를 사용하지 않는 복구 경로를 확보하기 전까지는 작업을 시작하지 마십시오.
지금 바로 서비스 제공업체의 제어판에서 VNC 또는 시리얼 콘솔을 열고 로그인하십시오. root 비밀번호를 모르는 경우, 먼저 제어판에서 root 비밀번호를 재설정하고 정상 작동하는지 확인하십시오. 한 번도 테스트하지 않은 콘솔은 복구 경로로 간주할 수 없습니다.
아래 순서는 중요합니다. 각 단계는 다음 단계로 넘어가기 전에 검증되어야 하며, onion 라우팅이 작동할 때까지 22번 포트는 열어 두어야 합니다.
- tor를 설치하고 부트스트랩이 완료되는지 확인합니다.
- onion 서비스를 정의하고 주소를 확인합니다.
- 22번 포트가 열려 있는 상태에서 onion 주소를 통해 접속합니다.
- 클라이언트 인증을 추가한 뒤 다시 접속합니다.
sshd을(를) 루프백 인터페이스에 바인딩하고 22번 포트를 닫습니다.- 재부팅 후 onion 주소를 통해 다시 접속합니다.
전체 과정 동안 현재의 SSH 세션을 계속 열어 두십시오. 이미 연결된 세션은 새로운 연결을 차단하는 방화벽 변경 사항이 적용되어도 유지되므로, 첫 번째 구조 수단이 됩니다.
서버에 tor 설치하기
Ubuntu는 자체 저장소에서 tor를 제공하지만, 해당 빌드는 버전이 뒤처지는 경우가 많습니다. The Tor Project 저장소는 문서에서 설명하는 최신 버전을 포함하고 있습니다. apt 저장소 가이드의 명령어를 사용하여 저장소를 추가하십시오.
sudo apt update
sudo apt install -y apt-transport-https wget gpg
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을 작성하십시오. Suites는 릴리스 코드네임을 사용하며, 이는 lsb_release -cs 명령어로 확인할 수 있습니다(Ubuntu 24.04에서는 noble가 출력됩니다).
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 -y tor deb.torproject.org-keyring
sudo journalctl -u tor@default -n 20 --no-pager로그는 Bootstrapped 100% (done)로 끝나야 합니다. 이 아래에서 멈춘다면 tor가 네트워크에 연결할 수 없는 상태이며, 이는 거의 항상 아웃바운드 방화벽 규칙 문제이거나 시스템 시간이 잘못 설정된 경우입니다.
유닛 이름에 주의하십시오. systemctl status tor은 모든 것이 정상일 때도 Active: active (exited)을 보고합니다. Debian과 Ubuntu는 tor를 다중 인스턴스 마스터 유닛으로 패키징하며, 이 유닛의 유일한 역할은 실제 인스턴스를 불러오는 것이기 때문입니다. 데몬 자체는 tor@default.service로 실행됩니다. status 및 journalctl을 사용할 때는 이 이름을 사용하십시오. tor의 시작, 중지, 재로드는 실제 인스턴스에 전달되므로 sudo systemctl reload tor는 예상대로 작동합니다.
포트 22에 대한 onion 서비스 정의
/etc/tor/torrc에 다음 두 줄을 추가합니다.
HiddenServiceDir /var/lib/tor/ssh/
HiddenServicePort 22 127.0.0.1:22두 번째 줄은 Tor에게 onion 주소의 가상 포트 22를 수락하고 서버의 127.0.0.1:22로 연결하도록 지시합니다. Tor는 loopback을 통해 sshd에 도달하며, 바로 이 때문에 나중에 sshd가 공용 주소에서 수신 대기하지 않도록 설정할 수 있습니다. 이 두 번째 줄을 대신 127.0.0.1:80의 웹 서버로 지정하면 동일한 두 지시어로 onion 주소에 사이트 게시가 가능하며, 이는 Tor 설치 후 운영하기 유용한 두 번째 서비스입니다.
sudo systemctl reload tor
sudo cat /var/lib/tor/ssh/hostname이 명령은 56자의 base32 문자와 그 뒤에 .onion을 출력합니다. 이 문자들은 인코딩된 형태의 서비스 공개 키입니다. 이 과정에는 인증 기관이나 별도의 이름 등록 절차가 존재하지 않습니다.
/var/lib/tor/ssh/은 Tor가 직접 생성하게 하십시오. 사용자가 직접 생성할 때 소유자가 잘못되었거나 권한이 0700보다 느슨하면 Tor는 사용을 거부하며, 저널에는 해당 디렉터리의 권한이 너무 개방적이라는 오류가 기록됩니다. 내부 파일들은 서비스의 식별자이며, hs_ed25519_secret_key이 곧 주소 그 자체입니다. 이 디렉터리는 600 권한으로 백업하여 서버 외부의 안전한 곳에 보관하십시오. 이 파일을 분실하면 새로운 주소를 생성해야 하며 모든 클라이언트의 설정을 수정해야 합니다.
워크스테이션에서 연결하기
워크스테이션에는 Tor 클라이언트가 필요하며, 별도의 설정은 필요하지 않습니다. Debian이나 Ubuntu에서는 sudo apt install -y tor netcat-openbsd를 사용합니다. Tor는 127.0.0.1:9050에서 SOCKS5 프록시로 대기합니다. SOCKS는 범용 프록시 프로토콜이며, 버전 5는 IP 주소 대신 호스트 이름을 전달할 수 있는데 이 부분이 여기서 핵심입니다.
OpenSSH에는 자체적인 SOCKS 클라이언트가 없으므로 보조 프로그램을 사용하여 연결합니다. ~/.ssh/config에 다음 내용을 추가하십시오.
Host myvps
HostName xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.onion
User admin
ProxyCommand /usr/bin/nc -X 5 -x 127.0.0.1:9050 %h %p
ServerAliveInterval 30-X 5는 SOCKS5를 선택하고, -x 127.0.0.1:9050은 로컬 Tor를 가리킵니다. %h는 onion 이름을 Tor에 이름 형태로 전달하므로, Tor가 네트워크 내부에서 이를 해석합니다. 이때 반드시 OpenBSD netcat을 사용해야 합니다. GNU netcat에는 -X 옵션이 없으며, 사용 시 nc: invalid option -- 'X' 오류와 함께 중단됩니다.
ssh myvpsTor가 회로를 먼저 구성해야 하므로 첫 연결은 느립니다. 호스트 키 지문은 다른 곳에서와 마찬가지로 수락하십시오. 이후부터는 일반적인 SSH 키 관리 방식이 그대로 적용됩니다. 전송 계층은 변경되었지만, 인증 방식은 동일합니다.
일회성 연결이라면 설정 항목을 추가하지 않아도 됩니다. torsocks ssh admin@xxxxx.onion 명령으로 동일한 작업을 수행할 수 있습니다.
v3 클라이언트 인증 추가
현재 상태에서는 주소를 아는 사람이라면 누구나 SSH 배너에 접근하여 추측을 시도할 수 있습니다. Onion 주소는 디렉터리 시스템에서 열거할 수 없으므로 주소 자체가 비밀처럼 작동하지만, 셸 기록이나 git 저장소에 커밋된 설정 파일 등을 통해 평범한 방식으로 유출될 수 있습니다. 클라이언트 인증은 이러한 격차를 메워줍니다. 서비스는 클라이언트 키로 암호화된 디스크립터를 게시하므로, 주소만 알고 키가 없는 사람은 서비스의 위치조차 파악할 수 없습니다.
클라이언트에서 x25519 키 쌍을 생성합니다. 다음은 Tor Project의 클라이언트 인증 가이드에서 가져온 파이프라인이며, 한 가지 변경 사항이 있습니다.
openssl genpkey -algorithm x25519 -out /tmp/k1.prv.pem
grep -v " PRIVATE KEY" /tmp/k1.prv.pem | base64 -d | tail --bytes=32 | base32 | sed 's/=//g' > /tmp/k1.prv.key
openssl pkey -in /tmp/k1.prv.pem -pubout | grep -v " PUBLIC KEY" | base64 -d | tail --bytes=32 | base32 | sed 's/=//g' > /tmp/k1.pub.key위 명령어의 게시된 버전은 base64pem -d을 사용하지만, 일반적인 Ubuntu 설치 환경에는 포함되어 있지 않습니다. 따라서 해당 명령어는 base64pem: command not found 오류와 함께 중단됩니다. GNU base64 -d은 동일한 PEM 본문을 디코딩할 수 있으므로 대신 사용하십시오.
서버에 공개 키를 설치합니다.
sudo install -d -m 700 -o debian-tor -g debian-tor /var/lib/tor/ssh/authorized_clients
echo "descriptor:x25519:PASTE_PUBLIC_KEY_HERE" | sudo tee /var/lib/tor/ssh/authorized_clients/laptop.auth >/dev/null
sudo chown debian-tor:debian-tor /var/lib/tor/ssh/authorized_clients/laptop.auth
sudo systemctl reload tor.auth로 끝나는 파일만 읽습니다. laptop.auth.txt로 저장하면 tor는 오류 메시지 없이 파일을 무시하며, 서비스는 주소를 아는 모든 사람에게 계속 열려 있게 됩니다.
클라이언트에서 개인 키를 설치합니다. Ubuntu에서 tor 데몬은 debian-tor 사용자로 실행되므로 홈 디렉터리의 파일을 읽을 수 없습니다. 따라서 해당 사용자가 접근할 수 있는 위치에 디렉터리를 유지하십시오.
sudo install -d -m 700 -o debian-tor -g debian-tor /var/lib/tor/onion_auth
echo "ADDRESS_WITHOUT_DOT_ONION:descriptor:x25519:PASTE_PRIVATE_KEY_HERE" | sudo tee /var/lib/tor/onion_auth/myvps.auth_private >/dev/null
sudo chown debian-tor:debian-tor /var/lib/tor/onion_auth/myvps.auth_private
sudo chmod 600 /var/lib/tor/onion_auth/myvps.auth_private클라이언트의 /etc/tor/torrc에 ClientOnionAuthDir /var/lib/tor/onion_auth를 추가하고 tor를 다시 로드합니다. 만약 macOS의 Homebrew 빌드처럼 본인 계정으로 tor를 실행 중이라면, ClientOnionAuthDir을 모드 0700으로 설정된 ~/.tor/onion_auth로 지정하십시오.
해당 파일 내부의 주소는 .onion 접미사를 제외한 56자입니다. 완료되면 /tmp/k1.prv.pem와 /tmp/k1.prv.key을 삭제하십시오.
이제 양방향으로 테스트합니다. ssh myvps은 여전히 연결되어야 합니다. 키가 없는 장치에서 동일한 주소로 접속하면 실패해야 합니다. 그 실패가 바로 인증이 활성화되었다는 증거입니다.
포트 22 닫기, 다음 순서로 진행
먼저 안전장치를 설정합니다. 아래의 두 변경 사항을 적용한 뒤, 만약 접속이 차단되더라도 15분 후에 자동으로 설정을 되돌리는 명령어입니다.
sudo systemd-run --on-active=15m --unit=ssh-rescue \
/bin/sh -c 'ufw allow 22/tcp; rm -f /etc/systemd/system/ssh.socket.d/override.conf; systemctl daemon-reload; systemctl restart ssh.socket'onion 라우팅이 정상적으로 작동함을 확인했다면 sudo systemctl stop ssh-rescue.timer를 실행하여 안전장치를 취소합니다.
다음으로, 공용 주소에서 sshd가 대기하지 않도록 설정합니다. Ubuntu 24.04는 소켓 유닛을 통해 ssh를 활성화하므로, sshd_config 내의 ListenAddress 설정은 무시됩니다. sshd가 아닌 ssh.socket가 리스닝 소켓을 점유하기 때문입니다. 현재 어떤 방식을 사용 중인지 확인하십시오.
systemctl is-enabled ssh.socket출력 결과가 enabled이라면 sudo systemctl edit ssh.socket을 실행하고 다음 내용을 추가하십시오.
[Socket]
ListenStream=
ListenStream=127.0.0.1:22비어 있는 ListenStream=은 패키지 유닛에서 상속된 값을 초기화합니다. 이 줄을 생략하면 기존 공용 리스너가 유지된 상태에서 두 번째 리스너가 추가되는데, 이는 이 단계에서 가장 흔히 발생하는 실수입니다.
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
ss -tlnp | grep ':22'ss를 실행하면 127.0.0.1:22이 표시되어야 하며 0.0.0.0:22에는 아무것도 없어야 합니다. 만약 ssh.socket가 비활성화된 상태였다면 /etc/ssh/sshd_config.d/10-onion.conf에 ListenAddress 127.0.0.1을 넣고 sudo systemctl restart ssh를 실행한 뒤, 동일한 ss 명령어로 다시 확인하십시오. 해당 출력 결과가 두 경우 모두에 대한 증명입니다.
이제 방화벽을 설정합니다. 이는 일반적인 VPS에서의 ufw 규칙 관리와 같습니다. 먼저 sudo ufw status numbered을 실행하여 나열된 SSH 규칙 중 삭제할 항목을 확인하십시오.
sudo ufw status numbered
sudo ufw delete allow OpenSSH
sudo ufw default allow outgoing
sudo ufw default deny incoming
sudo ufw status verbose나가는 트래픽은 허용 상태로 두십시오. Tor는 443이나 9001 같은 포트를 통해 릴레이에 연결됩니다. 따라서 기본 거부(default-deny) 정책을 적용하면 Tor 부트스트래핑이 중단되며, 동시에 유일한 접속 경로까지 차단됩니다. 대부분의 제공업체는 제어판에서 별도의 네트워크 방화벽을 운영합니다. 그곳에서도 22번 포트를 닫아야 합니다. 그렇지 않으면 ufw 설정과 관계없이 포트가 외부에서 계속 노출될 수 있습니다.
이 서버에서 Docker를 실행 중이라면 작업을 완료하기 전에 게시된 포트를 확인하십시오. Docker는 자체 규칙을 동일한 테이블에 직접 작성하며 ufw를 우회하여 컨테이너 포트를 게시하므로, ufw의 거부 정책만으로는 전체 상황을 파악할 수 없습니다.
신뢰하기 전에 재부팅하십시오
systemctl is-enabled tor@default
sudo reboot첫 번째 명령에서 서비스가 활성화된 것으로 나타나지 않으면 재부팅하기 전에 sudo systemctl enable tor@default을 실행하십시오. 2분 정도 기다린 후 ssh myvps를 실행하십시오. Tor는 부팅 후 부트스트랩 과정을 거쳐야 하므로, 기기가 켜진 후 일정 시간이 지나야 onion 주소로 응답이 시작됩니다.
서비스가 다시 시작되지 않으면 콘솔을 열고 sudo journalctl -u tor@default -b을 확인하십시오. torrc 구문 오류나 디렉터리 권한 문제가 그곳에 출력됩니다. torrc를 적용하기 전에 편집 내용을 미리 검토할 수도 있습니다.
sudo -u debian-tor tor --verify-configWireGuard 터널과 비교했을 때의 비용
직접 구축한 VPS 기반의 WireGuard VPN과 비교하면, onion 서비스는 속도가 느리고 예측 가능성이 떨어집니다. 이 방식을 선택하기 전에 감수해야 할 트레이드오프를 명확히 이해해야 합니다.
지연 시간(Latency). 클라이언트 회로는 3개의 릴레이를 거치며, 서비스 측에서도 3개의 릴레이를 추가로 거칩니다. 따라서 사용자의 키 입력은 전 세계에서 무작위로 선택된 약 6대의 장비를 통과하게 됩니다. 대화형 타이핑에는 눈에 띄는 지연이 발생하며, 파일 복사 속도도 느립니다. 반면 WireGuard는 1개의 홉만 추가합니다. time ssh myvps 'echo ok'을 사용하여 직접 측정해 보십시오. 지연 시간은 tor가 구성한 회로에 따라 달라지며, tor가 회로를 새로 구성할 때마다 변하기 때문입니다.
경로상에 존재하는 사용자 공간 데몬. WireGuard는 커널에서 동작하며 네트워크와 함께 시작됩니다. 반면 Tor는 시작 후 부트스트랩 과정을 거쳐 가드 릴레이에 연결되어야만 통신이 가능합니다. Tor가 실패하면 제공자의 콘솔에 직접 접속해야 합니다.
시계 정확도. Onion 서비스 설명자는 특정 시간대를 기준으로 게시되므로, 시스템 시계가 크게 틀어지면 주소 조회에 실패하며 어디에서도 명확한 오류 메시지를 확인할 수 없습니다. timedatectl는 System clock synchronized: yes을 보고해야 합니다.
이러한 불편을 감수하고 얻는 이점은 방화벽 규칙의 정확성에 의존하지 않는 노출 방지입니다. 스캔할 포트도, 배너를 탈취할 대상도 없습니다. 주소 자체가 공개 키이므로 SSH가 시작되기도 전에 엔드포인트가 자신의 신원을 증명합니다.
실무적인 관점에서는 두 가지를 모두 사용하는 것이 좋습니다. 일상적인 경로로는 WireGuard를 사용하고, WireGuard 설정에 문제가 생겼을 때를 대비한 비상 경로로 onion 서비스를 유지하십시오. 이렇게 하면 공개 SSH 포트를 여는 대신 하나의 UDP 포트만 열어두는 상태가 됩니다. 물론 이 모든 과정이 sshd 자체의 보안 강화를 대신할 수는 없습니다. 키 기반 인증과 비루트(non-root) 계정 로그인은 여전히 중요합니다. onion 서비스는 네트워크 경로를 보호할 뿐, 그 이후의 보안까지 책임지지는 않기 때문입니다.
실패 유형 및 확인되는 오류
Tor가 Bootstrapped 0%를 통과하지 못합니다. 아웃바운드 트래픽이 차단되었거나 시스템 시간이 크게 어긋난 경우입니다. sudo ufw status verbose로 아웃바운드 정책을 확인한 후 timedatectl을 실행하십시오.
systemctl status tor에서 active (exited) 메시지가 출력됩니다. Debian 및 Ubuntu에서는 정상적인 현상입니다. 대신 tor@default를 읽어보십시오.
설명자(descriptor)를 찾을 수 없습니다. Tor가 SOCKS 확장 오류 F0인 "Onion Service Descriptor Can Not be Found"를 반환합니다. 설명자가 아직 게시되지 않았거나(재시작 후 잠시 시간이 소요됨), 서버에서 tor가 실행 중이지 않은 경우입니다.
F4, "Onion Service Missing Client Authorization". 클라이언트에 tor가 사용할 수 있는 일치하는 .auth_private이 없습니다. torrc에 ClientOnionAuthDir이 포함되어 있는지, 디렉터리 권한이 0700인지, 파일 이름이 .auth_private로 끝나는지, 그리고 debian-tor이 해당 파일을 읽을 수 있는지 확인하십시오.
F5, "Onion Service Wrong Client Authorization". 개인 키가 서버의 .auth 파일과 일치하지 않습니다. base32 문자열 내부에 포함된 후행 =나 불필요한 줄 바꿈이 원인일 수 있습니다.
nc: invalid option -- 'X'. OpenBSD 버전이 아닌 GNU netcat이 설치된 상태입니다. sudo apt install -y netcat-openbsd을 실행하십시오.
Could not resolve hostname. ssh가 일반 DNS를 시도했으나 .onion에 대한 응답을 찾지 못해 ProxyCommand이 실행되지 않았습니다. ~/.ssh/config의 Host 패턴이 입력한 이름과 일치하지 않는 경우입니다.
Permission denied (publickey). 터널은 정상적으로 작동했으며 tor의 역할은 끝났습니다. 이 문제는 일반적인 publickey 권한 거부 문제로 간주하고 tor와는 별개로 해결하십시오.
FAQ
Onion service를 사용하면 VPS에 열린 포트가 전혀 없게 됩니까?
네, sshd를 127.0.0.1에 바인딩하고 방화벽에서 인바운드 트래픽을 차단하면 그렇습니다. Tor는 릴레이로 아웃바운드 TCP 연결을 생성하고 세션이 그 경로를 통해 돌아오므로, 서버의 공인 IP 주소로 들어오는 연결을 수락하는 프로세스는 존재하지 않게 됩니다. 서버에서 ss -tlnp를 실행하고 외부에서 포트 스캔을 수행하여 이를 확인할 수 있습니다. ufw와는 별개로 제어판에서 관리하는 클라우드 제공업체의 네트워크 방화벽도 반드시 닫아야 한다는 점을 잊지 마십시오.
SSH 보안을 위해 .onion 주소만으로 충분합니까?
아닙니다. 주소는 56자이며 디렉터리 시스템에서 추측하거나 열거할 수 없으므로 비밀번호처럼 작동하지만, 셸 히스토리나 설정 파일에 노출될 위험이 있습니다. v3 클라이언트 인증을 추가하십시오. 이를 사용하면 서비스 설명자가 클라이언트 키로 암호화되므로, 주소만 알고 있는 공격자는 확장 오류 F4를 받게 되며 sshd에 도달할 수 없습니다.
재부팅 후 tor가 시작되지 않으면 어떻게 됩니까?
onion 주소가 유일한 접속 경로이므로 SSH 접근이 완전히 차단됩니다. 이것이 바로 포트 22를 닫기 전에 제공업체의 콘솔 접속을 반드시 테스트해야 하는 이유입니다. 또한 Tor는 부팅 후 부트스트랩 과정에 시간이 걸리므로, 서버가 ping에 응답하더라도 onion 주소는 즉시 응답하지 않을 수 있습니다. 만약 끝까지 응답이 없다면 콘솔로 로그인하여 sudo journalctl -u tor@default -b을 확인하십시오. torrc 구문 오류나 /var/lib/tor/ssh의 권한 문제 등이 기록되어 있을 것입니다.
SSH over Tor가 WireGuard보다 느립니까?
네, 훨씬 느립니다. onion 서비스 연결은 무작위로 선택된 약 6개의 릴레이를 거치지만, WireGuard는 서버까지 한 번의 암호화된 홉으로 직접 연결됩니다. 따라서 타이핑 시 지연 시간이 느껴지고 전송 속도도 느립니다. 일반적인 구성 방식은 일상적인 작업에는 WireGuard를 사용하고, VPN 설정이 깨졌을 때를 대비한 비상 경로로 onion 서비스를 유지하는 것입니다.