infra
Platform

모듈 맵

[Network] 인바운드/아웃바운드 포트 접속 장애 실전 디버깅

0 / 35 완료

펼치기
0 / 35 완료0%

네트워크 트러블슈팅 · 24 / 35

[Network] 인바운드/아웃바운드 포트 접속 장애 실전 디버깅

보안그룹을 열었는데도 연결이 안 되는 상황을 5단계 진단 플로우로 체계적으로 추적합니다 — Security Group, NACL, Route Table, IGW/NAT, OS 방화벽 계층별 점검

🚨INCIDENT ALERT
HIGH

새 EC2에 8080 포트를 열었는데 외부에서는 계속 타임아웃이 납니다. Security Group은 분명 허용했지만, NACL·라우팅·OS 방화벽 중 어디가 막는지 아무도 확신하지 못합니다.

클라우드 네트워크 장애는 한 화면만 보고 끝나지 않습니다. 패킷이 통과하는 계층을 순서대로 좁혀야 합니다.

AWS 보안그룹 실전 디버깅

첫 번째 클라우드 인프라를 구성한 날, 팀장이 말했습니다. "80포트 열어서 웹 서버 올려봐." Security Group 인바운드에 TCP 80을 추가하고, EC2를 올렸는데 브라우저에 아무것도 뜨지 않습니다. telnet을 해봐도 Connection timed out. "분명히 열었는데 왜 안 되지?" 클라우드를 처음 다루는 엔지니어가 절반 이상 겪는 상황입니다. Security Group만 봐서는 이유를 모릅니다 — 클라우드 네트워크에는 트래픽이 통과해야 하는 관문이 Security Group 외에도 여러 겹 존재하기 때문입니다. 이 챕터에서는 "포트를 열었는데 안 된다"는 상황을 5단계 진단 플로우로 체계적으로 좁혀나가는 방법을 배웁니다.


이번 챕터에서 배울 것
  • 1Security Group의 Stateful 동작 원리와 인바운드/아웃바운드 연결 추적 메커니즘을 설명할 수 있다
  • 2aws ec2 describe-security-groups / describe-network-interfaces로 부착 상태를 확인할 수 있다
  • 3포트를 열었는데 안 될 때 SG → NACL → Route Table → IGW/NAT → OS 방화벽 5단계 진단 플로우를 적용할 수 있다
  • 4VPC Flow Logs에서 REJECT 로그를 읽고 차단 지점을 특정할 수 있다
  • 5NACL과 Security Group을 Stateless/Stateful 관점에서 비교하고 실무 함의를 판단할 수 있다
  • 6EC2 두 대 사이의 포트 차단·허용 시나리오를 실습할 수 있다
실습 환경 준비
AWS CLI 설치 및 자격증명 확인
aws sts get-caller-identity
AWS CLI 버전 확인 (v2 필요)
aws --version
실습에 사용할 리전 환경변수 설정 (서울 리전 기준)
export AWS_DEFAULT_REGION=ap-northeast-2
EC2 인스턴스 목록 확인 (실습 인스턴스가 Running 상태인지 확인)
aws ec2 describe-instances --query 'Reservations[*].Instances[*].[InstanceId,State.Name,PrivateIpAddress,PublicIpAddress]' --output table
실습 요구사항

AWS 계정, IAM 사용자(ec2:Describe* 권한, logs:FilterLogEvents 권한), AWS CLI v2 이상. EC2 인스턴스가 없는 경우 describe 명령은 aws-cli 출력 형식 학습 용도로 사용하세요.


💡개념

Security Group Stateful 동작 원리

Security Group을 처음 배울 때 가장 많이 혼동하는 부분이 Stateful 동작입니다. 온프레미스 ACL이나 iptables에 익숙한 엔지니어라면 "응답 트래픽도 아웃바운드 규칙에서 허용해야 하지 않나?"라는 의문이 자연스럽게 생깁니다. 결론부터 말하면 — Security Group에서는 그럴 필요가 없습니다.

Security Group Stateful 동작 원리 — 보안그룹은 stateful이라 인바운드로 허용된 연결의 응답은 아웃바운드 규칙 없이도 자동 허용. 명시적 거부(deny)가 없어 허용 규칙의 합집합으로 동작하며, 인바운드만 신경 쓰면 됨. NACL(stateless, 응답도 별도 허용 필요)과 대비되는 핵심 차이확대

연결 추적(Connection Tracking)이란

Security Group은 내부적으로 모든 연결의 상태를 추적합니다. TCP 3-way handshake가 성공해 연결이 수립되면, 이 연결의 응답 패킷은 아웃바운드 규칙을 확인하지 않고 자동으로 허용됩니다. 이것이 Stateful 방화벽의 핵심입니다.

Security Group 연결 추적 — 인바운드 허용된 연결의 응답·후속 패킷은 아웃바운드 규칙을 보지 않고 연결 추적 매칭으로 자동 허용확대

Stateful이 실무에서 의미하는 것

아웃바운드 기본 규칙이 "전체 허용(0.0.0.0/0)"인 경우, Security Group 설정에서 인바운드 규칙만 신경 쓰면 됩니다. 인바운드에서 허용한 연결의 응답은 아웃바운드 설정과 무관하게 돌아갑니다.

아웃바운드를 제한하는 경우 — 예를 들어 DB 서버가 외부 인터넷으로 나가는 것을 막고 싶을 때 — 에는 아웃바운드 규칙을 수정합니다. 이때도 인바운드에서 허용된 연결의 응답은 영향을 받지 않습니다. Stateful이기 때문입니다.

아웃바운드 기본 규칙을 제거하면 어떻게 되나

아웃바운드 규칙을 모두 제거한 경우:

1. 외부에서 이 EC2로 들어오는 인바운드 연결 → 응답 트래픽은 자동 허용 (Stateful)
2. 이 EC2에서 외부로 나가는 아웃바운드 연결 → 차단됨 (규칙 없음)

즉, 아웃바운드 규칙 제거는 "이 서버가 먼저 연결을 시작하는 것"만 막습니다.
외부에서 들어온 연결에 대한 응답은 여전히 나갑니다.

이 동작 원리를 이해하지 못하면 "아웃바운드를 다 막았는데 왜 응답이 가지?"라는 혼란이 생깁니다. 연결을 시작한 방향이 중요한 것이 Stateful의 핵심입니다.

💡개념

패킷 한 개가 보안그룹을 통과하는 순서 — 도착부터 응답까지

앞에서 "SG는 stateful"이라는 개념을 봤다면, 이번에는 패킷 한 개가 실제로 어느 순서로 평가되는지를 따라갑니다. 이 순서를 알면 "인바운드만 열었는데 왜 응답까지 되지", "SG는 열었는데 왜 timeout이지"를 단계로 좁힐 수 있습니다. 클라이언트가 EC2의 8080으로 붙는 한 번의 연결에서, 보안그룹 안쪽에서는 아래 순서가 일어납니다.

TEXT
[외부 클라이언트]  TCP SYN → EC2:8080
   │
   ① 패킷이 인스턴스 ENI에 도착
   │    (NACL이 있으면 서브넷 NACL 인바운드를 먼저 통과해야 함)
   │
   ② 인바운드 SG 규칙 평가 — 화이트리스트
   │    → 허용 규칙 중 출발지·포트가 매칭되는 게 하나라도 있나?
   │    → 있으면 통과 / 하나도 없으면 조용히 드롭 (거부 규칙 자체가 없음)
   │
   ③ 연결 추적(conntrack) 등록 — 여기서부터 stateful
   │    → 이 연결의 5-튜플(출발지·목적지 IP/포트/프로토콜)을 상태 테이블에 기록
   │
   ④ 응답 패킷 (EC2 → 클라이언트)
   │    → 아웃바운드 SG 규칙을 보지 않고 conntrack 매칭으로 자동 허용
   │
   ⑤ (NACL이 있으면) 응답은 NACL 아웃바운드도 통과해야 함
   │    → NACL은 stateless라 ephemeral 포트(1024-65535) 허용이 따로 필요
   ▼
[클라이언트]  응답 수신 → 연결 성립

각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:

단계하는 일여기서 막히면
① ENI 도착라우팅으로 패킷이 인스턴스 ENI까지 전달. NACL이 있으면 서브넷 인바운드 규칙을 먼저 평가라우팅 없음·NACL deny → 응답 없이 Connection timed out
② 인바운드 SG 평가허용 규칙(출발지·포트)을 훑어 매칭이 하나라도 있으면 통과. SG는 화이트리스트라 거부 규칙이 없고, 매칭이 없으면 그냥 버린다포트·출발지 규칙 없음 → 조용히 드롭 → Connection timed out (RST 아님)
③ conntrack 등록통과한 연결의 5-튜플을 상태 테이블에 기록. 이 기록이 stateful의 실체거의 안 막힘 (초대량 연결로 conntrack 테이블이 포화되면 신규 연결 드롭)
④ 응답 자동 허용등록된 연결의 응답·후속 패킷은 아웃바운드 규칙을 보지 않고 conntrack 매칭으로 통과아웃바운드를 다 지워도 이 응답은 나감 — 대신 인스턴스가 새로 여는 아웃바운드만 막힘
⑤ NACL 아웃바운드NACL을 쓴다면 응답은 stateless라 ephemeral 포트로 돌아가는 아웃바운드를 별도 허용해야 함ephemeral 미허용 → 요청은 갔는데 응답만 막혀 Connection timed out

SG만 보면 "인바운드 ②만 맞으면 응답 ④는 저절로 된다" 가 핵심입니다. 그래서 접속이 안 될 때 Connection timed out이면 ①·②·⑤(경로·인바운드 SG·NACL 응답) 중 하나가 패킷을 삼킨 것이고, Connection refused면 ①②는 통과해 인스턴스까지 도달했으나 그 포트에 앱이 없다는 뜻입니다 — SG/NACL 문제가 아니라 ss -tlnp로 넘어갈 신호입니다.


실습 1: Security Group 부착 상태 확인

보안그룹이 올바른 인스턴스에 붙어있는지, 어떤 규칙이 설정되어 있는지 AWS CLI로 직접 확인하는 방법입니다. 콘솔 UI보다 CLI로 확인하면 여러 인스턴스를 한 번에 비교하거나 자동화에 활용할 수 있습니다.

1EC2 인스턴스에 부착된 Security Group 조회

인스턴스 ID로 어떤 Security Group이 붙어있는지 확인합니다. 먼저 인스턴스 목록을 조회해 실습에 사용할 ID를 파악합니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/networking/part5/exam_23 && cd /tmp/networking/part5/exam_23

# 인스턴스 목록에서 ID 확인
aws ec2 describe-instances \
  --query 'Reservations[*].Instances[*].[InstanceId,State.Name,Tags[?Key==`Name`].Value|[0]]' \
  --output table

# 인스턴스 ID로 부착된 Security Group 목록 조회
aws ec2 describe-instances \
  --instance-ids i-0abc12345def67890 \
  --query 'Reservations[*].Instances[*].SecurityGroups' \
  --output table

# 출력 예시:
# -------------------------------------------------
# |          DescribeInstances                    |
# +-------------------------+---------------------+
# |     GroupId             |      GroupName      |
# +-------------------------+---------------------+
# |  sg-0a1b2c3d4e5f67890  |  web-server-sg      |
# |  sg-0f9e8d7c6b5a43210  |  shared-monitoring  |
# +-------------------------+---------------------+
aws ec2 describe-instances --instance-ids i-0abc12345def67890 --query 'Reservations[*].Instances[*].SecurityGroups' --output table
🔍실행 후 확인할 것
  • GroupId, GroupName 목록 확인 — 예상한 Security Group이 부착됐는지 확인
  • 예상과 다른 SG가 있거나, 필요한 SG가 없으면 attach/detach 필요
  • ALB/RDS는 자체 ENI를 가지므로 EC2 SG와 별도로 확인해야 함
2Security Group 규칙 상세 조회

Security Group ID로 인바운드 규칙 전체를 조회합니다. 어느 포트가 어느 IP 대역에 허용됐는지 확인하는 가장 직접적인 방법입니다.

로컬 터미널
# Security Group ID로 규칙 상세 조회
aws ec2 describe-security-groups \
  --group-ids sg-0a1b2c3d4e5f67890 \
  --query 'SecurityGroups[*].{
    Name:GroupName,
    Inbound:IpPermissions[*].{
      Protocol:IpProtocol,
      FromPort:FromPort,
      ToPort:ToPort,
      CIDR:IpRanges[*].CidrIp
    }
  }' \
  --output json

# 이름에 "web"이 포함된 Security Group 검색
aws ec2 describe-security-groups \
  --filters "Name=group-name,Values=*web*" \
  --query 'SecurityGroups[*].[GroupId,GroupName,VpcId]' \
  --output table

# ALB나 RDS 같은 관리형 서비스의 ENI 조회
aws ec2 describe-network-interfaces \
  --filters "Name=group-id,Values=sg-0a1b2c3d4e5f67890" \
  --query 'NetworkInterfaces[*].{ENI:NetworkInterfaceId,Type:InterfaceType,Description:Description}' \
  --output table
aws ec2 describe-security-groups --group-ids sg-0a1b2c3d4e5f67890 --query 'SecurityGroups[*].{Name:GroupName,Inbound:IpPermissions[*].{Protocol:IpProtocol,FromPort:FromPort,ToPort:ToPort,CIDR:IpRanges[*].CidrIp}}' --output json
🔍실행 후 확인할 것
  • Inbound 규칙에서 허용된 Protocol, FromPort, ToPort, CIDR 확인
  • 0.0.0.0/0으로 열린 포트는 인터넷 전체 허용 — SSH(22), DB(3306/5432)는 위험
  • eni type=elasticloadbalancing 이면 ALB ENI — EC2 SG 수정이 ALB SG에 영향 없음

💡개념

'포트 열었는데 안 된다' 5단계 진단 플로우

Security Group만 보고 "열었는데 왜 안 되지?"를 반복하는 것은 그 외 4개의 관문을 확인하지 않았기 때문입니다. 클라우드에서 트래픽이 EC2에 도달하기까지 통과해야 하는 관문은 순서대로 5개입니다.

Security Group 디버그 5단계 — 접속 불가 시 ① 인바운드 규칙에 출발지·포트가 허용됐는지 → ② 올바른 SG가 인스턴스/ENI에 연결됐는지 → ③ NACL이 막지 않는지 → ④ OS 방화벽(firewalld/ufw) → ⑤ 프로세스 LISTEN 여부 순으로 점검해 차단 지점을 특정확대

5단계 진단 순서

연결 실패 5단계 진단 순서 — Security Group→NACL→Route Table→IGW/NAT→OS 방화벽 순으로 관문을 좁혀가며 차단 지점을 찾는다확대

각 단계별 점검 포인트

1단계: Security Group

로컬 터미널
# 확인할 것:
# - 해당 포트가 인바운드 규칙에 있는가?
# - 소스 IP가 허용 범위에 포함되는가?
# - Security Group이 올바른 인스턴스에 붙어있는가?

aws ec2 describe-security-groups --group-ids sg-xxxxx \
  --query 'SecurityGroups[*].IpPermissions'

2단계: Network ACL (NACL)

로컬 터미널
# NACL은 서브넷 단위로 적용됩니다
# 인스턴스가 속한 서브넷의 NACL을 확인합니다
aws ec2 describe-network-acls \
  --filters "Name=association.subnet-id,Values=subnet-xxxxx" \
  --query 'NetworkAcls[*].Entries' \
  --output table

# 인바운드와 아웃바운드 각각 허용 규칙이 있는지 확인
# Egress: false = 인바운드, true = 아웃바운드
# RuleAction: allow | deny

3단계: Route Table

로컬 터미널
# 인스턴스가 있는 서브넷의 라우팅 테이블 확인
aws ec2 describe-route-tables \
  --filters "Name=association.subnet-id,Values=subnet-xxxxx" \
  --query 'RouteTables[*].Routes[*].{Dest:DestinationCidrBlock,Target:GatewayId}' \
  --output table

# 0.0.0.0/0 → igw-xxxx : Public 서브넷 (인터넷 직접 연결)
# 0.0.0.0/0 → nat-xxxx : Private 서브넷 (아웃바운드만)
# 경로 없음             : 인터넷 도달 불가

4단계: IGW / NAT GW 상태

로컬 터미널
# IGW 연결 상태 확인
aws ec2 describe-internet-gateways \
  --filters "Name=attachment.vpc-id,Values=vpc-xxxxx" \
  --query 'InternetGateways[*].{IGW:InternetGatewayId,State:Attachments[0].State}'

# NAT Gateway 상태 확인 (Private 서브넷의 아웃바운드용)
aws ec2 describe-nat-gateways \
  --filter "Name=vpc-id,Values=vpc-xxxxx" \
  --query 'NatGateways[*].{NAT:NatGatewayId,State:State,SubnetId:SubnetId}'

5단계: OS 방화벽

로컬 터미널
# EC2 내부에서 확인 (SSH 접속 후)
# Amazon Linux 2 / RHEL 계열
sudo firewall-cmd --list-all

# Ubuntu 계열
sudo ufw status verbose

# iptables 직접 확인 (모든 배포판)
sudo iptables -L INPUT -n -v --line-numbers

# 포트가 실제로 리스닝 중인지 확인
sudo ss -tlnp | grep :8080

에러 메시지로 단계 추측하기

Connection timed out (연결 타임아웃, 무반응):
  → Security Group 또는 NACL이 DROP/REJECT 중
  → Route Table에 경로가 없거나 IGW가 없음
  → OS 방화벽이 DROP으로 설정됨

Connection refused (연결 거부):
  → EC2는 도달했지만 해당 포트에서 아무것도 리스닝하지 않음
  → 또는 OS 방화벽이 REJECT로 응답
  → 애플리케이션이 실행되지 않았거나 다른 포트를 사용 중

No route to host:
  → Route Table에 목적지 경로가 없음
  → 또는 NACL이 DENY 응답을 보냄

실습 2: VPC Flow Logs에서 REJECT 로그 읽기

VPC Flow Logs는 ENI를 통과하는 트래픽을 기록합니다. Security Group이나 NACL이 패킷을 차단할 때 REJECT가 기록됩니다. 이 로그를 읽으면 어느 계층에서, 어느 방향으로 트래픽이 막히는지를 알 수 있습니다.

Flow Log 레코드 형식은 다음과 같습니다:

OUTPUT
2 123456789012 eni-0ab1c2d3e4f56789 203.0.113.50 10.0.2.100 52341 8080 6 1 40 1620000000 1620000060 REJECT OK

각 필드의 의미는 다음과 같습니다.

필드예시 값의미
version2레코드 버전
account-id123456789012계정 ID
interface-ideni-0ab1c2d3e4f56789ENI ID
srcaddr / dstaddr203.0.113.50 / 10.0.2.100소스 IP / 목적지 IP
srcport / dstport52341 / 8080소스 포트 / 목적지 포트
protocol6프로토콜 (6=TCP, 17=UDP)
packets / bytes1 / 40패킷 수 / 바이트 수
start / end1620000000 / 1620000060시작 / 종료 시각 (epoch)
actionREJECT허용(ACCEPT) / 차단(REJECT)
log-statusOK로그 기록 상태
3CloudWatch Logs에서 REJECT 로그 필터링

VPC Flow Logs가 CloudWatch에 저장된 경우, REJECT 패턴으로 필터링해 어느 트래픽이 차단됐는지 확인합니다.

로컬 터미널
# Flow Logs가 CloudWatch Logs에 저장된 경우
# 로그 그룹 목록 확인
aws logs describe-log-groups \
  --log-group-name-prefix "/aws/vpc/flowlogs" \
  --query 'logGroups[*].logGroupName'

# REJECT 로그만 필터링 (최근 1시간)
aws logs filter-log-events \
  --log-group-name "/aws/vpc/flowlogs/vpc-xxxxx" \
  --filter-pattern "REJECT" \
  --start-time $(date -d '1 hour ago' +%s000) \
  --query 'events[*].message' \
  --output text

# 특정 소스 IP의 REJECT만 필터링 (실제 장애 대응 시 유용)
aws logs filter-log-events \
  --log-group-name "/aws/vpc/flowlogs/vpc-xxxxx" \
  --filter-pattern "203.0.113.50 REJECT" \
  --query 'events[*].message' \
  --output text

# 특정 목적지 포트에 대한 REJECT 확인
aws logs filter-log-events \
  --log-group-name "/aws/vpc/flowlogs/vpc-xxxxx" \
  --filter-pattern "8080 REJECT" \
  --query 'events[*].message' \
  --output text
aws logs filter-log-events --log-group-name '/aws/vpc/flowlogs/vpc-xxxxx' --filter-pattern 'REJECT' --start-time $(date -d '1 hour ago' +%s000) --query 'events[*].message' --output text
🔍실행 후 확인할 것
  • action=REJECT: Security Group 또는 NACL이 차단 — SG 규칙 점검
  • 인바운드 REJECT: srcaddr가 외부 IP, dstaddr가 EC2 Private IP
  • 아웃바운드 REJECT: srcaddr가 EC2 Private IP, dstaddr가 외부 IP
  • Flow Logs에 항목 자체가 없으면: Route Table 문제로 패킷이 ENI까지 도달 못한 것

REJECT 로그로 차단 지점 판별

Flow Logs에서 REJECT가 보이는 경우:
  → Security Group 또는 NACL이 차단 중
  → VPC Flow Logs는 인스턴스 ENI 기준으로 기록
  → 인바운드 REJECT = ENI에 들어오기 전에 차단 = Security Group 또는 NACL

Flow Logs에서 ACCEPT가 보이는데 연결이 안 되는 경우:
  → Security Group/NACL은 통과했지만 인스턴스까지 도달
  → 원인: OS 방화벽(iptables), 앱이 리스닝하지 않음, 프로세스 크래시

Flow Logs에 항목 자체가 없는 경우:
  → Route Table 문제로 패킷이 ENI에 도달조차 못함
  → 또는 Flow Logs 자체가 설정되지 않음

실전 패턴: 인바운드 차단 vs 아웃바운드 차단 구분

로컬 터미널
# 인바운드 차단 로그 예시 (외부 → EC2)
# srcaddr가 외부 IP, dstaddr가 EC2 Private IP
2 123456789012 eni-abc12345 203.0.113.50 10.0.2.100 52341 8080 6 1 40 ... REJECT OK

# 아웃바운드 차단 로그 예시 (EC2 → 외부)
# srcaddr가 EC2 Private IP, dstaddr가 외부 IP
2 123456789012 eni-abc12345 10.0.2.100 52.95.110.50 34521 443 6 1 40 ... REJECT OK

# EC2 간 통신 차단 (같은 VPC 내)
2 123456789012 eni-abc12345 10.0.1.50 10.0.2.100 45678 3306 6 1 40 ... REJECT OK

💡개념

NACL vs Security Group — Stateless vs Stateful의 실무 함의

VPC 모듈에서 두 방화벽의 개념적 차이를 배웠다면, 여기서는 그 차이가 실제 트러블슈팅에서 어떤 함정을 만드는지를 봅니다. NACL의 Stateless 특성은 경험이 없으면 절대 직관적으로 알 수 없습니다.

핵심 비교

항목Security GroupNetwork ACL (NACL)
적용 단위인스턴스(ENI)서브넷
상태 추적Stateful (연결 추적)Stateless (연결 추적 없음)
기본 동작모든 인바운드 차단, 모든 아웃바운드 허용모든 트래픽 허용 (기본 NACL)
규칙 평가모든 규칙 AND 조건으로 평가규칙 번호 순서대로, 첫 매칭에서 결정
허용/거부허용 규칙만 (명시적 거부 불가)허용과 거부 모두 설정 가능
응답 트래픽자동 허용별도 아웃바운드 규칙 필요

NACL Stateless가 만드는 함정 — ephemeral 포트

클라이언트가 TCP 연결을 시작할 때, OS는 임의의 **ephemeral 포트(임시 포트, 1024-65535)**를 소스 포트로 사용합니다. 서버 응답은 이 포트로 돌아가야 합니다.

NACL Stateless와 ephemeral 포트 함정 — 클라이언트의 임시 포트(1024-65535)로 돌아오는 응답을 NACL 인바운드에서 따로 허용해야 한다확대

Security Group이라면 연결 추적을 통해 52341 포트 응답을 자동 허용합니다. NACL은 그렇지 않습니다.

언제 NACL을 쓰고 언제 Security Group을 쓰는가

Security Group으로만 충분한 경우:

  • 대부분의 일반 워크로드
  • 인스턴스별 세밀한 접근 제어가 필요한 경우
  • 다른 Security Group을 소스로 지정하고 싶을 때 (예: app-sg에서 db-sg로만 허용)

NACL을 추가로 사용하는 경우:

  • 서브넷 단위로 특정 IP 대역을 명시적으로 차단해야 할 때 (DDoS 방어)
  • 규정 준수(Compliance)로 서브넷 경계에서의 명시적 차단 기록이 필요할 때
  • Security Group 규칙 한도(기본 60개 per SG)를 초과하는 복잡한 구성

실무 권장 패턴:

기본 NACL은 전체 허용으로 두고 (기본값),
세밀한 접근 제어는 Security Group으로만 관리합니다.
NACL 수정은 ephemeral 포트 함정 때문에 위험하므로
명확한 필요가 있을 때만 건드립니다.

실습 3: EC2 두 대 사이 포트 차단·허용 시나리오

Security Group으로 EC2 간 통신을 제어하는 가장 실무적인 패턴입니다. "IP 대신 Security Group ID를 소스로 지정"하는 방법이 핵심입니다.

시나리오: 웹 서버(web-ec2)에서 DB 서버(db-ec2)의 MySQL(3306) 허용

로컬 터미널
# 1단계: web-ec2에 부착된 Security Group ID 확인
WEB_SG=$(aws ec2 describe-instances \
  --instance-ids i-0abc12345 \
  --query 'Reservations[*].Instances[*].SecurityGroups[0].GroupId' \
  --output text)
echo "Web SG: $WEB_SG"

# 2단계: db-ec2의 Security Group에 web-sg를 소스로 3306 허용
DB_SG="sg-0db1234567890abcd"

aws ec2 authorize-security-group-ingress \
  --group-id $DB_SG \
  --protocol tcp \
  --port 3306 \
  --source-group $WEB_SG

# 성공 출력:
# { "Return": true, "SecurityGroupRules": [...] }
로컬 터미널
# 3단계: 규칙이 적용됐는지 확인
aws ec2 describe-security-groups \
  --group-ids $DB_SG \
  --query 'SecurityGroups[*].IpPermissions[?ToPort==`3306`]' \
  --output json

# UserIdGroupPairs 섹션에 web-sg ID가 보이면 성공
# IP 대역이 아닌 SG ID가 소스로 지정됨

특정 규칙 제거 (차단 복원)

로컬 터미널
# 방금 추가한 규칙 제거 (차단 상태로 되돌리기)
aws ec2 revoke-security-group-ingress \
  --group-id $DB_SG \
  --protocol tcp \
  --port 3306 \
  --source-group $WEB_SG

# 제거 후 재확인
aws ec2 describe-security-groups \
  --group-ids $DB_SG \
  --query 'SecurityGroups[*].IpPermissions[?ToPort==`3306`]'
# [] 빈 배열이 나오면 규칙 제거 완료

EC2 내부에서 포트 통신 직접 검증

로컬 터미널
# web-ec2 내부에서 db-ec2로 포트 연결 테스트 (SSH 접속 후)
# nc(netcat)으로 TCP 연결만 테스트
nc -zv 10.0.2.100 3306 -w 3

# 성공: Connection to 10.0.2.100 3306 port [tcp/mysql] succeeded!
# 실패(SG 차단): nc: connect to 10.0.2.100 port 3306 (tcp) failed: Connection timed out
# 실패(앱 없음): nc: connect to 10.0.2.100 port 3306 (tcp) failed: Connection refused
로컬 터미널
# 여러 포트를 한 번에 점검하는 스크립트
DB_HOST="10.0.2.100"
for port in 3306 5432 6379 27017; do
  result=$(nc -zv -w 2 $DB_HOST $port 2>&1)
  if echo "$result" | grep -q "succeeded"; then
    echo "PORT $port ($(grep -w $port /etc/services 2>/dev/null | head -1 | awk '{print $1}' || echo 'unknown')): OPEN"
  else
    echo "PORT $port: CLOSED/FILTERED"
  fi
done

두 에러의 차이

보안그룹 디버깅에서 이 두 에러는 전혀 다른 원인을 가리킵니다. 에러 메시지만으로도 원인 계층을 절반까지 좁힐 수 있습니다.

Connection timed out

로컬 터미널
# 현상
nc -zv 10.0.2.100 8080 -w 5
# nc: connect to 10.0.2.100 port 8080 (tcp) timed out

curl --connect-timeout 5 http://10.0.2.100:8080
# curl: (28) Connection timed out after 5001 milliseconds

의미: 클라이언트가 SYN 패킷을 보냈지만 아무 응답이 없습니다. 패킷이 방화벽에 의해 조용히 DROP되고 있습니다.

원인 가설:

  • Security Group 인바운드에 해당 포트 규칙 없음 (기본 DROP)
  • NACL이 해당 트래픽을 DENY
  • OS 방화벽(iptables)이 DROP 규칙으로 응답 없이 차단
  • Route Table에 경로가 없어 패킷이 전달되지 않음

점검 순서:

로컬 터미널
# 1. SG 규칙 확인
aws ec2 describe-security-groups --group-ids sg-xxxxx \
  --query 'SecurityGroups[*].IpPermissions[?ToPort==`8080`]'

# 2. Flow Logs에서 REJECT 확인
aws logs filter-log-events \
  --log-group-name "/aws/vpc/flowlogs/vpc-xxxxx" \
  --filter-pattern "10.0.2.100 REJECT"

# 3. EC2 내부 iptables 확인 (SSH 가능한 경우)
sudo iptables -L INPUT -n | grep 8080

Connection refused

로컬 터미널
# 현상
nc -zv 10.0.2.100 8080 -w 3
# nc: connect to 10.0.2.100 port 8080 (tcp) failed: Connection refused

curl --connect-timeout 5 http://10.0.2.100:8080
# curl: (7) Failed to connect to 10.0.2.100 port 8080: Connection refused

의미: 클라이언트의 SYN 패킷에 서버가 RST(Reset)으로 즉시 응답합니다. 패킷은 목적지에 도달했지만, 해당 포트에서 아무것도 리스닝하지 않고 있습니다.

원인 가설:

  • 애플리케이션이 실행되지 않음 (프로세스 크래시, 미배포)
  • 애플리케이션이 다른 포트에서 리스닝 중 (8080 대신 8081 등)
  • OS 방화벽이 REJECT 설정 (DROP이 아닌 REJECT)

점검 순서:

로컬 터미널
# EC2 내부에서 포트 리스닝 상태 확인 (SSH 접속 후)
sudo ss -tlnp | grep :8080
# 아무 출력 없음 → 아무것도 리스닝하지 않음

# 실제로 뭐가 리스닝 중인지 전체 확인
sudo ss -tlnp

# 프로세스 상태 확인
ps aux | grep -E "java|node|python|nginx"
systemctl status my-app

빠른 판별 공식

Connection timed out = 패킷이 사라짐 = 방화벽 또는 라우팅 문제
Connection refused   = 패킷이 도달함 = 앱 문제 (리스닝 안 함)

timed out → Security Group / NACL / Route Table / OS DROP 규칙 점검
refused   → ss -tlnp / ps aux / systemctl status 점검

증상

같은 VPC 안에 있는 EC2 2대 중 하나에서만 RDS 연결이 됩니다. 동일한 앱, 동일한 연결 정보인데도 한쪽에서는 연결이 거부됩니다.

DB 클라이언트
# EC2 A에서 (성공)
psql -h mydb.xxxx.ap-northeast-2.rds.amazonaws.com -U admin -d mydb
# psql (15.2)
# Type "help" for help. mydb=#

# EC2 B에서 (실패)
psql -h mydb.xxxx.ap-northeast-2.rds.amazonaws.com -U admin -d mydb
# psql: error: connection to server on host "mydb.xxxx.rds.amazonaws.com" failed:
# Connection refused

원인 진단

로컬 터미널
# RDS Security Group의 인바운드 규칙 확인
aws ec2 describe-security-groups \
  --group-ids sg-rds-group-id \
  --query 'SecurityGroups[].IpPermissions[]'

# 출력 결과 예시:
# "IpRanges": []
# "UserIdGroupPairs": [{ "GroupId": "sg-ec2-group-A" }]
# → EC2 A의 SG만 허용, EC2 B의 SG는 없음

# EC2 B의 Security Group ID 확인
aws ec2 describe-instances --instance-ids i-ec2-b-id \
  --query 'Reservations[].Instances[].SecurityGroups'
# [{ "GroupId": "sg-ec2-group-B" }]  ← 다른 SG

해결

로컬 터미널
# RDS SG에 EC2 B의 SG 추가
aws ec2 authorize-security-group-ingress \
  --group-id sg-rds-group-id \
  --protocol tcp \
  --port 5432 \
  --source-group sg-ec2-group-B

# 변경 후 즉시 테스트 (SG 규칙은 실시간 반영)
psql -h mydb.xxxx.rds.amazonaws.com -U admin -d mydb

예방: 동일 역할 EC2는 동일 SG를 사용하거나, 역할 기반 SG를 미리 설계합니다.


심화 — 규칙은 다 열었는데 "연결은 되고 큰 데이터만 멈춘다"

💡개념

심화: 핸드셰이크는 되는데 큰 응답이 사라진다 — PMTUD 블랙홀과 MSS

SG도 열었고 Flow Log도 ACCEPT인데 "연결은 되는데 큰 데이터만 멈춘다"는 신고가 옵니다. 이건 규칙 문제가 아니라 패킷 크기 문제입니다. SG/NACL 다섯 관문을 아무리 다시 봐도 안 나오는 이유가 여기 있습니다.

  • MTU와 DF 비트: 경로 상 가장 작은 MTU를 넘는 IP 패킷은 쪼개거나(fragment) 버려져야 합니다. TCP는 보통 DF(Don't Fragment) 비트를 세워 보내므로, 중간 라우터는 쪼개는 대신 발신자에게 ICMP type 3 code 4(Fragmentation Needed, MTU 이만큼)를 돌려줘야 합니다. 발신자는 이를 받고 세그먼트 크기를 줄입니다 — 이것이 Path MTU Discovery(PMTUD)입니다.
  • 블랙홀의 성립: 그 ICMP를 NACL·SG·중간 방화벽이 막으면, 발신자는 "너무 크다"는 신호를 못 받고 같은 큰 패킷을 계속 재전송하다 조용히 유실합니다. 반면 핸드셰이크·짧은 요청 같은 작은 패킷은 통과하니 "연결은 되는데 큰 응답·업로드만 멈춘다"가 됩니다. VPN·Direct Connect·오버레이 네트워크처럼 터널 헤더로 MTU가 1500보다 줄어드는 구간에서 특히 흔합니다.
  • Flow Log 함정: 이 경우 규칙 자체는 통과했으므로 Flow Log에는 ACCEPT로 찍힙니다. REJECT를 찾아 헤매면 원인을 영영 못 만납니다 — ACCEPT인데 안 되는 것이 오히려 단서입니다.
  • 두 갈래 대책: ① ICMP Fragmentation Needed(type 3 code 4)를 NACL 인바운드/아웃바운드와 SG에 허용해 PMTUD를 살립니다. ② 그래도 안 되면 MSS clamping으로 애초에 큰 패킷을 안 만듭니다 — 리눅스는 iptables의 TCPMSS --clamp-mss-to-pmtu, 또는 인터페이스 MTU를 경로 최소값(예: 1400)에 맞춰 낮춥니다. TCP는 SYN에서 MSS를 협상하므로, MSS를 낮추면 처음부터 경로에 맞는 크기로만 보냅니다.

상황: 8080을 열고 Flow Log에서 ACCEPT까지 확인했습니다. 그런데 작은 헬스체크 curl은 200이 즉시 오는데, 수십 KB 이상 큰 응답은 중간에서 멈춥니다. SSH도 붙긴 하는데 긴 디렉터리 목록을 내면 화면이 특정 지점에서 얼어붙습니다. 하필 온프레미스와 VPC를 VPN으로 잇는 경로입니다.

원인: 경로 중간(VPN 터널)의 MTU가 1500보다 작은데, DF 비트가 켜진 큰 TCP 패킷이 그 구간에서 거절됩니다. 이를 알리는 ICMP Fragmentation Needed(type 3 code 4)가 NACL·SG에 막혀 PMTUD가 블랙홀에 빠졌습니다. 발신자는 세그먼트를 줄이라는 신호를 못 받고 큰 패킷만 반복 유실하며, 작은 패킷은 통과하니 겉으로는 "연결됨"으로 보입니다.

진단: ping에 DF 비트를 세워 크기를 스윕합니다 — ping -M do -s 1472 대상은 성공하는데 -s 1500 근처부터 실패하면 경로 MTU가 낮은 것입니다. tcpdump로 같은 큰 세그먼트가 재전송만 반복되고 ICMP 응답이 없는 것을 확인합니다. Flow Log는 ACCEPT라 여기서는 단서가 없다는 사실 자체가 방향을 알려줍니다.

해결: NACL과 SG에 ICMP type 3(destination unreachable), 특히 code 4를 허용해 PMTUD를 복구합니다. 그래도 통제가 어려우면 게이트웨이나 인스턴스에서 MSS clamping을 적용하거나(iptables -t mangle의 TCPMSS --clamp-mss-to-pmtu) 인터페이스 MTU를 경로 최소값에 맞춰 낮춥니다. 근본은 터널 MTU를 고려해 TCP MSS가 처음부터 작게 협상되도록 만드는 것입니다.


💼
실무 맥락
현업 패턴

클라우드 인프라 담당자의 신규 서비스 배포 전 보안그룹 체크리스트

신규 서비스를 배포할 때마다 Security Group 설정을 처음부터 만드는 팀이 있고, 체크리스트대로 검증하는 팀이 있습니다. 장애가 적은 팀은 항상 후자입니다. 아래는 실제 배포 전 5분이면 확인할 수 있는 체크리스트입니다.

배포 전 확인 사항

로컬 터미널
#!/bin/bash
# sg-preflight-check.sh — 신규 서비스 배포 전 Security Group 점검 스크립트

INSTANCE_ID="${1:-}"
if [ -z "$INSTANCE_ID" ]; then
  echo "사용법: $0 <instance-id>"
  exit 1
fi

echo "=== Security Group 배포 전 체크리스트 ==="
echo ""

# 1. 부착된 SG 목록
echo "[1] 부착된 Security Group:"
aws ec2 describe-instances \
  --instance-ids $INSTANCE_ID \
  --query 'Reservations[*].Instances[*].SecurityGroups[*].[GroupId,GroupName]' \
  --output table

# 2. 0.0.0.0/0으로 열린 인바운드 포트 (과도한 공개 접근 확인)
echo ""
echo "[2] 인터넷 전체(0.0.0.0/0)에 열린 인바운드 포트:"
SG_IDS=$(aws ec2 describe-instances \
  --instance-ids $INSTANCE_ID \
  --query 'Reservations[*].Instances[*].SecurityGroups[*].GroupId' \
  --output text)

for sg in $SG_IDS; do
  aws ec2 describe-security-groups \
    --group-ids $sg \
    --query "SecurityGroups[*].IpPermissions[?IpRanges[?CidrIp=='0.0.0.0/0']].[{Port:FromPort,Proto:IpProtocol}]" \
    --output table
done

# 3. 22번 포트(SSH) 허용 범위 확인
echo ""
echo "[3] SSH(22) 접근 허용 범위:"
for sg in $SG_IDS; do
  aws ec2 describe-security-groups \
    --group-ids $sg \
    --query 'SecurityGroups[*].IpPermissions[?FromPort==`22`].IpRanges[*].CidrIp' \
    --output text
done

echo ""
echo "=== 체크리스트 완료 ==="

점검 기준

체크 항목권장 설정
SSH(22) 소스 IP0.0.0.0/0이면 즉시 수정 → 팀 VPN IP 대역 또는 Bastion SG ID만 허용
DB 포트(3306/5432) 소스0.0.0.0/0이면 즉시 수정 → 앱 서버 SG ID만 허용
서비스 포트(80/443) 소스0.0.0.0/0 허용(Public 서비스) 또는 ALB SG ID만 허용(Private 배포)
아웃바운드 규칙기본 전체 허용 유지(대부분) → 규정상 제한 필요 시에만 수정
SG당 규칙 수30개 이하 유지(가독성) → 초과 시 SG 분리 검토

자주 하는 실수 TOP 3

  1. IP 대신 SG ID를 써야 하는데 IP를 씀

    • EC2 IP는 재시작 시 바뀔 수 있음
    • 다른 EC2 허용 시 항상 SG ID를 소스로 사용
  2. 인바운드만 확인하고 아웃바운드를 제거함

    • 누군가 "보안 강화"로 아웃바운드를 다 지웠을 때
    • EC2에서 외부 패키지 설치(yum, apt)가 안 됨
    • Stateful이므로 인바운드 응답은 영향 없지만 EC2 시작 요청은 차단됨
  3. SG를 직접 수정했는데 ALB/RDS에 적용 안 됨

    • ALB와 RDS는 자체 ENI와 SG를 가짐
    • EC2 SG 수정이 ALB SG에 영향을 주지 않음
    • aws ec2 describe-network-interfaces로 실제 부착 위치 확인 필요

명령어·단축키 빠른 참조

이 모듈에서 다룬 보안그룹 디버깅 명령을 실전 옵션과 함께 모았습니다. 콘솔 대신 CLI로 확인하면 여러 계층을 한 번에 비교할 수 있습니다.

명령어/단축키용도자주 쓰는 예
aws ec2 describe-security-groupsSG 인바운드/아웃바운드 규칙 조회--group-ids sg-xxx --query 'SecurityGroups[*].IpPermissions'
aws ec2 describe-instances인스턴스에 부착된 SG 확인--instance-ids i-xxx --query 'Reservations[*].Instances[*].SecurityGroups'
aws ec2 describe-network-acls서브넷 NACL 인/아웃 규칙 확인--filters "Name=association.subnet-id,Values=subnet-xxx"
aws ec2 describe-route-tables0.0.0.0/0 → igw/nat 경로 확인--filters "Name=association.subnet-id,Values=subnet-xxx"
aws ec2 describe-network-interfacesALB/RDS ENI 실제 부착 위치--filters "Name=group-id,Values=sg-xxx"
aws ec2 authorize-security-group-ingressSG ID를 소스로 포트 허용--group-id $DB_SG --protocol tcp --port 3306 --source-group $WEB_SG
aws logs filter-log-eventsFlow Logs에서 REJECT 필터--filter-pattern "8080 REJECT" (차단 지점 특정)
nc -zvEC2 내부 포트 도달성 테스트nc -zv 10.0.2.100 3306 -w 3 (timed out=방화벽 / refused=앱)
ss -tlnp포트 LISTEN 여부 확인sudo ss -tlnp | grep :8080 (refused 원인)
iptables -L INPUT -n -vOS 방화벽 차단 확인sudo iptables -L INPUT -n | grep 8080
ping -M do -s경로 MTU/PMTUD 블랙홀 확인ping -M do -s 1472 <대상> (큰 패킷만 멈출 때)

다음 모듈에서는 컨테이너 네트워크 디버깅을 다루며, Docker/Kubernetes 환경에서 서비스 간 통신이 끊겼을 때 계층적으로 원인을 좁히는 방법을 살펴봅니다.

보안 그룹을 가상 방화벽으로 설계·운영하는 큰 그림은 가상 서버(EC2)(Cloud Engineering 트랙)에서, 계정 차원의 권한 통제는 계정과 IAM에서 체계적으로 다룹니다.

지식 확인

퀴즈 — 8문제

Q1

AWS Security Group에서 인바운드 규칙으로 TCP 8080을 허용했습니다. 클라이언트가 보낸 요청에 대한 응답 트래픽은 아웃바운드 규칙에서 별도로 허용해야 하나요?

Q2

VPC Flow Logs에서 아래 로그를 발견했습니다. 이 상황의 원인으로 가장 적절한 것은? `2 123456789012 eni-abc12345 10.0.1.50 10.0.2.100 - - 6 1 40 1620000000 1620000060 REJECT OK`

Q3

EC2 A(10.0.1.10)에서 EC2 B(10.0.2.20)의 TCP 3306(MySQL)으로 연결을 시도할 때, NACL을 통한 응답 트래픽이 돌아오려면 무엇이 필요한가요?

Q4

NACL 인바운드에 3306을 허용했는데도 응답이 막힌다. NACL이 stateless라는 점을 고려할 때 추가로 필요한 것은?

Q5

Security Group의 기본 아웃바운드 'allow all' 규칙을 제거하면 어떤 일이 생기는가?

Q6

VPC Flow Log에 REJECT가 보이는데 Security Group에는 해당 포트가 허용돼 있다. 다음으로 확인할 것은?

Q7

[심화] Path MTU Discovery(PMTUD)는 경로 중간에서 패킷이 너무 크면 라우터가 보내는 특정 ICMP 메시지에 의존합니다. NACL이나 SG가 이 ICMP를 막으면 어떤 증상이 나타납니까?

Q8

[심화] 8080을 열었고 Flow Log도 ACCEPT인데, 작은 curl 요청은 200이 오지만 큰 응답이나 SSH의 긴 출력이 특정 지점에서 멈춥니다. 온프레미스와 VPC를 VPN으로 잇는 구간입니다. 원인 확인에 가장 적절한 것은?

0 / 8 답변

🧪 실습으로 확인하기

포트는 열렸다는데 왜 안 되지? — ss/netstat/telnet으로 TCP 진단

초급

"포트 8080 열었는데요?"와 "왜 안 돼요?" 사이의 간극을 메우는 실습. ss로 바인딩 상태를 확인하고, telnet/nc으로 원격 연결을 테스트하고, iptables 방화벽을 진단하고, 바인딩 주소(0.0.0.0 vs 127.0.0.1)까지 수정하는 4단계 TCP 포트 진단 플로우를 완성한다.

35📋 4단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요