← ~/notes · 2 min read

공유기 포트 안 열고 집 서버에 SSH 들어가기 — Cloudflare Tunnel + ProxyCommand

집에 있는 Proxmox 호스트와 VM 4대에 외부에서 SSH로 들어가야 했는데, 공유기 포트는 절대 안 열고 싶었다.

목차
  1. 결과물
  2. 아키텍처
  3. Proxmox 호스트에 cloudflared 설치
  4. config.yml — 다중 호스트 라우팅
  5. DNS 레코드
  6. systemd로 상시 가동
  7. Mac 쪽 — ProxyCommand
  8. VM 간 라우팅 — 호스트가 게이트웨이
  9. 보안
  10. 만들면서 알게 된 것들
  11. 응용

집에 있는 서버 한 대에 외부에서 SSH로 들어가는 건 ssh user@공인IP로 끝나지만, 그러려면 공유기에 포트 22를 열어둬야 한다. 그게 싫었다.

  • 공인 IP가 KT 다이나믹이라 바뀜
  • 22번 열어두면 매일 수천 건 무차별 대입
  • 가족 공유 공유기라 설정 건드리기 부담
  • VPN 셋업도 가족 PC에 깔게 강제할 수 없음

답은 Cloudflare Tunnel이었다. 무료, 포트 노출 0, 도메인 기반 식별.


결과물

ssh ssh.lab-it.net   → 172.30.1.100 (Proxmox 호스트)
ssh vm1.lab-it.net   → 172.30.1.110 (마인크래프트 서버)
ssh vm2.lab-it.net   → 172.30.1.120 (Docker 서버)
ssh vm3.lab-it.net   → 172.30.1.130 (Project/허니팟 서버)

학교에서, 카페에서, 어디서든 ssh vm3.lab-it.net 한 줄. 키 인증, 비밀번호 없음.


아키텍처

Mac (어디서든)
  │
  │ ssh vm3.lab-it.net
  │
  │ ProxyCommand: cloudflared access ssh --hostname vm3.lab-it.net
  ▼
Cloudflare 엣지 (TLS)
  │
  │ Cloudflare Tunnel (지속 연결)
  ▼
Proxmox 호스트 vm0 (172.30.1.100)
  │ cloudflared 데몬
  ├─▶ 자기 자신 ssh (ssh.lab-it.net)
  └─▶ 내부 네트워크 → vm1/vm2/vm3 ssh (vm*.lab-it.net)

핵심: 공유기에 포트를 열지 않는다. cloudflared가 outbound 연결을 Cloudflare 엣지에 유지하고, 외부 요청이 그 연결을 타고 들어온다.


Proxmox 호스트에 cloudflared 설치

# 패키지 설치
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -o cloudflared.deb
dpkg -i cloudflared.deb

# Cloudflare 계정 로그인 (브라우저 자동 열림)
cloudflared tunnel login

# 터널 생성
cloudflared tunnel create proxmox-ssh
# tunnel ID: bd6de2ac-2b70-42c2-9736-d8d63367a7f6

터널 생성하면 ~/.cloudflared/<tunnel-id>.json에 자격증명이 저장된다.


config.yml — 다중 호스트 라우팅

핵심은 하나의 터널에 여러 호스트네임을 라우팅하는 것이다. Cloudflare Tunnel은 ingress 규칙으로 호스트네임 → 내부 주소 매핑을 정의한다.

/etc/cloudflared/config.yml:

tunnel: bd6de2ac-2b70-42c2-9736-d8d63367a7f6
credentials-file: /root/.cloudflared/bd6de2ac-2b70-42c2-9736-d8d63367a7f6.json

ingress:
  - hostname: ssh.lab-it.net
    service: ssh://localhost:22
  - hostname: vm1.lab-it.net
    service: ssh://172.30.1.110:22
  - hostname: vm2.lab-it.net
    service: ssh://172.30.1.120:22
  - hostname: vm3.lab-it.net
    service: ssh://172.30.1.130:22
  - service: http_status:404

마지막 catch-all http_status:404는 필수 — 빠지면 cloudflared가 시작 거부한다.


DNS 레코드

각 호스트네임이 터널을 가리키게 DNS CNAME을 만든다. CLI로 한 번에:

cloudflared tunnel route dns proxmox-ssh ssh.lab-it.net
cloudflared tunnel route dns proxmox-ssh vm1.lab-it.net
cloudflared tunnel route dns proxmox-ssh vm2.lab-it.net
cloudflared tunnel route dns proxmox-ssh vm3.lab-it.net

자동으로 <tunnel-id>.cfargotunnel.com을 가리키는 CNAME이 생긴다.


systemd로 상시 가동

cloudflared service install
systemctl enable --now cloudflared

호스트 부팅 시 자동 시작. cloudflared가 outbound 연결을 유지하므로 공유기 재부팅돼도 IP 바뀌어도 자동 복구.


Mac 쪽 — ProxyCommand

~/.ssh/config:

Host ssh.lab-it.net
  HostName ssh.lab-it.net
  User root
  ProxyCommand cloudflared access ssh --hostname %h
  IdentityFile ~/.ssh/id_ed25519

Host vm1.lab-it.net
  HostName vm1.lab-it.net
  User minecraft
  ProxyCommand cloudflared access ssh --hostname %h
  IdentityFile ~/.ssh/id_ed25519

Host vm2.lab-it.net
  User docker
  ProxyCommand cloudflared access ssh --hostname %h
  IdentityFile ~/.ssh/id_ed25519

Host vm3.lab-it.net
  User project
  ProxyCommand cloudflared access ssh --hostname %h
  IdentityFile ~/.ssh/id_ed25519

%h가 호스트네임으로 치환된다. Mac에 cloudflared CLI 설치는 brew install cloudflared.

각 VM의 ~/.ssh/authorized_keys에 Mac의 ed25519 공개키를 미리 등록해두면 비밀번호 없이 접속.


VM 간 라우팅 — 호스트가 게이트웨이

vm1/vm2/vm3는 Proxmox 내부 브리지(vmbr0)에 붙어 있고 외부에서 직접 접근 불가. 하지만 호스트(172.30.1.100)는 cloudflared와 같은 머신이므로, ingress 규칙에서 ssh://172.30.1.110:22처럼 직접 가리키면 호스트가 알아서 라우팅한다.

별도 NAT/iptables 없음. Proxmox가 만든 브리지가 알아서 처리.


보안

  • 포트 22 노출 0. 외부에서 어떤 IP로도 22번에 직접 못 닿음.
  • TLS 강제. Cloudflare 엣지~Mac 구간이 자동 TLS.
  • Zero Trust 정책 (옵션). 대시보드에서 호스트네임마다 “내 이메일로 OTP 인증된 디바이스만”으로 추가 잠금 가능. 안 걸어도 키 인증이라 사실상 안전.
  • fail2ban 의미 없음. 기존 22번 무차별 대입이 사라지므로 fail2ban 의존도가 떨어짐.

만들면서 알게 된 것들

http_status:404 catch-all 누락. 처음에 ingress 마지막 줄을 빼먹고 cloudflared가 안 떠서 한참 헤맴. yaml 검증을 강제하는 좋은 디자인.

Mac에 cloudflared 별도 설치 필요. Tunnel은 서버 측만 있다고 끝이 아니다. 클라이언트(access ssh)도 cloudflared가 깔려 있어야 ProxyCommand가 동작.

다이나믹 IP에 더 강함. KT 공인 IP가 새벽에 바뀌어도 cloudflared가 outbound라 자동 재연결. DDNS 셋업 안 해도 됨.

여러 호스트 한 터널. 처음엔 호스트마다 터널 따로 만들 줄 알았는데, 하나로 다중 호스트네임 라우팅이 정석. 관리 단순.


응용

같은 패턴으로 SSH뿐 아니라 모든 TCP/HTTP를 노출 가능:

ingress:
  - hostname: honey.lab-it.net
    service: http://172.30.1.130:8888    # 허니팟 대시보드
  - hostname: xai.lab-it.net
    service: http://172.30.1.120:8201    # XAI DDoS 백엔드
  - hostname: ssh.lab-it.net
    service: ssh://localhost:22

서비스 추가는 yaml 한 줄. 공유기는 평생 안 건드림.


홈랩에서 가장 답답한 건 NAT 뚫기다. Cloudflare Tunnel이 그 답답함을 영구히 제거한다.