
SSH 포트포워딩 사용법 — ssh -L과 -R 차이와 DB 터널링 실전 예제
ssh 포트포워딩 사용법을 -L(로컬)과 -R(리모트) 차이부터 정리했다. 막힌 원격 DB에 로컬 툴로 붙는 법, 내 로컬 서버를 외부에 잠깐 여는 법, 그리고 GatewayPorts·방화벽 때문에 "분명 설정했는데 안 되는" 지점까지 실전 예제로 짚는다.
원격 서버에서 도는 PostgreSQL에 로컬 DBeaver로 붙어야 하는데, 정작 5432 포트가 바깥으로 안 열려 있다. 집에서 띄운 토이 프로젝트를 잠깐 남한테 보여주고 싶은데 공인 IP도 도메인도 없다. 개발하다 보면 이런 상황이 꼭 온다. 그때마다 꺼내 드는 게 SSH 포트포워딩인데, -L 하고 -R 중에 뭘 써야 하는지 헷갈려서 매번 검색하는 사람이 생각보다 많다.

포트포워딩이 사실 하는 일
이름은 거창한데 하는 일은 단순하다. 이미 맺어둔 SSH 세션을 통로(터널) 삼아, 한쪽 포트로 들어온 트래픽을 반대쪽 포트로 그대로 흘려보내는 것이다.
SSH를 보통 셸 접속용으로만 생각하는데, 연결이 한 번 맺어지면 그 암호화된 채널 위로 임의의 TCP 트래픽을 얹어 나를 수 있다. 중간 구간이 전부 SSH로 암호화되니, 평문으로 통신하는 내부 서비스를 바깥과 안전하게 이어주는 용도로도 자주 쓴다.
붙잡아야 할 건 딱 한 문장이다. "어느 쪽 컴퓨터에서 새 포트를 여느냐." 내 노트북에서 포트를 열면 로컬 포워딩(-L), 원격 서버에서 포트를 열면 리모트 포워딩(-R)이다. 이것만 쥐고 있으면 나머지 문법은 자연스럽게 따라온다.
ssh -L과 -R, 방향이 정반대다
둘의 차이는 트래픽이 흐르는 방향, 더 정확히는 접속을 받아주는 포트가 어느 쪽에 생기느냐다.
| 구분 | ssh -L (로컬 포워딩) | ssh -R (리모트 포워딩) |
|---|---|---|
| 새 포트가 열리는 곳 | 내 로컬 머신 | 원격 서버 |
| 트래픽 방향 | 로컬 → 원격 | 원격 → 로컬 |
| 대표 용도 | 막힌 내부 DB·관리 페이지에 접속 | 내 로컬 서비스를 외부에 노출 |
| 한 줄 요약 | 내가 안으로 들어간다 | 내 것을 밖으로 꺼낸다 |
말로만 보면 비슷하니 문법을 같이 보자. -L은 이렇게 쓴다.
ssh -L 로컬포트:목적지호스트:목적지포트 사용자@서버
여기서 목적지호스트는 SSH 서버 입장에서 바라본 주소다. 이 점을 놓치면 대부분 여기서 꼬인다. 서버 자신에서 도는 DB면 localhost, 서버와 같은 내부망의 다른 장비면 그 장비의 사설 IP를 적는다.
-R은 머릿속에서 순서가 한 번 뒤집힌다.
ssh -R 원격포트:목적지호스트:목적지포트 사용자@서버
이번엔 원격포트가 서버 쪽에 열리고, 뒤의 목적지호스트:목적지포트는 내 로컬에서 바라본 주소가 된다.
실전 1 — 막힌 원격 DB에 로컬 툴로 붙기
가장 흔한 케이스다. 운영 서버의 PostgreSQL(5432)이 보안상 외부에 안 열려 있을 때, 내 로컬 15432 포트를 서버의 5432로 터널링한다.
ssh -L 15432:localhost:5432 deploy@203.0.113.10
이제 DBeaver에서 localhost:15432로 접속하면 실제로는 암호화된 터널을 타고 서버의 5432에 닿는다. DB 입장에선 서버 내부에서 들어온 로컬 접속으로 보이니 방화벽을 손댈 필요도 없다. 셸은 필요 없고 터널만 백그라운드로 돌리고 싶으면 -fNL을 묶는다.
ssh -fNL 15432:localhost:5432 deploy@203.0.113.10
-f는 백그라운드, -N은 원격 명령을 실행하지 않음(터널만), -L은 포워딩이다. 세 글자를 붙여 쓰면 "조용히 터널만 하나 파놓는다"가 된다.

실전 2 — 내 로컬 서버를 잠깐 외부에 보여주기
방향을 뒤집어 보자. 내 노트북 3000 포트에서 도는 개발 서버를, 원격 서버의 8080으로 꺼내고 싶다. 클라이언트에게 데모를 보여주거나 외부 웹훅을 로컬로 받아봐야 할 때 쓴다.
ssh -R 8080:localhost:3000 deploy@203.0.113.10
서버의 8080으로 들어온 요청이 터널을 타고 내 노트북 3000으로 전달된다. 그런데 이 상태로는 서버의 localhost(127.0.0.1)에서만 접근된다. 서버에서 curl localhost:8080은 되는데 다른 사람 브라우저로는 안 열리는 게 정상이다. 진짜로 외부에 열려면 아래 GatewayPorts가 필요하다.
자주 막히는 지점
① -R로 열었는데 외부에서 접속이 안 돼요
제일 많이 걸리는 함정이다. -R로 연 포트는 기본적으로 서버의 127.0.0.1에만 바인딩된다. 외부 인터페이스에 열려면 서버의 /etc/ssh/sshd_config에서 GatewayPorts clientspecified(또는 yes)로 바꾸고 sshd를 재시작한 뒤, 바인딩 주소를 명시해 준다.
ssh -R 0.0.0.0:8080:localhost:3000 deploy@203.0.113.10
② 방화벽·보안그룹
GatewayPorts를 켰는데도 안 된다면 그다음은 방화벽이다. 클라우드라면 보안그룹(AWS Security Group 등)이, 서버 OS라면 ufw·firewalld가 해당 포트를 막고 있을 수 있다. OS 안에서만 열고 클라우드 콘솔은 안 건드려서 "분명 다 설정했는데 안 된다"가 유독 자주 나온다. 두 겹을 따로 확인하자.
③ bind: Address already in use
터널을 걸려는 포트를 이미 다른 프로세스가 점유한 경우다. lsof -i:15432로 누가 잡고 있는지 보고 포트 번호를 바꾸거나 정리한다. 앞서 -f로 백그라운드에 띄운 터널이 안 죽고 남아 있는 경우가 의외로 흔하니 거기부터 의심하는 게 빠르다.
④ 터널이 자꾸 끊겨요
유휴 연결이 중간 장비에서 잘리는 거라면 -o ServerAliveInterval=30을 붙여 주기적으로 신호를 보낸다. 끊겨도 자동으로 다시 붙는 상시 터널이 필요하면 autossh를 쓰는 편이 마음 편하다.
실무에서 국내 개발팀이 이걸 가장 많이 꺼내 드는 순간은 따로 있다. 운영 DB가 사설 서브넷에 들어가 있어 배스천(bastion) 서버를 한 번 거쳐야만 닿을 때다. 이럴 땐 배스천을 경유하는 -J(ProxyJump)를 -L과 묶으면 터널 체인이 한 줄로 깔끔해진다. 다만 포트포워딩을 "임시 우회"가 아니라 상시 운영 경로로 삼는 건 개인적으로 권하지 않는다. -R로 내부 서비스를 바깥에 열어두는 건 사실상 임시로 구멍을 하나 뚫는 일이라, 데모가 끝나면 반드시 닫아야 하고, 팀 공용 서버에서 누가 어떤 터널을 열어놨는지 추적이 안 되는 순간 그대로 보안 사고로 이어진다. 외부 노출이 상시로 필요해졌다면 포트포워딩 대신 리버스 프록시(nginx)나 Cloudflare Tunnel 같은 관리형 도구로 넘어가는 게 맞다. 포트포워딩은 "지금 당장, 잠깐"에 쓰는 도구라고 선을 그어두면 사고가 확실히 줄어든다.
정리하면
-L은 내가 안으로 들어가는 길(로컬에 포트가 열림), -R은 내 것을 바깥으로 꺼내는 길(원격에 포트가 열림)이다. 방향만 잡으면 나머지 문법은 저절로 풀린다. 막힌 DB엔 -L, 로컬 데모 공개엔 -R, 외부에서 안 열리면 GatewayPorts와 방화벽을 차례로 의심하면 된다.
여러분은 포트포워딩을 주로 어떤 상황에서 쓰시나요? autossh로 상시 터널을 운영하는 쪽인지, 아니면 매번 손으로 여닫는 쪽인지 궁금합니다.
참고
- OpenSSH 공식 매뉴얼
ssh(1)의-L·-R·-N·-f옵션 설명 sshd_config(5)의GatewayPorts·AllowTcpForwarding항목
댓글 0
댓글을 불러오는 중…