Published on

ssh 터널링 총정리: 로컬·원격·동적 포워딩

Authors
  • avatar
    Name
    Indo Yoon
    Twitter

ssh 터널링은 이미 성립된 SSH 연결을 통로로 재사용해 다른 포트의 트래픽을 안전하게 오가게 하는 기능입니다. OpenSSH 클라이언트가 제공하는 -L, -R, -D 세 가지 옵션만 이해하면 대부분의 막힌 네트워크 상황을 풀 수 있어요. 이 글은 세 방식의 차이를 표로 먼저 비교하고, 실제 설정 절차와 운영할 때 주의할 점까지 순서대로 정리합니다.

한눈에 보기

ssh 터널링이 필요한 순간은 대개 "포트가 막혀 있다"는 한 문장으로 요약됩니다. 클라우드 보안 그룹이나 사내 방화벽이 특정 포트만 허용하고, 정작 필요한 데이터베이스나 내부 콘솔은 그 목록에 없는 경우가 흔합니다. 이때 SSH 포트 하나만 뚫려 있다면 나머지 트래픽은 그 연결 안으로 밀어 넣을 수 있습니다.

방향을 이해하는 것이 핵심입니다. 로컬 포워딩은 내 쪽에 리스닝 소켓을 만들고, 원격 포워딩은 서버 쪽에 리스닝 소켓을 만듭니다. 동적 포워딩은 특정 목적지를 정하지 않고 SOCKS 프록시처럼 동작한다는 점에서 앞의 둘과 결이 다릅니다.

또 하나 기억할 점은 대상 호스트 이름이 어디서 해석되는지입니다. 포워딩 대상 주소는 내 PC가 아니라 SSH 서버의 네트워크 관점에서 해석됩니다. 그래서 사내에서만 통하는 호스트 이름을 그대로 적어도 동작합니다.

세 가지 방식 비교

구분옵션리스닝 위치트래픽 방향대표적인 쓰임
로컬 포워딩-L내 PC내 PC에서 원격 쪽으로원격 데이터베이스, 내부 웹 콘솔 접근
원격 포워딩-R원격 서버원격 서버에서 내 PC 쪽으로외부에서 사내 장비로 들어오기
동적 포워딩-D내 PCSOCKS 프록시로 임의 목적지브라우저나 CLI 도구 전체 프록시

표에서 보듯 세 방식은 "어디에 소켓을 만드는가"로 갈립니다. 목적지가 하나로 고정되어 있으면 -L이나 -R을 쓰고, 목적지가 계속 바뀌면 -D를 씁니다.

로컬 포워딩 설정하기

가장 자주 쓰는 형태입니다. 문법은 ssh -L [바인드주소:]로컬포트:대상호스트:대상포트 사용자@SSH서버 순서입니다. 예를 들어 점프 호스트를 거쳐 사내 PostgreSQL에 붙고 싶다면 아래처럼 실행합니다.

ssh -L 15432:db.internal:5432 deploy@bastion.example.com

이 명령이 성립하면 내 PC의 15432 포트로 보낸 트래픽이 bastion을 거쳐 db.internal의 5432로 전달됩니다. 애플리케이션 설정에는 호스트를 127.0.0.1, 포트를 15432로 적으면 됩니다. 실제 데이터베이스 주소를 로컬 주소로 바꿔치기하는 셈입니다.

바인드 주소를 생략하면 기본적으로 루프백에만 열립니다. 같은 PC의 다른 계정이나 컨테이너에서도 접근해야 한다면 -L 0.0.0.0:15432:db.internal:5432처럼 명시할 수 있지만, 이 경우 해당 포트가 네트워크에 노출된다는 점을 먼저 따져야 합니다.

원격 포워딩 설정하기

반대 방향이 필요할 때 씁니다. 집이나 외부망에 있는 장비에서 사내 서버로 접속해야 하는데, 사내망은 인바운드를 허용하지 않는 구조라면 원격 포워딩이 답이 됩니다.

ssh -R 8080:localhost:3000 deploy@public.example.com

이 명령은 public.example.com의 8080 포트로 들어온 요청을, SSH 연결을 맺은 내 PC의 3000 포트로 넘깁니다. 즉 내 노트북에서 띄운 개발 서버를 외부 서버의 주소로 잠깐 공개하는 형태입니다.

원격 포워딩은 서버 쪽 sshd 설정에 영향을 받습니다. AllowTcpForwarding이 꺼져 있으면 동작하지 않고, 루프백이 아닌 주소로 바인드하려면 GatewayPorts 설정을 함께 봐야 합니다. 설정을 바꿨다면 sshd를 다시 읽어 들여야 적용됩니다.

동적 포워딩 설정하기

목적지를 매번 지정하기 번거로울 때 쓰는 방식입니다. 클라이언트가 로컬에 SOCKS 프록시를 열고, 브라우저나 도구가 그 프록시를 통해 여러 내부 주소로 접근합니다.

ssh -D 1080 -N -f deploy@bastion.example.com

-N은 원격 명령을 실행하지 않고 포워딩만 수행하라는 뜻이고, -f는 인증이 끝난 뒤 백그라운드로 넘어가라는 뜻입니다. 두 옵션을 같이 쓰면 터미널을 점유하지 않는 프록시가 하나 생깁니다. 이후 브라우저의 프록시 설정에서 SOCKS 호스트를 127.0.0.1, 포트를 1080으로 지정하면 됩니다.

동적 포워딩은 편리한 만큼 범위가 넓습니다. 프록시를 거치는 모든 요청이 SSH 서버의 네트워크에서 나가는 것처럼 보이므로, 업무용 접속과 개인 접속을 섞지 않도록 주의해야 합니다.

config 파일로 관리하기

옵션이 길어지면 매번 타이핑하기 어렵습니다. ~/.ssh/config에 호스트 별칭으로 묶어 두면 ssh bastion 한 번으로 같은 터널이 열립니다.

Host bastion
    HostName bastion.example.com
    User deploy
    LocalForward 15432 db.internal:5432
    LocalForward 16379 cache.internal:6379
    ServerAliveInterval 30
    ServerAliveCountMax 3

LocalForward는 여러 줄을 쌓을 수 있어서, 하나의 SSH 세션으로 여러 서비스에 동시에 접근할 수 있습니다. RemoteForward와 DynamicForward도 같은 방식으로 추가합니다. keepalive 관련 항목을 넣어 두면 유휴 시간이 길어질 때 세션이 조용히 끊기는 상황을 줄일 수 있습니다.

여러 홉을 거쳐야 한다면 터널을 중첩하기보다 ProxyJump를 쓰는 편이 관리하기 쉽습니다. ssh -J 점프호스트 최종호스트 형태로 한 번에 연결하면 중간 서버에 포워딩 규칙을 흩뿌리지 않아도 됩니다.

상황별 선택 가이드

정리하면 선택 기준은 단순합니다. 접근하려는 대상이 하나이고 내 쪽에서 시작하는 연결이라면 로컬 포워딩을 씁니다. 데이터베이스 클라이언트, 관리 콘솔, 사내 API 테스트가 여기에 해당합니다.

반대로 외부에서 내 장비나 사내 자원으로 들어와야 한다면 원격 포워딩입니다. 협력사에 잠깐 데모를 보여주거나, 외부 서버에서 사내 배치 작업을 호출해야 하는 경우가 그렇습니다.

불특정 다수의 내부 주소를 오가야 한다면 동적 포워딩이 맞습니다. 다만 대상이 명확한데 습관적으로 -D를 쓰면 접근 범위가 불필요하게 넓어지므로, 목적지가 정해진 경우에는 -L로 좁히는 편이 안전합니다.

주의할 점

첫째, 터널은 인증이 유지되는 동안만 살아 있습니다. 세션이 끊기면 포워딩도 함께 사라지므로, 오래 유지해야 하는 터널은 ServerAliveInterval 같은 keepalive 옵션이나 별도 프로세스 관리 도구를 함께 검토해야 합니다.

둘째, 바인드 주소를 넓히는 결정은 신중해야 합니다. 기본값인 루프백은 해당 호스트 안에서만 접근 가능하지만, 전체 인터페이스로 열면 같은 네트워크의 다른 사용자도 포트를 볼 수 있습니다. 서버 쪽 GatewayPorts를 건드리는 경우에는 특히 어떤 포트가 어디까지 노출되는지 먼저 확인해야 합니다.

셋째, 계정 권한과 정책을 확인해야 합니다. 낮은 번호의 포트에 바인드하려면 권한이 필요하고, 서버 정책상 TCP 포워딩이 막혀 있으면 아무리 클라이언트 옵션을 맞춰도 연결되지 않습니다. 동작하지 않을 때는 클라이언트 명령보다 서버 설정을 먼저 의심하는 편이 빠릅니다.

넷째, 터널을 우회로로 남겨 두지 않아야 합니다. 디버깅용으로 연 포워딩을 그대로 두면 인증 정보 없이 내부 자원에 닿는 경로가 계속 열려 있게 됩니다. 작업이 끝나면 세션을 닫고, 상시 필요한 터널이라면 누가 왜 쓰는지 기록해 두는 편이 좋습니다.

자주 묻는 질문

로컬 포워딩과 원격 포워딩은 무엇이 다른가요?

리스닝 소켓을 어느 쪽에 만드는지가 다릅니다. -L은 내 PC에 포트를 열고 SSH 서버 너머의 대상을 향해 트래픽을 보내며, -R은 SSH 서버에 포트를 열고 내 쪽으로 트래픽을 넘깁니다. 즉 "내가 남의 자원에 접근"이면 -L, "남이 내 자원에 접근"이면 -R입니다.

터널이 열렸는지 어떻게 확인하나요?

클라이언트에서 해당 로컬 포트로 연결을 시도해 보면 됩니다. 로컬 포워딩이라면 ss -lnt 같은 명령으로 리스닝 상태를 확인하고, 실제 애플리케이션으로 접속해 응답이 오는지 보는 것이 가장 확실합니다. 원격 포워딩은 서버 쪽에서 같은 방식으로 확인합니다.

여러 서비스를 하나의 SSH 세션으로 묶을 수 있나요?

가능합니다. ~/.ssh/config에 LocalForward 항목을 여러 줄로 추가하면 하나의 연결로 여러 포트를 동시에 포워딩합니다. 명령줄에서도 -L 옵션을 반복해서 적으면 같은 효과를 냅니다.

동적 포워딩은 언제 쓰는 게 좋나요?

접근해야 할 내부 주소가 여러 개이거나 자주 바뀔 때 적합합니다. 브라우저나 CLI 도구가 SOCKS 프록시를 지원하면 별도 설정 없이 그대로 활용할 수 있습니다. 다만 목적지가 하나로 정해져 있다면 -L로 범위를 좁히는 편이 관리와 보안 양쪽에서 유리합니다.

출처

터널을 운영 환경에 상시로 두기 시작했다면 접속 기록과 프로세스 관리까지 함께 손봐야 합니다. 서버 점검 항목 전반은 리눅스 서버 운영 체크리스트 글에서 이어서 확인하실 수 있습니다.