엔지니어링 노트

SSH 역방향 터널로 클라우드 Mac과 로컬 API 연결하기

원격 Mac ·약 9분 읽기

SSH 역방향 터널로 클라우드 Mac과 로컬 API 연결하기

로컬 개발 컴퓨터에서 아직 배포하지 않은 API를 실행하고 있지만, Xcode 프로젝트와 빌드 스크립트는 클라우드 Mac에서 실행되는 상황이 있습니다. 라우터 포트를 직접 개방하면 설정이 번거로울 뿐 아니라 공격 표면도 넓어집니다. 코드 저장소의 주소를 임시로 공인 도메인으로 바꾸는 방법 역시 설정에 불필요한 변경 사항을 남기기 쉽습니다. 더 안전한 방법은 개발 컴퓨터에서 SSH 연결을 시작하고, 역방향 터널을 통해 로컬 포트를 클라우드 Mac의 루프백 주소에 매핑하는 것입니다.

먼저 트래픽 방향 확인하기

역방향 터널은 “클라우드에서 로컬로 접속”해야 할 때 사용합니다. 개발 컴퓨터의 127.0.0.1:8080에서 API가 실행 중이고, 클라우드 Mac에서는 127.0.0.1:18080으로 이 API를 호출한다고 가정하겠습니다. SSH 연결은 개발 컴퓨터에서 시작해 클라우드 Mac으로 접속하며, 클라우드에서 받은 트래픽을 다시 로컬 포트로 전달합니다.

로컬 포트 전달은 반대 방향입니다. 개발 컴퓨터에서 클라우드 Mac의 루프백 주소에만 바인딩된 디버깅 서비스에 접속할 때 사용합니다. 예를 들어 클라우드 서비스가 127.0.0.1:9000에 있다면 개발 컴퓨터의 127.0.0.1:19000으로 매핑할 수 있습니다.

목적 SSH 옵션 연결을 시작하는 위치 접속 주소
클라우드 Mac에서 로컬 API 접속 -R 개발 컴퓨터 클라우드의 127.0.0.1:18080
로컬에서 클라우드 디버깅 서비스 접속 -L 개발 컴퓨터 로컬의 127.0.0.1:19000

처음부터 터널 문제를 의심하지 말고, 실제 클라이언트로 원본 서비스가 정상 동작하는지 먼저 확인합니다.

curl -i http://127.0.0.1:8080/health
lsof -nP -iTCP:8080 -sTCP:LISTEN

애플리케이션에 상태 확인 경로가 없다면 로컬에서만 수신하는 테스트 서버를 임시로 실행할 수 있습니다.

python3 -m http.server 8080 --bind 127.0.0.1

외부 노출을 최소화한 역방향 터널 만들기

개발 컴퓨터에서 클라우드 주소를 설정한 뒤 다음 명령을 실행합니다.

export CLOUD_MAC_IP="你的节点地址"
ssh -N \
  -o ExitOnForwardFailure=yes \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -R 127.0.0.1:18080:127.0.0.1:8080 \
  dev@"$CLOUD_MAC_IP"

-N은 원격 명령을 실행하지 않고 연결을 포트 전달에만 사용한다는 뜻입니다. ExitOnForwardFailure를 지정하면 포트 바인딩에 실패했는데도 정상처럼 보이는 SSH 세션이 남는 상황을 방지할 수 있습니다. 두 연결 유지 옵션은 끊어진 연결을 감지하는 데 사용되며, 네트워크가 중단된 뒤 터널을 자동으로 다시 생성해 주지는 않습니다.

클라우드 Mac에서 새 터미널을 열고 다음과 같이 확인합니다.

lsof -nP -iTCP:18080 -sTCP:LISTEN
curl -i http://127.0.0.1:18080/health

lsof 출력의 수신 주소는 *:18080이 아니라 127.0.0.1:18080이어야 합니다. 연동 테스트에 사용할 주소는 소스 코드에 하드코딩하지 말고 개발 환경 변수로 주입해야 합니다.

export DEV_API_BASE_URL="http://127.0.0.1:18080"
xcodebuild -scheme DemoApp -configuration Debug test

터널은 전송 구간을 암호화하고 접속 지점을 제한할 뿐, API 자체의 인증을 대신하지 않습니다. 포트가 루프백 주소에서만 수신하더라도 테스트 토큰, 권한 검사, 민감 정보를 제거한 로그는 그대로 유지해야 합니다.

SSH 설정으로 입력 실수 줄이기

긴 명령을 반복해서 입력하면 로컬 포트와 원격 포트, 대상 주소를 서로 뒤바꾸기 쉽습니다. 개발 컴퓨터의 ~/.ssh/config에 전용 항목을 만들어 두는 편이 안전합니다.

Host minid-api-tunnel
    HostName CLOUD_MAC_IP
    User dev
    RemoteForward 127.0.0.1:18080 127.0.0.1:8080
    ExitOnForwardFailure yes
    ServerAliveInterval 30
    ServerAliveCountMax 3

CLOUD_MAC_IP를 실제 노드 주소로 바꾸고 설정 파일의 권한도 올바르게 지정합니다.

chmod 700 ~/.ssh
chmod 600 ~/.ssh/config
ssh -N minid-api-tunnel

하나의 항목에 관련 없는 포트 전달을 여러 개 몰아넣지 마세요. 프로젝트마다 별칭을 하나씩 사용하고 포트도 용도별로 구분합니다. 예를 들어 API에는 18080, 디버깅 패널에는 19000을 사용할 수 있습니다. 이렇게 구성하면 수신 중인 포트만 보고도 어떤 프로젝트에 속하는지 파악할 수 있고, 여러 사람이 같은 노드를 사용할 때 발생하는 충돌도 줄일 수 있습니다.

로컬에서 클라우드 서비스에 접속해야 한다면 로컬 포트 전달을 별도로 만듭니다.

ssh -N \
  -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:19000:127.0.0.1:9000 \
  dev@"$CLOUD_MAC_IP"

이후 개발 컴퓨터에서만 http://127.0.0.1:19000에 접속합니다. 두 포트 전달 방식 모두 애플리케이션 서비스를 공인 네트워크 인터페이스에 바인딩할 필요가 없습니다.

계층별로 연결 실패 진단하기

터널 문제는 “원본 서비스, SSH 포트 전달, 대상 클라이언트”의 세 계층으로 나누어 확인해야 합니다. 방화벽과 애플리케이션 설정, 프로젝트 코드를 한꺼번에 변경하지 않도록 주의합니다.

원본 서비스 계층

개발 컴퓨터에서 curllsof를 실행합니다. API가 IPv6 주소인 ::1에서만 수신하는데 포트 전달 대상은 127.0.0.1로 지정했다면 연결이 거부됩니다. 수신 프로토콜을 통일하거나 포트 전달 대상을 실제 수신 주소로 변경해야 합니다. 로컬 프록시가 루프백 트래픽을 가로채지 않는지도 확인합니다. 다음 명령으로 프록시를 일시적으로 우회할 수 있습니다.

curl --noproxy '*' -i http://127.0.0.1:8080/health

SSH 포트 전달 계층

상세 출력을 활성화해 포트 요청 결과를 확인합니다.

ssh -vv -N \
  -o ExitOnForwardFailure=yes \
  -R 127.0.0.1:18080:127.0.0.1:8080 \
  dev@"$CLOUD_MAC_IP"

원격 포트 전달에 실패했다는 메시지가 나오면 먼저 사용 중이지 않은 다른 포트로 바꿔 봅니다. 그래도 실패한다면 클라우드 SSH 서비스가 TCP 포트 전달을 허용하는지 확인합니다. 편의를 위해 공인 주소에 바인딩하거나 모든 접속 출처를 허용해서는 안 됩니다.

대상 클라이언트 계층

클라우드 터미널에서 curl이 성공하더라도 모든 실행 대상이 동일한 네트워크 환경을 자동으로 사용하는 것은 아닙니다. 빌드 스크립트, GUI 애플리케이션, 시뮬레이터는 서로 다른 환경 설정을 읽을 수 있습니다. HTTP 요청이 프로젝트의 보안 정책에 의해 제한될 수도 있습니다. 호출 프로세스, 대상 URL, 응답 코드, 제한 시간을 각각 기록한 뒤 네트워크 문제인지 애플리케이션 계층의 거부인지 판단해야 합니다.

실행 전 점검과 마무리

연동 테스트가 끝나면 터널에 의존하는 테스트 작업을 먼저 중지한 다음 SSH 세션을 종료합니다. lsof로 매핑된 포트가 사라졌는지 확인하고, 프로젝트에 남아 있는 임시 주소와 테스트 토큰, 디버그 로그를 삭제합니다.

코드를 커밋하기 전에 최소한 다음 항목을 확인하세요.

  • API는 계속 개발 컴퓨터의 루프백 주소에서만 수신합니다.
  • 클라우드의 매핑 포트는 127.0.0.1에서만 수신합니다.
  • SSH 개인 키 권한은 600, 설정 디렉터리 권한은 700입니다.
  • 프로젝트는 환경 변수로 연동 주소를 선택하며, 배포 설정에서는 해당 포트를 참조하지 않습니다.
  • 로그에 요청 토큰, 키, 전체 사용자 데이터, 인증 헤더가 포함되어 있지 않습니다.
  • 여러 사람이 프로젝트를 함께 사용한다면 포트 번호, 담당자, 종료 시간을 기록해 두었습니다.

이 방식은 임시 API, 콜백 검증, 원격 빌드 연동 테스트에 적합합니다. 여러 노드가 서비스를 장기간 호출해야 한다면 임시 터널을 운영 환경의 진입점으로 사용하지 말고, 독립적인 인증과 접근 제어, 감사 기록을 갖춘 정식 배포 방식으로 전환해야 합니다.

자주 묻는 질문

SSH 역방향 터널을 만들면 로컬 API가 인터넷에 공개되나요?

원격 리스닝 주소를 127.0.0.1로 지정하면 클라우드 Mac 내부에서만 접근할 수 있습니다. 실제 리스닝 주소를 확인하고 0.0.0.0 바인딩은 피해야 합니다.

SSH 연결은 성공했지만 매핑된 포트에 접속할 수 없는 이유는 무엇인가요?

먼저 개발 PC의 원본 서비스가 올바른 포트에서 실행되는지 확인하십시오. 이후 클라우드 Mac에서 lsof로 리스닝 상태를 확인하면 포트 충돌이나 TCP 포워딩 제한을 구분할 수 있습니다.

Xcode에서 실행한 앱도 터널 주소를 바로 사용할 수 있나요?

클라우드 Mac의 빌드 스크립트는 터널의 127.0.0.1 주소를 사용할 수 있습니다. 시뮬레이터와 앱은 네트워크 범위 및 프로젝트의 HTTP 보안 설정을 별도로 확인해야 합니다.

MiniD 클라우드 Mac

전용 물리 Mac mini를 일·주·월 단위로 대여

모든 플랜은 전용 물리 Mac mini에서 실행되며 원격 데스크톱과 SSH로 접속할 수 있습니다. 최신 모델·리전·기간은 주문 페이지에서 확인하세요.

주문 옵션 보기