🔥 NAT Gateway: Private Subnet의 외부 연결
강의 목차

처음 private subnet에 EC2 한 대를 띄우고 apt-get update를 친 순간, 명령은 그냥 멈춰 있었다. 라우팅 테이블도 보안 그룹도 다 맞춰 둔 줄 알았는데. 그제야 지난 편 Internet Gateway: 외부로 나가는 유일한 길에서 IGW는 경계 한 줄짜리 객체이고, public IPv4가 없으면 패킷이 멈춘다 라고 정리해 둔 게 떠올랐다. private subnet 인스턴스에는 그 public IPv4가 없다. 그래서 다른 박스가 하나 더 필요하다.
그 박스가 NAT Gateway다.
박스 한 개의 위치
NAT Gateway는 public subnet 안에 연결되는 매니지드 NAT 서비스다. AZ에 묶인 자원이고, 기본적으로 EIP 한 개를 붙여 둔다. 그 EIP가 곧 바깥에서 본 한 인스턴스의 얼굴이다. 같은 NAT GW를 가리키는 private 인스턴스가 100대든 1만 대든 인터넷 쪽에서는 그 한 개의 IP만 노출한다 (필요하면 secondary IP를 더 붙일 수 있는데, 그 얘기는 뒤에서 따로 한다).
Internet Gateway: 외부로 나가는 유일한 길에서 IGW는 logically one-to-one NAT라고 적었다. NAT Gateway는 거기에서 한 글자만 다르다. N:1 NAT. NAT는 여러 인스턴스의 사설 IP를 자기 EIP 하나로 합친다. 그래서 인스턴스마다 EIP를 붙여 줄 필요가 없고, 그래서 한 박스가 바깥으로 나가는 좁은 통로 역할을 한다.

"단방향"이라는 말의 정확한 뜻
다이어그램과 자료에서는 outbound only나 단방향 이라는 표현을 자주 쓴다. 처음 봤을 때 나도 이 부분에서 한참 헤맸다. 응답 패킷도 못 들어오는 줄 알았다. 그게 아니다.
NAT Gateway는 stateful한 connection tracking 테이블을 유지한다. 안쪽 인스턴스가 바깥 80번 포트에 SYN을 던지면, NAT는 (인스턴스 사설 IP, 포트) ↔ (NAT EIP, 포트) 한 항목을 만들어 둔다. 그 항목 위에서 오는 응답 패킷은 거꾸로 매핑되어 인스턴스로 잘 돌아온다. 막히는 것은 바깥에서 먼저 시작하는 새 연결 이다. 인터넷의 어떤 호스트도 NAT Gateway의 EIP에 처음으로 손을 뻗어서 안쪽 인스턴스에 닿을 수 없다. 그게 보안 속성이고, 단방향 이라는 말의 정확한 의미다.
패킷 한 줄의 여행
private 인스턴스가 https://example.com을 부르면 패킷이 가는 길은 이렇다.
- 인스턴스의 라우트 테이블이 0.0.0.0/0 → NAT Gateway 한 줄을 본다. (라우팅 테이블: 패킷이 어디로 가는지에서 이 매칭 규칙을 봤다.)
- 패킷은 같은 VPC 안 NAT Gateway까지 간다. NAT는 출발지 IP를 자기 사설 IPv4로 바꾸고 conntrack 항목을 적어 둔다. (이 단계는 처음 마주치면 헷갈리기 쉽다. NAT GW 안에서 사설 IP가 곧장 EIP로 가지 않는다.)
- NAT Gateway가 속한 public subnet의 라우트 테이블이 다시 0.0.0.0/0 → IGW를 본다.
- IGW가 1:1 NAT 동작으로 NAT의 사설 IPv4를 NAT에 연결된 EIP로 매핑해 바깥 인터넷에 흘려보낸다.
응답은 같은 길을 거꾸로. NAT의 conntrack 항목이 응답을 보고 인스턴스 사설 IP로 다시 매핑한다. 한 흐름에 NAT GW 한 번, IGW 한 번. AWS는 NAT를 시간당+GB당으로 청구하고, IGW 자체는 처리 요금이 0이지만 AWS는 그 EIP의 PublicIPv4 시간당 비용을 별도 항목으로 청구한다.

AZ에 갇힌 박스
여기가 처음 NAT를 그릴 때 가장 자주 헛디디는 대목이다. NAT Gateway는 AZ 자원 이다. 한 NAT GW는 한 AZ 안에서만 살고, 그 AZ가 죽으면 같이 죽는다.
그래서 진짜 운영 토폴로지는 AZ당 NAT GW 한 개 + AZ당 RT 한 개다. ap-northeast-2-a의 private subnet은 ap-northeast-2-a의 NAT를 가리키고, ap-northeast-2-c의 private subnet은 ap-northeast-2-c의 NAT를 가리킨다. 한 AZ가 죽어도 다른 AZ는 자기 NAT로 계속 나간다. 단일 NAT GW로 cross-AZ 트래픽까지 빼면, AZ 간 data transfer 비용이 한 번 더 붙고, 단일 장애점도 같이 생긴다.

2025-11에 Regional NAT Gateway라는 변형이 새로 나왔다. AZ에 묶지 않고 워크로드가 있는 AZ로 자동으로 퍼지는 모드다. 옵트인이고, 기본은 여전히 AZ-scoped(이제 Zonal 이라고 부른다). 새 토폴로지를 짜고 단순화가 우선이면 후보지만, 이 글의 기본 그림은 Zonal 쪽이다.
다시 비용
비용 구조는 Subnet: Public과 Private의 진짜 차이에서 본 그대로다. ap-northeast-2 기준 시간당 $0.045 + 처리 GB당 $0.045, 두 줄. 여기서는 한 줄만 더 박아 둔다. AWS는 그 비용을 AZ 수만큼 그대로 곱해 청구한다. 3-AZ 권장 토폴로지를 따르면 NAT GW 시간당 비용도 그대로 3배다.
첫 글에서 다룬 "관리형이 감추는 비용" 흐름에서 NAT는 IGW의 PublicIPv4 시간당 $0.005보다 한 자릿수 더 무거운 항목이다.
55,000이라는 숫자
운영에서 의외로 자주 부딪히는 한도가 IPv4 한 개와 unique destination 한 쌍당 동시 55,000 연결이라는 한도다. 같은 외부 endpoint(외부 OAuth provider, 외부 결제 게이트웨이, 외부 DB) 한 곳으로 fan-out 하는 워크로드라면 가까워지는 숫자다. 한도를 넘기면 새로 시도한 connection이 silent하게 실패한다.
탈출구 두 가지: secondary IPv4 주소를 NAT GW에 최대 7개까지 더 붙여 1+7=8개로 8 × 55,000 = 440,000까지 늘리거나, NAT GW 자체를 분할한다 (Public NAT의 EIP 연관 한도는 기본 2개라, 8개까지 채우려면 service quota 증액 요청이 같이 필요하다). AWS가 secondary IP attach 기능을 따로 둔 건 이 한도 때문이다.
대역폭 자체는 5 Gbps에서 시작해 AWS가 100 Gbps까지 자동 확장한다. 패킷 처리량은 1M PPS에서 10M PPS까지. 워크로드 패턴에 따라 다르긴 하지만, 내가 본 한도 이슈는 대역폭보다 connection 쪽이 압도적으로 많았다.
350초 RST: silent failure 지점
idle 350초가 지난 TCP connection을 NAT Gateway는 자기 conntrack 테이블에서 지운다. 다음 패킷이 오면 RST 한 번 돌려보내고 끝. FIN이 아니다. application이 350초 idle을 모르고 그 위에 메시지를 다시 쓰면 connection reset 한 번 맞고 그대로 종료한다. TCP keepalive를 350초보다 짧게 두는 게 표준 처방이다.
한 가지 헷갈리는 대목이 있다. 2023-11에 AWS가 도입한 configurable connection tracking idle timeout은 NAT GW의 350초 자체를 바꾸는 기능이 아니다. 그쪽은 ENI 단위, Nitro 인스턴스의 보안 그룹 conntrack을 다루는 별도 기능이다. NAT GW의 350초는 여전히 고정값이다.
언제 NAT GW를 쓰지 말아야 하는가
두 번째 짚을 큰 흐름. 답은 Gateway Endpoint다.
S3와 DynamoDB는 Gateway Endpoint라는 별도의 경로를 제공한다. private subnet에서 S3/DynamoDB로 가는 트래픽을 VPC 라우팅 테이블에 한 줄 추가하는 것만으로 NAT Gateway를 거치지 않게 만들 수 있다. 시간당 비용 0, 처리 GB당 비용 0. AWS의 흔치 않은 무료 경로.
private subnet 워크로드의 outbound가 주로 S3 다운로드(데이터 처리 잡, 컨테이너 이미지 캐시, 정적 자산 빌드 같은 케이스)인 경우, Gateway Endpoint 없이 NAT만 쓰면 청구서가 그대로 NAT 처리 GB당 $0.045 항목으로 차곡차곡 쌓는다. Gateway Endpoint를 같이 그어 두면 그 비용이 0이 된다.
다만 Gateway Endpoint를 지원하는 서비스는 S3와 DynamoDB 단 둘 이다. 다른 AWS 서비스에 사설로 닿고 싶으면 Interface Endpoint(PrivateLink)인데, AWS는 그쪽에 시간당 + GB당 두 항목을 따로 청구한다.

NAT Instance 한 줄
옛 이름으로 NAT Instance가 있었다. EC2 인스턴스 한 대에 NAT 라우팅을 직접 돌리는 방식이다. AWS는 deprecated 처리는 안 했지만 NAT Gateway로 마이그레이션을 권장하는 입장이고, 운영 환경에서 NAT Instance를 새로 만드는 사례는 거의 보이지 않는다. 포트 포워딩이 꼭 필요하거나 dev 환경 비용을 마지막 한 푼까지 깎는 경우 정도가 예외다.
마치며
프로덕션에서는 매번 같은 세 줄을 함께 적는다. AZ당 NAT GW 한 개. AZ당 라우트 테이블. S3/DynamoDB Gateway Endpoint 한 줄. 단일 NAT으로 cross-AZ를 빼지 않고, S3 트래픽은 NAT 청구서에서 빼낸다. 그 세 줄이 NAT Gateway라는 박스가 청구서 1등으로 올라가는 가장 흔한 흐름을 미리 차단한다.
참고 자료
- NAT gateway basics: AWS VPC User Guide: NAT Gateway의 정의·한도·동작
- Pricing for NAT gateways: AWS VPC User Guide: 시간당·GB당 과금 모델
- Compare NAT gateways and NAT instances: AWS: NAT Gateway vs NAT Instance 권장 사항
- Gateway endpoints: AWS PrivateLink: S3/DynamoDB 무료 사설 길
- Introducing Amazon VPC Regional NAT Gateway: AWS Networking & Content Delivery: 2025-11 발표된 Regional 모드
- Attach multiple IPs to a NAT Gateway: AWS: 55,000 한도 확장











