TECH 으로 돌아가기
TECH HACKER NEWS 오늘 7분 읽기 29 READS

ngrok 없이 OpenSSH와 Nginx만으로 만드는 셀프호스팅 HTTP 터널

ngrok 없이 OpenSSH와 Nginx만으로 만드는 셀프호스팅 HTTP 터널
SOURCE IMAGE · HACKER NEWS

로컬에서만 돌아가는 개발 서버를 외부에 잠깐 공유해야 할 때가 있다. 작성 중인 블로그 글의 미리보기가 localhost:8080에서만 뜨는데 동료에게 교정을 부탁하고 싶은 상황이 대표적이다. 이런 용도로 ngrok이나 Cloudflare Quick Tunnels 같은 상용 서비스, 혹은 frp·localtunnel처럼 전용 클라이언트가 필요한 셀프호스팅 도구가 흔히 쓰인다. sish처럼 평범한 SSH 클라이언트만으로 접속되지만 특정 SSH 서버를 요구하는 방식도 있다. Vincent Bernat은 이 모든 추가 소프트웨어를 걷어내고, 이미 서버에서 돌아가고 있는 OpenSSH와 nginx만으로 공유 가능한 HTTPS URL을 만드는 방법을 정리했다.

기본 구조: SSH 원격 포워딩 + nginx 프록시

핵심 아이디어는 단순하다. SSH의 원격 포트 포워딩(remote forwarding)으로 원격 서버의 한 포트를 로컬 서비스로 연결한다. 이때 원격 포트를 0으로 지정하면 서버가 비어 있는 포트를 자동으로 할당해 준다. 그다음 nginx가 가령 https://p41535.ssh.luffy.cx 로 들어온 요청을 http://127.0.0.1:41535 로 프록시하도록 설정한다. 이를 위해 *.ssh.luffy.cx 에 대한 DNS 레코드와 Let's Encrypt 와일드카드 인증서가 필요하다. 저자는 Route 53에 호스팅한 별도 존(acme.luffy.cx)을 ACME DNS-01 챌린지에 사용하며, NixOS 환경에서는 인증서가 자동으로 발급·갱신된다.

이 구성만 놓고 보면 콘텐츠의 기밀성을 지켜 주는 유일한 '비밀'은 포트 번호뿐이다. 다른 포워딩 솔루션들이 도메인 이름에 임의의 문자열을 덧붙여 외부인이 가능한 값을 열거(enumeration)하지 못하게 막는 것과 대비된다. 포트 번호는 경우의 수가 많지 않아, 이 상태로는 운이 나쁘면 제3자가 주소를 추측해 들어올 여지가 있다.

secure_link 모듈로 만료되는 토큰 붙이기

저자는 여기에 nginx의 ngx_http_secure_link_module을 더해 보안을 강화한다. 이 모듈은 비밀값을 포함한 여러 값에 대해 해시를 계산하고, 요청에 담긴 해시와 비교한다. 해시는 base64로 인코딩되는데, 도메인 이름은 대소문자를 구분하지 않으므로 해시를 서브도메인에 넣을 수 없다. 그래서 해시와 만료 타임스탬프를 URL의 사용자명(username) 자리에 담는 방식을 택했다. 클라이언트는 HTTP 기본 인증(basic authentication)으로 이 사용자명을 서버에 전달하며, curl을 비롯한 대부분의 HTTP 클라이언트가 이를 지원한다.

동작 흐름은 이렇다. nginx는 사용자명을 $remote_user 변수로 노출한다. 모듈은 해시와 만료 타임스탬프가 쉼표로 구분된 형태를 기대하므로, map 지시자로 $remote_user에서 두 부분을 추출해 쉼표로 다시 결합한다. 해시 대상 문자열에는 만료 타임스탬프, 포트, 그리고 비밀값이 들어간다. 검증 결과는 $secure_link 변수로 반환된다. 해시가 틀리거나 없으면 WWW-Authenticate 헤더와 함께 401을 돌려주어 자격 증명을 요구하고, 링크가 만료된 경우에는 410을 반환한다. 실제 프록시 단계에서는 Authorization 헤더를 제거한 뒤 요청을 전달하며, WebSocket 연결을 위한 지시자도 함께 넣는다.

할당된 포트를 찾아내는 헬퍼 스크립트

여기까지 읽으면 자연스럽게 "그럼 해시는 어떻게 생성하느냐"는 의문이 든다. 저자는 이 과정을 자동화하는 헬퍼 스크립트를 제시하는데, 가장 까다로운 부분이 OpenSSH가 할당한 임시 포트를 알아내는 일이다. 이 포트 번호는 어떤 환경 변수에도 나타나지 않기 때문이다. 우회책으로 상위 sshd-session 프로세스들을 거슬러 찾아 올라간 뒤, 해당 프로세스들이 열고 있는 리스닝 포트를 조회한다. 그렇게 알아낸 포트로 공유용 URL을 만들어 출력하고 세션을 열린 채 유지한다.

저자는 이 스크립트를 서버에 http-over-ssh라는 이름으로 설치하고 ~/.ssh/config에 관련 항목을 등록해, 짧은 명령 하나로 터널과 공유 URL을 동시에 얻는다. NixOS 사용자를 위해서는 http-over-ssh.nix 형태의 설정도 함께 공개한다.

실무적 의미와 한계

이 접근의 가장 큰 장점은 의존성이 없다는 점이다. 상용 서비스에 트래픽을 흘려보내지 않고, 전용 클라이언트나 추가 데몬을 깔 필요도 없다. 이미 SSH와 nginx가 돌아가는 서버를 가진 사람이라면 사실상 설정 작업만으로 자기 통제 아래 있는 터널을 손에 넣는다. secure_link로 만료 토큰까지 붙이면 포트 추측만으로 노출되던 약점도 상당히 보완된다. 공유 링크에 수명을 부여할 수 있다는 점은 교정 요청처럼 '잠깐만 열어 두는' 용도에 잘 맞는다.

반면 한계도 분명하다. 이 방식은 와일드카드 DNS 레코드, 와일드카드 인증서, DNS-01 챌린지를 처리할 수 있는 외부에서 접근 가능한 서버를 전제로 한다. 인프라를 직접 운영하지 않는 사람에게는 ngrok을 한 번 실행하는 편이 여전히 간편하며, 저자 역시 이 점을 농담조로 인정한다. 또 임시 포트를 프로세스 트리에서 역추적해 찾는 방식은 OpenSSH가 그 값을 깔끔하게 노출하지 않는 데서 비롯된 임기응변이어서, 환경에 따라 견고함을 검증해 둘 필요가 있다. 결국 이 글의 가치는 완제품 도구라기보다, 손에 이미 있는 표준 구성요소를 조합해 외부 서비스 의존을 걷어내는 사고방식을 보여 주는 데 있다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://vincent.bernat.ch/en/blog/2026-http-over-ssh
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기 →
처리 중...