jaysnote
21분

Oracle Cloud Always Free로 Mumbai WireGuard VPN 구성하기

Oracle Cloud Mumbai VM에 WireGuard 출구 노드를 만들고, Mac과 Android가 서로 다른 키로 접속하도록 구성한 과정과 보안, 운영 기준을 정리한다.

목표는 단순했다. Mac과 Android에서 필요할 때 터널을 켜면 인터넷 트래픽이 Oracle Cloud Mumbai 리전을 거쳐 나가게 만드는 것이다. 공용 무료 VPN을 이용하지 않고, 내가 관리하는 작은 VM을 개인 출구 노드로 사용한다.

최종 구성은 다음과 같다.

  • Oracle Cloud 홈 리전: ap-mumbai-1
  • 서버: Ubuntu 24.04, Always Free 대상 VM
  • VPN: WireGuard, UDP 51820
  • 클라이언트: Mac, Android, 기기마다 별도 키 쌍
  • 클라이언트 주소: 10.66.66.2, 10.66.66.3
  • DNS: 1.1.1.1, 1.0.0.1
  • 관리: SSH는 현재 관리자 공인 IPv4 한 개에서만 허용
  • 자동화: Terraform으로 OCI 자원을 만들고, 셸 스크립트로 서버와 클라이언트 설정 생성

공인 IP, OCI 사용자 OCID, tenancy OCID, API 개인키, WireGuard 개인키는 공개 글에서 모두 제외했다.

Mac과 Android가 Oracle Cloud Mumbai VM을 출구로 공유하는 구조

이 구성을 이해할 때는 제어 경로데이터 경로를 나눠 보는 것이 좋다.

  • 제어 경로는 Mac에서 vpnctl, Terraform, OCI API를 이용해 서버와 네트워크를 만들고 설정하는 과정이다. 평소 인터넷 사용 중에는 이 경로를 사용하지 않는다.
  • 데이터 경로는 Mac이나 Android의 실제 인터넷 패킷이 WireGuard 터널, OCI VM, NAT를 차례로 통과하는 경로다.

전체 시스템은 다섯 층으로 나뉜다.

구성 요소맡은 역할
로컬 관리vpnctl, Terraform, OCI CLI자원 생성, 키 준비, 클라이언트 프로필 생성, 서버 전원 관리
OCI 네트워크VCN, public subnet, route table, Internet Gateway, NSGVM에 공인 진입점과 인터넷 출구를 제공하고 허용할 포트를 제한
OCI 컴퓨트Ubuntu VM, VNIC, 공인 IPv4, boot volumeWireGuard와 Linux 라우팅을 실행하는 Mumbai 출구 노드
VPN 서버WireGuard, IP forwarding, iptables, systemd터널 복호화, 패킷 전달, IPv4 NAT, 재부팅 후 자동 시작
VPN 클라이언트macOS와 Android WireGuard 앱전체 기본 경로를 터널로 바꾸고 서버까지 암호화 전송

왜 WireGuard인가

이 용도에는 웹 프록시보다 VPN이 맞다. 브라우저뿐 아니라 앱의 트래픽도 같은 경로로 보내야 하기 때문이다. WireGuard는 공개키 기반으로 피어를 구분하고, 설정 항목이 적다. 서버에는 클라이언트 공개키만, 클라이언트에는 서버 공개키만 배포한다.

WireGuard는 사용하지 않을 때 불필요한 통신을 거의 하지 않는다. 이동통신망이나 공유기 NAT 뒤에 있는 Android가 매핑을 유지해야 할 때는 PersistentKeepalive = 25를 사용한다. WireGuard 공식 문서도 여러 방화벽 환경에서 25초를 실용적인 값으로 설명한다.

다시 만들기 전에 준비할 것

OCI 가입과 홈 리전 선택은 Terraform보다 먼저 끝내야 한다. 이 구성은 홈 리전이 ap-mumbai-1인지 확인하고, 다른 리전이면 배포를 중단한다. OCI 인증은 브라우저 세션 토큰 또는 API 키 방식 중 하나를 사용할 수 있다. 어떤 방식을 쓰더라도 OCI 설정 파일, 세션 토큰, API 개인키는 공개 저장소에 올리면 안 된다.

Mac에는 다음 도구가 필요하다.

brew install terraform oci-cli wireguard-tools qrencode

프로젝트에서 공개 가능한 구성 파일은 다음과 같다.

  • vpnctl: setup, plan, deploy, 검증, 서버 전원 명령을 묶은 진입점
  • infra/network.tf: VCN, 서브넷, 게이트웨이, 라우팅, 보안 규칙 정의
  • infra/compute.tf: Ubuntu 이미지, VM shape, VNIC, 부트 볼륨 정의
  • infra/cloud-init.yaml: 최초 부팅 시 패키지 설치, SSH와 커널 보안 설정
  • scripts/configure-server.sh: VM 안에 WireGuard 설정과 NAT 규칙 생성

반대로 다음 경로는 로컬에만 보관한다.

  • infra/local.auto.tfvars.json: tenancy OCID, 관리자 주소, SSH 공개키 등 실제 환경 입력
  • infra/terraform.tfstate: 생성된 OCI 자원의 OCID와 공인 주소를 기록하는 상태
  • secrets/: SSH 개인키, 기기별 WireGuard 개인키
  • generated/: 실제 Endpoint와 개인키가 든 클라이언트 프로필

tfstate에 WireGuard 서버 개인키는 들어가지 않지만 OCI 식별자와 실제 공인 IP가 들어간다. 개인키가 없다는 이유로 공개해도 되는 파일은 아니다.

Always Free 사양을 먼저 다시 확인했다

오래된 글에는 Ampere A1 무료 한도가 4 OCPU, 24GB로 적혀 있는 경우가 많다. 2026년 8월에 확인한 Oracle 공식 문서는 Always Free 사용자가 유지할 수 있는 A1 총량을 2 OCPU, 12GB 메모리로 안내한다. 시간 기준으로는 월 1,500 OCPU 시간, 9,000 GB 시간이다.

AMD 기반 VM.Standard.E2.1.Micro는 최대 두 대가 Always Free 대상이다. 부트 볼륨과 블록 볼륨은 홈 리전에서 합계 200GB까지 무료 범위이며, VM의 기본 부트 볼륨은 50GB다.

이번 구성은 A1 Flex의 1 OCPU, 1GB 메모리, 50GB 부트 볼륨을 먼저 요청했다. Mumbai의 A1 호스트 용량이 없어 Out of host capacity가 발생했고, 자동화가 E2.1.Micro로 한 번만 전환했다. 현재 서버는 E2.1.Micro, 1GB 메모리, 50GB 부트 볼륨으로 동작한다.

중요한 기준은 “무료라고 알려진 이름”이 아니라 생성 직전 공식 문서와 콘솔의 Always Free 표시를 다시 확인하는 것이다. Always Free Compute는 tenancy의 홈 리전에 만들어야 한다.

OCI 구성 요소는 각각 무엇을 하는가

Terraform은 다음 자원을 만든다.

OCI 자원설정과 역할
Tenancy와 compartment루트 compartment를 사용하며 모든 VPN 자원이 만들어지는 OCI 관리 경계
VCN10.10.0.0/16, VPN VM을 담는 독립 가상 네트워크
Public subnet10.10.1.0/24, 공인 IP를 가진 VNIC를 배치할 네트워크 구간
VNIC서브넷 사설 IP, 공인 IP, NSG를 VM에 연결하는 기본 네트워크 카드
공인 IPv4VNIC에 자동 할당되며 WireGuard Endpoint와 외부에 보이는 출구 주소로 사용
Internet GatewayVCN과 공용 인터넷 사이의 출입구
Route table0.0.0.0/0 목적지를 Internet Gateway로 전달
Security list모든 outbound와 필요한 ICMP를 허용해 서브넷 기본 통신과 경로 MTU 탐색 지원
NSGUDP 51820, 제한된 TCP 22, outbound 규칙으로 VPN과 관리 접속 제한
Compute instanceUbuntu 24.04에서 WireGuard, Linux routing, NAT 실행
Boot volume50GB이며 운영체제와 /etc/wireguard 설정을 영구 보관

VCN은 인터넷 회선 자체가 아니라 OCI 안의 사설 네트워크 경계다. 서브넷도 VM이 놓일 주소 구간일 뿐이다. VM이 인터넷과 통신하려면 세 가지가 모두 맞아야 한다.

  1. VNIC가 공인 IPv4를 가져야 한다.
  2. route table의 기본 경로가 Internet Gateway를 향해야 한다.
  3. Security list와 NSG가 필요한 ingress와 egress 패킷을 허용해야 한다.

이 중 하나라도 빠지면 VM은 만들어져도 WireGuard Endpoint에 도달하지 못하거나, 터널 접속 후 인터넷으로 나가지 못한다.

NSG에서 UDP 518200.0.0.0/0에 연다. 휴대전화는 Wi-Fi와 이동통신을 오가며 접속 주소가 바뀌기 때문이다. 반면 SSH 22는 배포 시점의 관리자 공인 IPv4 /32만 허용한다. OCI의 NSG는 VNIC에 적용하는 가상 방화벽이며, ingress와 egress 규칙으로 허용할 패킷을 정한다.

UDP 51820을 전 세계에 열었다고 해서 누구나 VPN을 사용할 수 있는 것은 아니다. WireGuard는 서버에 공개키가 등록된 피어만 암호학적으로 인증한다. 그래도 불필요한 다른 포트는 열지 않고, SSH는 별도의 /32 규칙으로 제한했다.

서버 장애 시 같은 VM을 복구하도록 recovery_action = "RESTORE_INSTANCE"도 설정했다.

Ubuntu VM 안에서 각 프로그램이 하는 일

서버는 cloud-init으로 WireGuard를 설치하고 IPv4 forwarding을 활성화한다.

net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 0

WireGuard 서버 인터페이스는 사설 터널 주소를 가진다.

[Interface]
Address = 10.66.66.1/24, fd42:66:66::1/64
ListenPort = 51820
PrivateKey = <VM 내부에서 생성한 서버 개인키>

PostUp = iptables -I INPUT 1 -p udp --dport 51820 -j ACCEPT
PostUp = iptables -I FORWARD 1 -i wg0 -o ens3 -j ACCEPT
PostUp = iptables -I FORWARD 1 -i ens3 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -s 10.66.66.0/24 -o ens3 -j MASQUERADE
PostUp = ip6tables -I FORWARD 1 -i wg0 -j REJECT

각 요소의 역할은 다음과 같다.

서버 요소역할
cloud-init첫 부팅에서 WireGuard와 자동 보안 업데이트를 설치하고 SSH 비밀번호와 root 로그인을 차단
wg0클라이언트의 암호화 패킷을 복호화하고 터널 내부 주소로 구분
net.ipv4.ip_forwardVM이 자기 앞으로 온 패킷만 처리하지 않고 다른 인터페이스로 전달하도록 허용
iptables INPUTVM 자체가 UDP 51820 패킷을 받도록 허용
iptables FORWARDwg0에서 인터넷 인터페이스로 나가는 요청과 그 응답을 허용
conntrack외부에서 임의로 시작한 연결은 막고, 클라이언트가 시작한 연결의 응답만 터널로 복귀시킴
iptables MASQUERADE터널 사설 주소를 VM의 Mumbai 공인 IPv4로 변환
ip6tables REJECT지원하지 않는 외부 IPv6가 원래 회선으로 새지 않도록 터널에서 차단
wg-quick@wg0설정에 맞춰 인터페이스와 방화벽 규칙을 올리고 재부팅 후 자동 시작

핵심은 IP forwarding과 MASQUERADE의 조합이다. forwarding만 켜면 패킷을 다른 인터페이스로 넘길 수 있지만, 인터넷의 응답 서버는 10.66.66.x 같은 터널 사설 주소로 돌아오는 길을 모른다. MASQUERADE가 출발지 주소를 VM의 Mumbai 공인 IPv4로 바꾸므로 응답이 VM으로 돌아온다. Linux conntrack은 이 응답을 원래 Mac 또는 Android 피어로 다시 연결한다.

실제 외부 인터페이스 이름은 이미지나 shape에 따라 달라질 수 있다. 구성 스크립트는 ip -4 route show default에서 기본 경로 인터페이스를 찾아 사용하므로 ens3를 무조건 가정하지 않는다. 위 예시는 현재 VM에서 확인된 이름이다.

브라우저 요청이 WireGuard와 NAT를 거쳐 Mumbai IP로 나가는 과정

Mac과 Android는 서로 다른 피어다

Mac과 Android에 같은 설정 파일을 복사하지 않았다. 각 기기에서 별도의 개인키를 사용하고, 서버에는 대응하는 공개키 두 개를 등록했다.

[Peer]
PublicKey = <Mac 공개키>
AllowedIPs = 10.66.66.2/32, fd42:66:66::2/128

[Peer]
PublicKey = <Android 공개키>
AllowedIPs = 10.66.66.3/32, fd42:66:66::3/128

이렇게 하면 한 기기를 잃어버렸을 때 해당 피어만 폐기할 수 있다. Android 키를 지워도 Mac 연결은 그대로 유지된다.

클라이언트 설정의 구조는 다음과 같다.

[Interface]
PrivateKey = <이 기기에만 저장할 개인키>
Address = 10.66.66.2/32, fd42:66:66::2/128
DNS = 1.1.1.1, 1.0.0.1
MTU = 1380

[Peer]
PublicKey = <서버 공개키>
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = <Mumbai 공인 IPv4>:51820
PersistentKeepalive = 25

각 항목은 다음 의미다.

설정 항목의미
[Interface] PrivateKey이 기기만 보관하는 신원 증명 값. 서버나 다른 기기에 복사하지 않음
[Interface] Address터널 안에서 이 기기가 사용할 고유 주소
[Interface] DNS터널이 활성화됐을 때 사용할 DNS resolver
[Interface] MTUWireGuard 캡슐화 여유를 확보해 큰 패킷 단편화 문제를 줄임
[Peer] PublicKey접속할 Mumbai 서버가 맞는지 확인하는 서버 공개키
[Peer] AllowedIPs어떤 목적지 트래픽을 이 피어에게 보낼지 결정하는 라우팅 규칙
[Peer] Endpoint암호화된 UDP 패킷을 보낼 Mumbai VM의 공인 주소와 포트
[Peer] PersistentKeepaliveNAT 뒤에서도 서버가 돌아올 수 있도록 주기적으로 매핑 유지

AllowedIPs = 0.0.0.0/0, ::/0은 전체 IPv4와 IPv6 기본 경로를 터널로 보낸다는 뜻이다. 서버에서는 외부 IPv6 forwarding을 막았다. 사용할 수 없는 IPv6를 원래 회선으로 우회시키지 않고 차단해 공인 IPv6 누출을 피한다.

WireGuard 앱은 단순히 설정 파일을 보관하는 프로그램이 아니다. 터널을 켜면 운영체제에 가상 네트워크 인터페이스를 만들고, AllowedIPs에 해당하는 경로를 그 인터페이스로 등록한다. 애플리케이션은 VPN을 따로 의식하지 않는다. 브라우저와 YouTube 앱이 평소처럼 패킷을 보내면 운영체제 라우팅이 WireGuard 쪽을 선택한다.

Mac은 WireGuard 앱에서 .conf 파일을 가져왔다. Android는 별도의 설정으로 만든 QR을 WireGuard 앱에서 스캔했다. QR에는 Android 개인키가 들어 있으므로 인증된 임시 뷰어에만 표시하고, 등록 직후 삭제했다.

실제 요청은 어떻게 Mumbai로 나가는가

  1. 브라우저나 앱이 원래 목적지로 보낼 평문 IP 패킷을 만든다.
  2. AllowedIPs가 만든 기본 경로 때문에 운영체제가 그 패킷을 WireGuard 가상 인터페이스로 보낸다.
  3. WireGuard 앱은 기기 개인키와 서버 공개키를 이용해 패킷을 암호화하고, 새 UDP 패킷 안에 넣는다.
  4. 바깥쪽 UDP 패킷은 현재 Wi-Fi나 이동통신 회선을 이용해 OCI 공인 IP의 51820 포트로 간다.
  5. OCI Internet Gateway, public subnet, VNIC, NSG를 통과한 패킷이 Ubuntu VM에 도착한다.
  6. 서버의 wg0가 등록된 피어 공개키를 확인하고 안쪽 원본 패킷을 복호화한다.
  7. Linux IP forwarding이 원본 패킷을 wg0에서 인터넷 인터페이스로 넘긴다.
  8. iptables NAT가 출발지를 Mumbai VM 공인 IP로 바꾸고, route table과 Internet Gateway가 외부로 전달한다.
  9. 외부 서비스의 응답은 Mumbai VM으로 돌아오고, conntrack이 원래 터널 피어를 찾는다.
  10. 서버가 응답을 다시 WireGuard로 암호화하면 Mac 또는 Android가 복호화해 요청한 앱에 전달한다.

외부 서비스가 보는 것은 3단계의 암호화용 UDP 바깥 주소가 아니라 8단계에서 NAT된 Mumbai VM 공인 주소다. 그래서 브라우저뿐 아니라 운영체제의 기본 경로를 따르는 대부분의 앱이 같은 India 출구를 사용한다.

서버의 wg show에서 Mac과 Android를 별도 피어로 확인할 수 있다. 최근 handshake가 갱신되고 transfer 바이트가 증가하면 해당 기기가 실제로 터널을 사용하고 있다는 뜻이다. 다만 서버에는 목적지 도메인을 기록하는 프록시를 설치하지 않았기 때문에 어떤 웹사이트를 열었는지는 남지 않는다.

리전과 GeoIP는 같은 말이 아니다

VM이 Mumbai 데이터센터에 있다고 해서 모든 GeoIP 데이터베이스가 즉시 그 IP를 India로 분류하는 것은 아니다. 처음 받은 OCI 공인 IP는 일부 서비스에서 Mumbai로, 다른 서비스에서는 United States로 분류됐다.

이 상태에서는 인도 지역 서비스가 미국 IP로 오인할 수 있다. VM은 그대로 두고 임시 공인 IP만 다시 할당했다. 새 주소는 확인한 여러 GeoIP 서비스에서 모두 India로 일치했다. 이후 Terraform state와 두 클라이언트의 Endpoint를 새 주소로 갱신했다.

검증할 때는 한 서비스만 믿지 않고 다음을 함께 본다.

확인 항목원하는 결과이유
OCI 리전ap-mumbai-1실제 VM 배치 위치 확인
두 개 이상의 GeoIP 서비스IN, Mumbai 또는 Maharashtra서비스별 DB 갱신 차이 확인
WireGuard handshake최근 시각클라이언트와 서버의 암호화 연결 확인
WireGuard transfer바이트 증가실제 트래픽 통과 확인

보안 경계

OCI 방화벽 규칙과 WireGuard 개인키 보관 경계

SSH 비밀번호 인증과 root 로그인을 비활성화했다. 서버 접속은 전용 SSH 키로만 허용하며, OCI NSG에서도 관리자 IPv4 한 곳으로 제한한다. WireGuard 서버 개인키는 Terraform에 넣지 않고 VM 안에서 생성했다. 따라서 Terraform state에 서버 개인키가 남지 않는다.

클라이언트 개인키가 든 .conf와 QR은 비밀번호처럼 다뤄야 한다. 채팅, 공개 저장소, 화면 공유에 노출됐다면 해당 기기의 키를 새로 만들고 서버의 공개키를 교체하는 것이 안전하다.

이 VPN은 익명화 서비스가 아니다. Oracle 계정과 VM 사용 기록은 소유자와 연결된다. Oracle은 인프라 차원의 로그를 보유할 수 있고, 외부 서비스는 Oracle 데이터센터 IP를 VPN이나 클라우드 주소로 식별할 수 있다. 개인용 지역 출구 노드에는 적합하지만, 신원 은폐를 보장하지는 않는다.

운영 명령

이번 구축에서는 반복 작업을 vpnctl로 묶었다.

./vpnctl setup          # 키 생성, 홈 리전, 관리자 IP 확인
./vpnctl plan           # 생성될 OCI 자원 검토
./vpnctl deploy         # VM 생성, WireGuard 구성, 프로필 생성
./vpnctl verify         # 현재 출구 국가 확인

Oracle VM 전원만 관리하는 명령도 추가했다.

./vpnctl server-status
./vpnctl server-start
./vpnctl server-stop

server-stop은 VM을 정상 종료하고 부트 볼륨과 WireGuard 설정을 보존한다. 단, Mac이나 Android의 VPN 스위치가 켜진 채 서버를 끄면 전체 기본 경로가 응답 없는 터널을 향해 인터넷이 막힐 수 있다. 클라이언트 터널을 먼저 끈 뒤 서버를 정지해야 한다.

평소에는 Oracle VM과 wg-quick@wg0를 계속 실행해 두고, Mac과 Android에서 필요할 때만 터널을 켜는 방식이 단순하다. wg-quick@wg0는 systemd에서 enabled 상태라 VM이 재부팅돼도 자동으로 올라온다.

처음부터 다시 만드는 순서

vpnctl 기준으로 전체 생성 순서는 다음과 같다.

  1. OCI 계정을 만들 때 Mumbai를 홈 리전으로 선택한다.
  2. Mac에 Terraform, OCI CLI, WireGuard 도구를 설치한다.
  3. OCI CLI를 브라우저 세션 또는 API 키 방식으로 인증한다.
  4. ./vpnctl setup을 실행한다. 이 단계가 홈 리전 확인, 현재 관리자 IP 확인, SSH 키와 Mac, Android 키 생성을 맡는다.
  5. ./vpnctl plan으로 region, VM shape, 부트 볼륨, ingress 규칙을 확인한다.
  6. ./vpnctl deploy를 실행한다. Terraform이 OCI 자원을 만들고, 스크립트가 SSH와 cloud-init 완료를 기다린 뒤 WireGuard 서버를 구성한다.
  7. 배포 스크립트가 서버 공개키를 받아 generated/mac.confgenerated/android.conf를 만든다.
  8. Mac 앱에는 mac.conf를 가져오고, Android 앱에는 ./vpnctl qr android로 표시한 QR을 스캔한다.
  9. 한 기기씩 터널을 켜고 ./vpnctl verify와 서버의 wg show로 국가, handshake, 전송량을 확인한다.

deploy는 A1을 먼저 요청한다. OCI가 명확한 capacity 오류를 반환할 때만 E2.1.Micro로 한 번 전환하며, 인증이나 권한 같은 다른 오류를 용량 문제로 오인해 재시도하지 않는다.

VM이 완전히 재생성되면 공인 Endpoint와 서버 키가 바뀔 수 있다. 로컬 Mac과 Android 키는 재사용할 수 있지만 새 서버 공개키와 Endpoint가 들어간 프로필을 다시 생성해 앱에 가져와야 한다. 기존 프로필의 주소만 임의로 고치는 것보다 deploy가 만든 새 프로필 전체를 다시 가져오는 편이 안전하다.

어디가 고장 났는지 확인하는 법

증상확인할 요소와 의미
handshake가 전혀 없음VM 전원, Endpoint, UDP 51820 NSG, 기기와 서버 공개키를 확인한다. 암호화 터널 자체가 성립하지 않은 상태다.
handshake는 있으나 인터넷 불가IP forwarding, FORWARD 규칙, NAT, OCI route table과 egress를 확인한다. 터널 뒤 인터넷 전달 경로 문제다.
일부 사이트만 열리지 않음MTU, DNS, GeoIP 또는 서비스의 클라우드 IP 차단을 확인해 터널 전체 장애와 구분한다.
인터넷은 되지만 India가 아님클라이언트 터널 상태, AllowedIPs, 확인 서비스를 본다. 트래픽이 원래 회선으로 나갈 수 있다.
India와 다른 국가 결과가 섞임여러 GeoIP 데이터베이스를 비교한다. OCI 리전이 아니라 IP 분류 DB 차이일 수 있다.
SSH만 접속 불가현재 관리자 IP와 NSG의 /32를 확인하고 회선 변경 후 ./vpnctl allow-ssh로 갱신한다.
서버 재부팅 후 VPN 불가systemctl status wg-quick@wg0wg show로 systemd 자동 시작과 WireGuard 설정을 확인한다.

서버에서 wg show의 latest handshake는 인증된 터널이 최근 성립했는지를 보여주고, transfer 값은 실제 바이트가 오갔는지를 보여준다. handshake가 있다는 사실만으로 NAT와 인터넷 출구까지 정상이라고 단정할 수는 없다. 마지막에는 클라이언트에서 두 곳 이상의 국가 확인 서비스를 조회해야 한다.

참고 자료

관련 글

← 목록으로