🔥 Network ACL: Subnet 레벨 방화벽과의 차이

1551자
18분

Subnet 경계에 놓인 AWS 오렌지(#ff9900) 색의 줄무늬 검문소가 외부에서 들어오는 굵은 파란 패킷을 먼저 받는 구도. Subnet 안쪽에는 슬레이트 그레이 EC2 서버 세 대가 나란히 놓여 있고 각 서버 앞에는 작은 에메랄드 그린(#10b981) 'SG' 방패 배지가 붙어 있다. NACL을 통과한 인바운드 화살표는 첫 번째 서버의 SG에 도달하고, 응답으로 빠져나가는 얇은 파란 화살표가 NACL 검문소로 향하는 옆에 작은 점선 콜아웃 'response not auto-allowed'이 덧붙어 NACL의 무상태 성격을 한 장으로 보여 주는 구조도. 흰 배경, 둥근 모서리, 절제된 그림자, 깔끔한 산세리프 글꼴의 표지 일러스트레이션

보안 그룹 한 장을 잘 적어 두면 NACL은 거의 손댈 일이 없다. 그런데도 VPC 콘솔에는 Network ACL 메뉴는 콘솔에 따로 한 칸을 두고 있고, 어떤 환경에서는 반드시 손이 가는 도구가 된다.

Subnet 경계에 놓인 한 장의 검문소

Security Group: 인스턴스 레벨 방화벽에서 본 SG는 ENI 한 장에 붙는다. NACL은 그보다 한 칸 바깥, Subnet 경계에 붙는다. 패킷은 Subnet에 들어오는 순간 NACL을 먼저 거치고, 그 다음에 ENI 앞에서 SG를 거치는 두 단계 통과 구조로 동작한다.

매달리는 규칙이 단순하다. Subnet 한 장에는 NACL이 정확히 한 개. 한 NACL은 여러 Subnet에 동시에 매달릴 수 있지만, 한 Subnet이 두 NACL을 동시에 매달지는 못한다. 그리고 NACL을 명시적으로 매달지 않은 Subnet은 VPC가 함께 만들어 준 default NACL이 자동으로 매달린 상태에서 출발한다.

여기서 한 가지 함정이 있다. VPC를 만들 때 함께 생기는 default NACL과 콘솔에서 새로 만든 custom NACL은 출발점이 정반대다. 이 비대칭은 뒤에서 한 번 더 짚는다.

Stateless라는 한 줄의 무게

NACL의 첫인상은 짧은 공식 문구 한 줄이 결정한다. "NACLs are stateless, which means that information about previously sent or received traffic is not saved." (verified 2026-04-26) 흐름을 기억하지 않는다는 뜻이다.

이게 운영에 무엇을 강요하는지가 진짜 이야기다. SG는 인바운드를 한 줄 허용하면 그에 대한 응답 outbound가 자동으로 통과한다. NACL은 아니다. 인바운드를 허용했다는 사실은 outbound 평가에 단 한 비트도 영향을 주지 않는다. 응답 패킷이 나가려면 outbound rule에 그 응답을 받아 줄 한 줄이 따로 적혀 있어야 한다.

두 개의 세로 패널로 분리된 비교 다이어그램. 왼쪽 패널 헤더 'Security Group: stateful'. 클라이언트 노트북 아이콘에서 출발한 굵은 파란 화살표가 AWS 오렌지(#ff9900) 박스 'SG inbound rule: allow TCP 443'을 통과해 슬레이트 그레이 서버 아이콘에 도달하고, 그 서버에서 위로 올라가는 에메랄드 그린(#10b981) 응답 화살표가 같은 SG 박스를 다시 통과하는 옆에 녹색 체크와 'response auto-allowed via connection tracking'이라는 라벨. 오른쪽 패널 헤더 'Network ACL: stateless'. 클라이언트에서 출발한 화살표가 두 개의 별도 게이트 'NACL inbound: allow TCP 443'과 'NACL outbound: must explicitly allow ephemeral 1024-65535'를 차례로 통과해 서버에 도달하는 모습. 응답 화살표는 outbound 게이트에 코랄 레드(#ef4444) 'X' 표식과 함께 'dropped: no rule covers ephemeral port' 라벨이 붙어 있어 outbound 룰이 비면 응답이 끊긴다는 점을 표시. 두 패널 아래 한 줄짜리 캡션 'NACL needs both directions explicitly defined.' 흰 배경, 둥근 모서리, 절제된 그림자, 깔끔한 산세리프 글꼴의 다이어그램

문제는 응답이 어떤 포트로 돌아오느냐를 내가 정하지 못한다는 점이다. 클라이언트가 80포트로 요청을 보내면, 응답은 클라이언트가 잡은 ephemeral port 한 곳으로 돌아온다. 이 범위가 OS마다 다르다 (verified 2026-04-26).

  • Linux 커널: 32768 – 61000
  • Windows Server 2008 이상: 49152 – 65535
  • Elastic Load Balancing / NAT Gateway / AWS Lambda: 1024 – 65535

운영에서 어떤 클라이언트가 들어올지 모르는 public-facing 환경이면 AWS 권고대로 1024–65535를 통째로 열어 두는 게 보통이다. 좁히면 어떤 OS의 클라이언트 응답이 끊기고, 끊기면 stateless라서 디버깅 흔적도 인스턴스 안에는 남지 않는다. SG에서는 신경 쓰지 않던 부분이, NACL에서는 매번 양방향 짝을 맞춰야 한다는 뜻이다.

Linux 클라이언트(ephemeral 32768-61000)에서 출발한 요청 화살표가 AWS 오렌지(#ff9900) 줄무늬 게이트 'NACL: subnet edge'와 그 다음 작은 'SG: ENI' 게이트를 차례로 통과해 ':80' 라벨이 붙은 서버 아이콘에 도달하는 흐름. 화살표 위에는 'request: dst tcp/80'이라는 라벨, 서버에서 다시 클라이언트로 돌아가는 응답 화살표 옆에는 'src tcp/80, dst tcp/49321 (ephemeral)'이라는 라벨과 'auto-allowed: stateful'이라는 SG 측 메모. 그 아래 코랄 레드(#ef4444) 경고 박스에 'can be blocked here unless outbound ephemeral range is allowed'라는 한 줄과 옆에 OS별 ephemeral 범위 표 'Linux: 32768-61000', 'Windows 2008+: 49152-65535', 'ELB / NAT GW / Lambda: 1024-65535'가 표시되어 있고, 그 아래 슬레이트 그레이 캡션 'when in doubt, AWS docs recommend opening 1024-65535 outbound'. 흰 배경, 둥근 모서리, 절제된 그림자, 깔끔한 산세리프 글꼴의 다이어그램

번호 순서로 평가하는 첫-매치 방식

NACL 룰은 번호가 붙는다. 범위는 1부터 32766까지. AWS 공식 표현 그대로 "We evaluate the rules in order, starting with the lowest numbered rule, when deciding whether allow or deny traffic. If the traffic matches a rule, the rule is applied and we do not evaluate any additional rules."

첫 매치에서 즉시 판정이 끝난다는 점이 SG와는 다르다. SG는 허용 규칙 중 하나라도 일치하면 통과인 OR 연산이고, NACL은 번호 낮은 한 줄이 단독으로 결정을 정한다. 그래서 NACL은 한 줄의 위치가 결정 자체를 바꾼다. 100번에 deny가 적혀 있으면, 200번에 allow가 같은 트래픽을 적어 놨어도 그 트래픽은 deny로 끝난다.

NACL 인바운드 룰 한 장의 내부 평가 흐름을 보여 주는 다이어그램. 위쪽에 작은 패킷 아이콘과 "packet" 라벨이 함께 표시되어 있고, 그 아래로 다섯 개의 가로 직사각형 룰 행이 세로로 늘어서 있다. 위에서부터 '100  ALLOW  TCP 80  0.0.0.0/0'이 AWS 오렌지(#ff9900) 강조와 에메랄드 그린(#10b981) 체크 마크로 통과 표시되어 있고, 그 옆에서 굵은 화살표 한 줄이 오른쪽으로 빠져나가 'allowed' 라벨의 서버 아이콘에 도착. 그 아래 '110  DENY  TCP 22  198.51.100.0/24', '120  ALLOW  ALL  10.0.0.0/16', '200  ALLOW  TCP 443  0.0.0.0/0' 세 줄은 회색으로 흐려진 채 점선 가로줄로 가로질러 'not evaluated' 효과를 줬고, 가장 아래의 짙은 슬레이트 그레이 막대 '*  DENY  ALL  0.0.0.0/0' 옆에는 작은 자물쇠 아이콘과 "cannot delete" 라벨. 위쪽에는 슬레이트 그레이 콜아웃 'rules: 1 to 32766. first match wins.'. 흰 배경, 둥근 모서리, 절제된 그림자, 깔끔한 산세리프 글꼴의 다이어그램

번호의 끝에는 늘 한 줄이 위치한다. rule number가 별표(*). 1–32766 범위 바깥에 위치하고, 모든 트래픽 deny이며, 변경도 삭제도 불가다. 어떤 룰에도 매칭되지 않은 패킷은 이 한 줄이 패킷을 차단한다. NACL의 마지막 결정은 늘 침묵의 deny인 셈.

default NACL과 custom NACL은 출발점이 정반대

같은 이름의 객체지만 생성 경로에 따라 시작 구성이 정반대다.

  • VPC를 만들 때 자동으로 생기는 default NACL: 인바운드 100번에 모든 IPv4 트래픽 allow, 아웃바운드 100번에 모든 IPv4 트래픽 allow, 그리고 양쪽 모두 끝에 * deny가 들어 있다. VPC에 IPv6 CIDR이 붙어 있으면 101번 라인에 IPv6 allow 한 줄이 더 들어간다. 결과는 모든 트래픽 통과. Subnet에 따로 NACL을 연결하지 않으면 AWS는 이 default NACL을 자동으로 연결해 평소와 같은 통과 동작을 유지한다.
  • 콘솔이나 API로 새로 만든 custom NACL: 인바운드 deny 한 줄, 아웃바운드 deny 한 줄. 그게 끝이다. 연결되는 순간 NACL이 그 Subnet의 모든 트래픽을 차단한다.

두 개의 카드가 나란히 놓인 비교 다이어그램. 왼쪽 카드 헤더 'default NACL'. 카드 본문 세 줄: 첫 줄 AWS 오렌지(#ff9900) '100  inbound ALLOW all', 둘째 줄 같은 색 '100  outbound ALLOW all', 셋째 줄 슬레이트 그레이 '*  DENY cannot delete'. 카드 아래 에메랄드 그린(#10b981) 둥근 배지 'all traffic flows'. 오른쪽 카드 헤더 'custom NACL (just created)'. 카드 본문 두 줄: 'inbound DENY'와 'outbound DENY'가 별표 룰로만 표시. 카드 아래 코랄 레드(#ef4444) 둥근 배지 'all traffic blocked'. 두 카드 시작 모양이 정반대임을 한 장에 보여 주는 비교형 그림. 흰 배경, 둥근 모서리, 절제된 그림자, 깔끔한 산세리프 글꼴의 다이어그램

이걸 모르고 "보안을 강화하려고 NACL을 새로 만들어 production Subnet에 매달았다"는 시나리오가 의외로 자주 사고를 친다. 새 custom NACL은 기본이 차단이라, 연결하는 순간 그 Subnet 위의 모든 인스턴스가 즉시 외부와의 통신을 잃는다. SG의 새 그룹이 0줄 인바운드 + 활짝 열린 아웃바운드로 시작했던 것과는 반대 방향의 위험이다.

룰 번호를 100, 200, 300으로 적는 건 미신이 아니다

AWS 공식 예제가 100, 105, 110, 115, 120 같은 5–10 단위로 번호를 잡는다. 권장 패턴은 단순하다. 나중에 사이에 끼워 넣을 여유를 남겨라. NACL은 한 줄을 원자적으로 다른 위치에 옮기는 명령이 없다. 옮기려면 같은 규칙을 새 번호로 추가하고 옛 번호의 규칙을 지우는 두 단계로 풀어야 한다. 그 사이 짧은 시간이 룰이 비어 있는 시간이고, stateless라 운영 중에는 그 짧은 공백을 신경 쓰게 된다.

100, 200, 300 단위로 잡고, 나중에 105, 110으로 끼워 넣고, 정말 빡빡해질 때만 재배치하는 패턴이 운영에 잘 통한다.

한도, SG와는 다른 곱셈

숫자 몇 개는 외워 두는 편이 낫다 (verified 2026-04-26).

  • VPC당 NACL: 200개 (조정 가능)
  • NACL당 인바운드 룰: 20줄 기본, 최대 40줄까지 조정 가능 (성능 영향 가능)
  • NACL당 아웃바운드 룰: 20줄 기본, 최대 40줄까지 조정 가능
  • IPv4 룰과 IPv6 룰은 AWS가 합산해서 한 카운트로 센다 (Security Group: 인스턴스 레벨 방화벽에서 본 SG와 다른 점: SG는 IPv4와 IPv6를 별개로 셌다)

SG가 ENI당 SGs × rules ≤ 1,000 한도까지 끌어쓸 수 있던 것과 비교하면 NACL은 훨씬 빡빡하다. 일상 ACL을 NACL에 적으려는 시도가 한도에서 막히는 이유도 같은 맥락이다. 한도 자체가 NACL은 일상 ACL이 아니라 굵직한 차단 한두 줄을 위한 영역이라는 설계 의도를 드러낸다.

그래서 언제 꺼내는가

NACL은 일상 보안 격리에 쓰는 도구가 아니다. 일반 트래픽 정책은 SG 한 장으로 거의 다 처리한다.

NACL이 진짜 필요한 시점은 좁다. 알려진 악성 IP 대역을 Subnet 단위로 통째로 차단해야 할 때. 또는 컴플라이언스 요건이 "Subnet 경계에 명시적 차단 룰을 반드시 적어야 한다고 명시할 때. 둘 다 SG의 허용만 적는다는 설계로는 풀리지 않는 시나리오다. 그게 NACL이 따로 존재하는 이유의 거의 전부다.

반대로, 일상 보안을 NACL에 박으려는 시도는 stateless 운영 부담을 떠안는 짓이 된다. 양방향 짝을 매번 맞춰야 하고, ephemeral 범위를 매 룰마다 의식해야 하고, 한도가 빡빡해서 룰이 빨리 차고, 첫-매치 방식이라 한 줄의 위치에 대한 인지 부담이 따라온다. SG로 풀 일을 NACL로 풀면 그 부담을 매일 진다.

ALB Health Check가 끊기는 흔한 함정

ALB가 매달린 Subnet의 NACL을 너무 좁게 잡으면 health check가 멈춘다. ALB의 ephemeral은 1024–65535이므로, target 인스턴스 Subnet의 outbound NACL에 그 범위가 비어 있으면 응답이 안 돌아간다. 모니터링이 왜 인스턴스가 살아 있는데 unhealthy로 빠지는가를 묻기 시작하면 NACL을 먼저 본다.

이건 모든 케이스를 커버하는 룰은 아니다. 단지 NACL을 처음 만질 때 가장 자주 밟는 함정이다. NACL은 굵게 한 줄을 그어야 할 때만 꺼내는 도구라는 것만 잡고 있으면, 평소에는 SG가 하던 역할에 그대로 머문다. 다음 편에서는 서로 다른 VPC가 어떻게 이어지는지가 핵심 관찰점이다.

YouTube 영상

채널 보기
AI는 데이터를 어떻게 분류할까? 벡터의 거리와 KNN 알고리즘 | 선형대수학
행렬의 가장 중요한 연산 - 행렬 곱셈 | 선형대수학
트라이(Trie) 자료구조: 파이썬으로 삽입(Insert) 연산 구현하기 | Trie 자료구조 이야기
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학
벡터의 정의와 덧셈 연산 | 선형대수학
AI 추천 시스템의 원리, 벡터 사이의 각도와 코사인 유사도 | 선형대수학
트라이(Trie)를 이용한 자동 완성 알고리즘 | Trie 자료구조 이야기
AI는 왜 수백 차원의 벡터를 사용할까? 고차원 공간과 행렬 | 선형대수학