EC2 SSH 연결 타임아웃 완전 해결 가이드: Security Group 인바운드 규칙부터 라우팅까지

새로 생성한 EC2 인스턴스에 SSH로 접속하려는데 'Connection timed out' 에러가 뜨는 상황은, AWS를 처음 쓰는 엔지니어뿐 아니라 경험 있는 운영자도 종종 마주친다. 문제는 단순히 Security Group 포트 하나가 아니라, Security Group → Network ACL → 라우팅 테이블 → 인스턴스 상태까지 여러 레이어가 겹쳐 있다는 점이다. 이 글에서는 EC2 SSH 연결 타임아웃의 실제 원인을 레이어별로 진단하고, CLI 기반으로 빠르게 수정하는 방법을 다룬다.

TL;DR — EC2 SSH 연결 타임아웃 빠른 진단표

진단 레이어확인 항목가장 흔한 원인
Security Group (인바운드)TCP 22 허용 여부, 소스 IP포트 22 규칙 누락 또는 소스 0.0.0.0/0 미설정
Network ACL인바운드 22 허용, 아웃바운드 임시 포트 허용아웃바운드 임시 포트(1024-65535) 차단
라우팅 테이블Internet Gateway 연결 여부Public Subnet에 IGW 라우트 없음
퍼블릭 IP / EIP인스턴스에 퍼블릭 IP 할당 여부퍼블릭 IP 없이 직접 접속 시도
인스턴스 상태Status Check 통과 여부System/Instance reachability 실패

EC2 SSH 연결이 어떻게 동작하는가

SSH 연결 요청이 EC2에 도달하기까지 거치는 경로를 이해하지 않으면, 어느 레이어에서 막혔는지 추측에 의존하게 된다. 패킷은 인터넷 → IGW → 서브넷 → Network ACL → Security Group → 인스턴스 순서로 흐른다. 각 레이어는 독립적으로 동작하며, 하나라도 막히면 클라이언트 입장에서는 동일하게 타임아웃으로 보인다.

graph LR Client["클라이언트"] --> IGW["Internet Gateway"] IGW --> RT["라우팅 테이블
0.0.0.0/0 → IGW"] RT --> NACL["Network ACL
Stateless 필터"] NACL --> SG["Security Group
Stateful 필터"] SG --> EC2["EC2 인스턴스
sshd :22"] style NACL fill:#f9a825,color:#000 style SG fill:#1565c0,color:#fff style EC2 fill:#2e7d32,color:#fff
  1. 클라이언트 → IGW: 인터넷에서 들어오는 SSH 패킷이 VPC의 Internet Gateway에 도달한다.
  2. IGW → 서브넷 라우팅: 라우팅 테이블에 0.0.0.0/0 → IGW 경로가 있어야 패킷이 서브넷으로 전달된다.
  3. Network ACL (Stateless): 서브넷 경계에서 인바운드 포트 22와 아웃바운드 임시 포트를 모두 허용해야 한다. Stateless이므로 양방향 규칙이 필요하다.
  4. Security Group (Stateful): 인스턴스 레벨 방화벽. 인바운드 TCP 22를 허용하면 응답 트래픽은 자동 허용된다.
  5. 인스턴스 OS: sshd 프로세스가 실행 중이어야 하며, 키 페어가 일치해야 한다.
Security Group은 Stateful이라 인바운드 허용만으로 응답이 나간다. 반면 Network ACL은 Stateless라서 아웃바운드 임시 포트를 별도로 열지 않으면 응답 패킷이 서브넷 밖으로 나가지 못한다. 이 차이를 모르면 Security Group만 고치고 계속 타임아웃을 보게 된다.

Step 1: Security Group 인바운드 규칙 확인 및 수정

가장 먼저 확인할 곳은 Security Group이다. 신규 인스턴스를 만들 때 기본 Security Group을 그대로 쓰거나, 커스텀 SG를 만들면서 SSH 규칙을 빠뜨리는 경우가 많다. 인스턴스에 연결된 SG ID를 먼저 확인하고, 해당 SG의 인바운드 규칙을 조회한다.

# 인스턴스에 연결된 Security Group ID 확인
aws ec2 describe-instances \
  --instance-ids i-0123456789abcdef0 \
  --query 'Reservations[*].Instances[*].SecurityGroups' \
  --output table

# 해당 SG의 인바운드 규칙 조회
aws ec2 describe-security-groups \
  --group-ids sg-0123456789abcdef0 \
  --query 'SecurityGroups[*].IpPermissions' \
  --output table

TCP 22 포트 규칙이 없거나, 소스가 현재 접속 IP를 포함하지 않으면 아래 명령으로 추가한다. 운영 환경에서는 0.0.0.0/0 대신 실제 접속 IP 대역으로 제한하는 것이 원칙이다.

# TCP 22 인바운드 규칙 추가 (소스를 특정 IP로 제한하는 것을 권장)
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 22 \
  --cidr 203.0.113.0/32

Security Group 규칙을 추가한 뒤 즉시 적용된다. 재시작이나 인스턴스 재연결은 필요 없다.

Step 2: Network ACL 인바운드·아웃바운드 규칙 확인

Security Group을 고쳤는데도 타임아웃이 계속된다면, 서브넷에 연결된 Network ACL을 봐야 한다. 기본 Network ACL은 모든 트래픽을 허용하지만, 커스텀 NACL을 사용하는 경우 인바운드 22번 포트와 아웃바운드 임시 포트(1024-65535)가 모두 열려 있어야 한다. 아웃바운드 임시 포트가 막혀 있으면 SSH 핸드셰이크 응답이 클라이언트에게 돌아가지 못한다.

# 인스턴스가 속한 서브넷 ID 확인
aws ec2 describe-instances \
  --instance-ids i-0123456789abcdef0 \
  --query 'Reservations[*].Instances[*].SubnetId' \
  --output text

# 서브넷에 연결된 Network ACL 확인
aws ec2 describe-network-acls \
  --filters Name=association.subnet-id,Values=subnet-0123456789abcdef0 \
  --query 'NetworkAcls[*].{AclId:NetworkAclId,Entries:Entries}' \
  --output table

출력에서 인바운드 규칙에 포트 22 허용이 있는지, 아웃바운드 규칙에 1024-65535 범위 허용이 있는지 확인한다. 누락된 경우 아래와 같이 추가한다.

# NACL 인바운드 TCP 22 허용 규칙 추가 (rule-number는 기존 규칙과 겹치지 않게 설정)
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0123456789abcdef0 \
  --rule-number 100 \
  --protocol tcp \
  --rule-action allow \
  --ingress \
  --cidr-block 0.0.0.0/0 \
  --port-range From=22,To=22

# NACL 아웃바운드 임시 포트 허용 규칙 추가
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0123456789abcdef0 \
  --rule-number 100 \
  --protocol tcp \
  --rule-action allow \
  --egress \
  --cidr-block 0.0.0.0/0 \
  --port-range From=1024,To=65535

Step 3: 라우팅 테이블과 Internet Gateway 연결 확인

Public Subnet임에도 불구하고 라우팅 테이블에 Internet Gateway 경로가 없으면, 패킷이 VPC 밖으로 나가지 못한다. 서브넷이 퍼블릭으로 의도되었는지 라우팅 테이블로 확인하는 것이 이 단계의 핵심이다.

# 서브넷의 라우팅 테이블 확인
aws ec2 describe-route-tables \
  --filters Name=association.subnet-id,Values=subnet-0123456789abcdef0 \
  --query 'RouteTables[*].Routes' \
  --output table

출력에서 0.0.0.0/0 대상이 igw-xxxxxxxxx로 연결되어 있어야 한다. 없다면 VPC에 Internet Gateway가 연결되어 있는지 먼저 확인하고, 라우트를 추가한다.

# VPC에 연결된 Internet Gateway 확인
aws ec2 describe-internet-gateways \
  --filters Name=attachment.vpc-id,Values=vpc-0123456789abcdef0 \
  --query 'InternetGateways[*].InternetGatewayId' \
  --output text

# 라우팅 테이블에 IGW 경로 추가
aws ec2 create-route \
  --route-table-id rtb-0123456789abcdef0 \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id igw-0123456789abcdef0

Step 4: 퍼블릭 IP 또는 Elastic IP 할당 확인

라우팅이 정상이어도 인스턴스에 퍼블릭 IP가 없으면 인터넷에서 직접 도달할 수 없다. 인스턴스 생성 시 'Auto-assign public IP'를 비활성화했거나, 서브넷 기본 설정이 퍼블릭 IP 자동 할당을 끄고 있는 경우다.

# 인스턴스의 퍼블릭 IP 확인
aws ec2 describe-instances \
  --instance-ids i-0123456789abcdef0 \
  --query 'Reservations[*].Instances[*].{PublicIP:PublicIpAddress,PrivateIP:PrivateIpAddress}' \
  --output table

퍼블릭 IP가 없다면 Elastic IP를 할당하고 인스턴스에 연결하는 것이 가장 빠른 해결책이다. 실행 중인 인스턴스에 바로 적용 가능하다.

# Elastic IP 할당
aws ec2 allocate-address --domain vpc

# 인스턴스에 Elastic IP 연결 (위 명령 출력의 AllocationId 사용)
aws ec2 associate-address \
  --instance-id i-0123456789abcdef0 \
  --allocation-id eipalloc-0123456789abcdef0

Step 5: 인스턴스 Status Check 및 sshd 상태 확인

네트워크 레이어가 모두 정상인데도 타임아웃이 지속된다면, 인스턴스 자체의 상태를 봐야 한다. System reachability 또는 Instance reachability가 실패 상태이면 OS 레벨 문제다. 이 경우 EC2 Serial Console이나 EC2 Instance Connect (지원 인스턴스 타입 한정)를 통해 접근을 시도할 수 있다.

# 인스턴스 Status Check 확인
aws ec2 describe-instance-status \
  --instance-ids i-0123456789abcdef0 \
  --query 'InstanceStatuses[*].{InstanceStatus:InstanceStatus.Status,SystemStatus:SystemStatus.Status}' \
  --output table

두 Status 모두 ok여야 정상이다. impaired가 뜨면 인스턴스를 중지 후 재시작(Stop → Start)하면 다른 물리 호스트로 이동하면서 해결되는 경우가 있다. 재부팅(Reboot)과 달리 Stop → Start는 하이퍼바이저 레벨 재배치를 유발한다.

실제 운영에서 자주 놓치는 함정 — 오진에서 실제 원인까지

Security Group에 포트 22를 열었는데도 타임아웃이 계속되는 상황을 겪은 적이 있다. 처음엔 키 페어 문제라고 생각했다. 타임아웃과 'Permission denied'는 다른 에러인데, 패닉 상태에서는 구분이 흐려진다.

실제 원인은 커스텀 Network ACL의 아웃바운드 규칙이었다. 인바운드 22는 열려 있었지만, 아웃바운드에 임시 포트 범위가 없었다. SSH 핸드셰이크 응답 패킷이 NACL에서 조용히 드롭되고 있었던 것이다. Security Group은 Stateful이라 응답을 자동 허용하지만, NACL은 Stateless라 응답 패킷도 별도 아웃바운드 규칙이 필요하다는 점을 간과했다.

VPC Flow Logs를 켜고 나서야 REJECT 로그가 아웃바운드 방향으로 찍히는 걸 확인했다. Flow Logs 없이는 NACL 드롭을 관찰할 방법이 없다.

# VPC Flow Logs 활성화 (CloudWatch Logs 그룹으로 전송)
aws ec2 create-flow-logs \
  --resource-type VPC \
  --resource-ids vpc-0123456789abcdef0 \
  --traffic-type ALL \
  --log-destination-type cloud-watch-logs \
  --log-group-name /aws/vpc/flowlogs \
  --deliver-logs-permission-arn arn:aws:iam::123456789012:role/FlowLogsRole

NACL 드롭은 Security Group 로그에 흔적이 없다. Flow Logs만이 유일한 관찰 수단이다.

EC2 SSH 연결 타임아웃 전체 진단 흐름

graph TD A["SSH 연결 시도"] --> B{"타임아웃 발생?"}; B -- Yes --> C{"SG에 TCP 22
인바운드 있음?"}; C -- No --> C1["SG 인바운드 규칙 추가"]; C -- Yes --> D{"NACL 인바운드 22
아웃바운드 임시포트 허용?"}; D -- No --> D1["NACL 규칙 추가"]; D -- Yes --> E{"라우팅 테이블에
IGW 경로 있음?"}; E -- No --> E1["IGW 라우트 추가"]; E -- Yes --> F{"퍼블릭 IP
할당됨?"}; F -- No --> F1["EIP 할당 및 연결"]; F -- Yes --> G{"Status Check
정상?"}; G -- No --> G1["인스턴스 Stop/Start"]; G -- Yes --> H["연결 성공"]; C1 --> H; D1 --> H; E1 --> H; F1 --> H; G1 --> H; style H fill:#2e7d32,color:#fff style C1 fill:#1565c0,color:#fff style D1 fill:#1565c0,color:#fff style E1 fill:#1565c0,color:#fff style F1 fill:#1565c0,color:#fff style G1 fill:#1565c0,color:#fff
  1. SSH 연결 시도: 클라이언트에서 ssh 명령 실행.
  2. 타임아웃 발생: 응답 없이 연결 실패.
  3. Security Group 확인: TCP 22 인바운드 규칙 존재 여부 및 소스 IP 범위 검증.
  4. Network ACL 확인: 인바운드 22, 아웃바운드 임시 포트 양방향 규칙 검증.
  5. 라우팅 테이블 확인: IGW 경로 존재 여부 확인.
  6. 퍼블릭 IP 확인: 인스턴스에 퍼블릭 IP 또는 EIP 할당 여부 확인.
  7. 인스턴스 Status Check: System/Instance reachability 상태 확인.
  8. 연결 성공: 모든 레이어 통과 후 SSH 세션 수립.

최소 권한 원칙 기반 IAM 정책 예시

Security Group 규칙을 CLI로 수정하려면 해당 작업에 필요한 IAM 권한이 있어야 한다. 아래는 Security Group 인바운드 규칙 추가와 인스턴스 상태 조회에 필요한 최소 권한 정책이다.

🔽 IAM 정책 예시 펼치기
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:DescribeSecurityGroups",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:DescribeNetworkAcls",
        "ec2:CreateNetworkAclEntry",
        "ec2:DescribeRouteTables",
        "ec2:CreateRoute",
        "ec2:DescribeInternetGateways",
        "ec2:DescribeInstanceStatus",
        "ec2:AllocateAddress",
        "ec2:AssociateAddress"
      ],
      "Resource": "*"
    }
  ]
}

Describe 계열 작업은 리소스 레벨 제한을 지원하지 않아 Resource: *가 필요하다. 수정 작업(Authorize, Create)은 특정 리소스 ARN으로 제한 가능하나, EC2 리소스 레벨 권한 지원 여부는 Service Authorization Reference에서 확인해야 한다.

EC2 SSH 연결 타임아웃 해결 마무리 및 다음 단계

EC2 SSH 연결 타임아웃은 단일 원인이 아니라 네트워크 스택 전체를 순서대로 점검해야 해결된다. Security Group 인바운드 규칙이 가장 흔한 원인이지만, Network ACL 아웃바운드 임시 포트, 라우팅 테이블 IGW 경로 누락, 퍼블릭 IP 미할당도 동일한 증상을 만든다. VPC Flow Logs를 상시 활성화해두면 어느 레이어에서 드롭이 발생하는지 즉시 확인할 수 있다.

다음으로 확인할 것들:

핵심 용어 정리

용어설명
Security GroupEC2 인스턴스 레벨의 가상 방화벽. Stateful로 동작하여 허용된 인바운드 트래픽의 응답은 자동 허용된다.
Network ACL (NACL)서브넷 레벨의 트래픽 필터. Stateless로 동작하여 인바운드와 아웃바운드 규칙을 각각 설정해야 한다.
Internet Gateway (IGW)VPC와 인터넷 간 트래픽을 라우팅하는 VPC 구성 요소. Public Subnet에서 인터넷 접근에 필수다.
임시 포트 (Ephemeral Port)클라이언트가 서버 응답을 받기 위해 OS가 임시로 사용하는 포트 범위. Linux 기준 일반적으로 32768-60999이나, NACL 설정 시 1024-65535를 허용하는 것이 일반적이다.
VPC Flow LogsVPC 내 네트워크 인터페이스를 통과하는 IP 트래픽 정보를 캡처하는 기능. 허용/거부 여부를 포함한다.

댓글

이 블로그의 인기 게시물

EC2 SSH 연결 시간 초과: 확인해야 할 보안 그룹(Security Group) 규칙

IAM User vs IAM Role 차이점 완전 정리 — EC2에서 S3 접근 시 무엇을 써야 하는가