termal.in

← 블로그

SSH 포트 포워딩 완전 정리: 로컬·리모트·다이내믹 터널

· Termalin Team sshport-forwardingtunnelingtutorial

SSH 포트 포워딩은 사람들이 Stack Overflow에서 복사해 와 한 번 되게 만들고는 끝내 제대로 이해하지 못하는 기능 중 하나입니다 — 그래서 다음에 다시 필요하면 -L, -R, -D 중 뭘 써야 하는지 또 찍어 보게 됩니다. 세 플래그는 어렵지 않습니다. 그저 이름이 잘못 붙었을 뿐입니다. 여기 그것을 한눈에 뻔하게 만드는 하나의 개념, 각각의 예제, 그리고 터널이 오후를 넘길지 말지를 가르는 플래그와 함정이 있습니다.

플래그를 고르는 단 하나의 질문

모든 SSH 터널은 한쪽 머신에 수신 포트를 열고, 거기로 들어온 것을 다른 머신에서 닿을 수 있는 어딘가로 조용히 실어 나릅니다. 그러니 질문은 하나뿐입니다. 내가 연결하는 포트를 어느 쪽이 여는가?

  • -L(로컬) — 포트가 내 머신에 열립니다. 로컬로 연결하면 트래픽이 서버 쪽으로 나갑니다.
  • -R(리모트) — 포트가 서버에 열립니다. 누군가 거기로 연결하면 트래픽이 내 쪽으로 나갑니다.
  • -D(다이내믹) — 포트가 내 머신에 열리지만 SOCKS 프록시입니다. 목적지가 미리 고정되지 않고 연결마다 결정됩니다.

나머지는 전부 세부 사항입니다. “어느 쪽이 포트를 여는가”만 머릿속에 두면 다시는 엉뚱한 플래그에 손대지 않습니다.

로컬 포워딩(-L) — 원격 서비스를 localhost로 끌어오기

가장 흔한 녀석입니다. 데이터베이스, 내부 대시보드, 메트릭 UI — 서버의 127.0.0.1에, 또는 서버만 닿을 수 있는 머신에 묶인 무언가요. 그걸 마치 로컬에 있는 것처럼 내 노트북에서 쓰고 싶습니다.

ssh -L 5432:localhost:5432 deploy@db.example.com

왼쪽에서 오른쪽으로 읽으세요: 내 머신에 5432를 열고, 들어오는 것은 서버 기준으로 localhost:5432로 포워딩하라. 이제 노트북의 psql -h 127.0.0.1이 서버의 Postgres와 대화합니다(영어) — 암호화된 SSH 연결을 통해, 데이터베이스를 네트워크에 전혀 노출하지 않고요.

가운데 호스트는 서버 쪽에서 해석되며, 이 점이 이 기법을 강력하게 만듭니다:

# bastion만 볼 수 있는 데이터베이스에 닿기
ssh -L 5432:10.0.0.9:5432 deploy@bastion.example.com

여기서 10.0.0.9는 내 노트북이 라우팅할 수 없는 사설 주소지만, bastion은 할 수 있습니다. 터널이 그 틈을 이어 줍니다.

리모트 포워딩(-R) — 내 머신을 서버 쪽에 노출하기

거울상이자, 사람들이 방향을 거꾸로 이해하는 녀석입니다(별도의 심화 글이 있습니다 — SSH 리버스 터널). 포트가 서버에 열리고, 거기로 들어온 연결이 내 쪽으로 나옵니다. 고전적인 용도 둘:

# 서버(또는 그 네트워크)가 내 노트북에서 도는 개발 서버에 닿게 하기
ssh -R 8080:localhost:3000 deploy@app.example.com

이제 서버의 localhost:8080에 들어오는 것은 무엇이든 내 노트북의 localhost:3000으로 흘러갑니다 — 상자에 있는 팀원에게 작업 중인 결과물을 보여 주거나, 원격 서비스가 나만 돌리는 무언가로 콜백하게 할 때 유용합니다.

이건 인바운드 접근이 없는, NAT 뒤에 갇힌 머신에 닿는 요령이기도 합니다. 그 머신이 내가 제어하는 서버로 리버스 터널을 열게 하고, 나는 서버를 거쳐 연결합니다. 연결을 받을 수 없는 머신이 대신 아웃바운드 연결을 거는 것이죠.

함정 하나: 기본적으로 서버는 포워딩된 포트를 localhost에 묶어서, 서버 자신만 쓸 수 있습니다. 서버 네트워크의 다른 머신들이 닿게 하려면 서버의 sshd_config에 GatewayPorts yes(또는 clientspecified)가 필요합니다 — 클라이언트에서 강제할 수 없는 서버 쪽 설정입니다.

다이내믹 포워딩(-D) — 플래그 하나로 SOCKS 프록시

-L은 고정된 목적지 하나로 포워딩합니다. -D는 대신 SOCKS 프록시를 열어서 목적지가 요청마다 선택됩니다:

ssh -D 1080 deploy@jump.example.com

브라우저(또는 curl --socks5 127.0.0.1:1080)를 127.0.0.1:1080으로 가리키면 모든 요청이 서버에서부터 라우팅됩니다. 내부 네트워크를 둘러보거나, 서비스가 데이터센터 IP에서 어떻게 보이는지 테스트하거나, -L을 하나씩 걸지 않고 내부 호스트 여럿에 닿는 빠른 방법입니다.

쓸 만하게 만드는 플래그들

벌거벗은 터널 명령은 원하지도 않은 셸을 열고, 그걸 닫는 순간 죽습니다. 실전에서는:

ssh -N -L 5432:localhost:5432 deploy@db.example.com
  • -N — 원격 명령을 실행하지 않습니다. 셸 말고 터널만 원하니까요.
  • -f — 연결 후 프로세스를 백그라운드로 보냅니다(-N과 짝지으세요).
  • -o ExitOnForwardFailure=yes — 포트를 묶지 못하면(이미 사용 중, 권한 거부) 어차피 연결해 놓고 조용히 아무것도 포워딩하지 않는 대신, 요란하게 실패합니다.
  • keepalive — 장수 터널은 ServerAliveInterval 30 없이는 유휴 타임아웃에 죽습니다. 넣지 않으면 쓰기를 멈추는 순간 터널이 끊깁니다.

ssh_config에 영구로 박아 두기

-L/-R/-D로 넘기는 것은 모두 대응하는 config 지시어가 있어서, 저장된 호스트가 자기 터널을 품고 다닙니다:

Host db-tunnel
    HostName db.example.com
    User deploy
    LocalForward 5432 localhost:5432
    ServerAliveInterval 30
    RequestTTY no

이제 ssh -N db-tunnel이 외울 플래그 없이 데이터베이스를 127.0.0.1:5432로 가져옵니다. RemoteForward와 DynamicForward도 똑같이 동작합니다. (config 파일 자체는 ~/.ssh/config 가이드(영어)에서 더 다룹니다.)

세 가지 함정

  • bind 주소가 중요합니다. -L 5432:...는 127.0.0.1에서만 수신합니다. 내 LAN의 다른 기기가 터널을 쓰게 하려면 bind 주소를 앞에 붙이세요: -L 0.0.0.0:5432:... — 그리고 방금 그걸 로컬 네트워크에 노출했음을 이해하세요.
  • “Address already in use” (주소가 이미 사용 중)는 로컬 포트가 점유됐다는 뜻입니다(흔히 종료되지 않은 이전 터널). 다른 로컬 포트를 쓰거나 옛것을 죽이세요. ExitOnForwardFailure는 그 조용한 실패를 실제로 알아챌 오류로 바꿉니다.
  • 가운데 호스트는 반대편에서 해석됩니다. -L a:localhost:b의 localhost는 내가 아니라 서버의 localhost입니다. 이걸 거꾸로 아는 것이 포트 포워딩에서 단연 가장 흔한 실수입니다.

터널이 바인드되지 않을 때

명령이 bind: Address already in use나 bind: Permission denied로 실패하면 수신 포트가 아예 열리지 않은 겁니다 — 짧은 목록을 짚어 보세요. 이미 사용 중: 무언가가 포트를 쥐고 있고, 흔히 어제 잊은 터널입니다 — ss -tlnp가 프로세스를 짚어 주면 다른 포트를 쓰거나 옛것을 죽이세요. 로컬에서의 권한 거부는 보통 특권 포트 문제입니다. 1024 미만은 root가 필요하니 443 대신 8443에서 수신하세요. 원격 쪽(-R)에서는 서버가 거부권을 쥡니다. sshd_config의 AllowTcpForwarding no는 포워딩을 통째로 막고, GatewayPorts가 없으면 어떤 bind 주소를 요청했든 원격 수신 포트가 localhost에만 묶입니다. 그리고 보안이 강화된 RHEL 계열 호스트에서는 설정이 허용해도 SELinux가 sshd의 bind를 거부할 수 있으니, 명령을 탓하기 전에 감사 로그를 확인하세요.

터널을 손으로 저글링하기가 지겨워질 때

포워딩은 훌륭한 기본기이고, 모든 개발자가 세 플래그를 줄줄 외워야 합니다. 마찰은 나중에 드러납니다. ssh -N -L ... 명령들(영어)을 적어 둔 메모장을 들고 다니고, 노트북이 잠들 때마다 다시 실행하며, 어느 로컬 포트가 어느 서비스에 매핑되는지 잊고, 터널이 아직 살아 있는지조차 한눈에 알 수 없습니다.

바로 그 지점에서 제대로 된 터널 관리자를 갖춘 클라이언트가 도움이 됩니다. Termalin은 로컬·리모트·다이내믹 포워딩을 저장된 config로서 호스트에 붙여 둡니다 — 클릭 한 번으로 시작하고, 어떤 것이 살아 있는지 보며, 명령을 다시 타이핑하는 대신 연결과 함께 자동으로 재수립되게 합니다. 같은 호스트 목록(키, 점프 호스트, keepalive 포함)은 여러 머신 사이에서 종단 간 암호화되어 동기화되고, 각 서버는 첫 연결 때 검증됩니다. 명령줄은 터널이 어떻게 동작하는지 가르쳐 주고, 클라이언트는 매일 아침 손으로 다시 쌓지 않게 해 줍니다.

어느 쪽이든, 개념이 요령의 전부입니다: 어느 쪽이 포트를 여는지 정하면, 나머지 플래그는 저절로 써집니다.


Termalin은 터널, SFTP, 키 관리자를 기본 내장한 무료 크로스 플랫폼 SSH 클라이언트입니다 — 다운로드하거나, 키를 안전하게 다루는 방식을 확인해 보세요.

호스트 한 대에서 먼저 써 보세요.

Termalin은 여러분과 여러분의 에이전트를 위한 빠른 SSH 클라이언트입니다.

무료 요금제 · Pro 14일 무료 체험 · 요금