infra
Platform

모듈 맵

[Network] 3-Way Handshake 원리와 신뢰성 높은 포트 체계

0 / 35 완료

펼치기
0 / 35 완료0%

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

[Network] 3-Way Handshake 원리와 신뢰성 높은 포트 체계

TCP 3-Way Handshake, UDP 특성, Well-known Port를 이해하고 nc로 통신을 테스트합니다

🚨INCIDENT ALERT
HIGH

서비스 포트를 열었다고 했지만 클라이언트는 여전히 연결하지 못합니다. TCP와 UDP의 동작 차이, 포트 번호의 의미를 모르면 같은 "포트"라는 단어 아래 다른 문제를 섞게 됩니다.

포트는 애플리케이션으로 들어가는 문입니다. 어떤 프로토콜의 어떤 문인지 먼저 구분해야 합니다.

TCP vs UDP와 포트 체계

네트워크에서 두 컴퓨터가 데이터를 주고받을 때, 단순히 IP 주소만으로는 어떤 애플리케이션이 받을지 알 수 없습니다. **포트(Port)**와 **전송 프로토콜(TCP/UDP)**이 이 문제를 해결합니다. 이 챕터에서는 TCP의 신뢰성 메커니즘인 3-Way Handshake, UDP의 빠른 통신 원리, 그리고 Well-known Port 체계를 이해하고 nc(netcat)으로 직접 통신을 실습합니다.


이번 챕터에서 배울 것
  • 1TCP 3-Way Handshake와 4-Way Teardown의 연결 수립·종료 과정을 설명할 수 있다
  • 2TCP의 신뢰성 메커니즘인 순서 번호, ACK, 재전송, 흐름 제어를 이해할 수 있다
  • 3UDP의 특성을 이해하고 TCP 대비 장단점을 비교할 수 있다
  • 4DNS·NTP·스트리밍 등 UDP가 적합한 서비스 유형을 판단할 수 있다
  • 5Well-known Port 체계와 주요 서비스 포트 번호를 구분할 수 있다
  • 6nc(netcat)로 TCP/UDP 포트 통신을 직접 실습할 수 있다
실습 환경 준비
netcat 설치 확인
nc -h 2>&1 | head -5
로컬 포트 LISTEN 상태 확인
ss -tlnp
nc로 임시 TCP 서버 열기 (터미널 A)
nc -l 9000

터미널 B에서 'nc localhost 9000'으로 접속하면 양방향 통신 실습 가능

💡개념

TCP — 신뢰성을 보장하는 연결 지향 프로토콜

마이크로서비스를 배포했는데 클러스터 내부에서는 헬스체크가 통과하지만 외부 모니터링은 타임아웃을 보고합니다. 방화벽도, 포트도, 프로세스도 문제없습니다. ss -tan 출력을 보면 SYN_RECV 상태로 쌓인 연결들이 보이는데, 이 상태가 무엇을 뜻하는지 해석조차 못 합니다. TCP의 연결 수립 과정을 이해하지 못하면 이 출력이 아무 의미 없는 숫자로만 보입니다.

TCP — 신뢰성을 보장하는 연결 지향 프로토콜확대

TCP란 무엇인가

**TCP(Transmission Control Protocol)**는 데이터를 순서대로, 빠짐없이, 오류 없이 전달하도록 설계된 전송 계층 프로토콜입니다. 이를 위해 연결 수립, 흐름 제어, 오류 감지 및 재전송 메커니즘을 갖추고 있습니다.

3-Way Handshake — 연결 수립

TCP 통신을 시작하기 전, 반드시 3단계 핸드셰이크로 연결을 수립합니다.

TCP 3-Way Handshake — 클라이언트와 서버가 SYN→SYN-ACK→ACK로 순서번호를 교환하고 연결을 확립하는 과정확대

각 단계의 의미:

  1. SYN: 클라이언트가 연결을 요청하며 초기 시퀀스 번호(ISN)를 전달
  2. SYN-ACK: 서버가 요청을 수락하고, 자신의 ISN을 전달하며 클라이언트의 SYN을 ACK
  3. ACK: 클라이언트가 서버의 SYN을 ACK하며 연결 수립 완료

연결 종료 — 4-Way Teardown

TCP 연결 종료는 4단계를 거칩니다:

TCP 연결 종료 4-Way Handshake — 양측이 FIN을 보내고 ACK로 확인해 정상 종료확대

TCP의 신뢰성 메커니즘

TCP가 "빠짐없이, 순서대로" 데이터를 보장하는 것은 여러 메커니즘이 조합된 결과입니다. 각 기능이 담당하는 역할을 정리하면 TCP가 왜 느리지만 신뢰할 수 있는지 이해됩니다.

기능설명
순서 번호(Sequence Number)패킷에 번호를 붙여 순서 보장
확인 응답(ACK)받은 패킷을 확인하고, 미수신 시 재전송 요청
재전송일정 시간 내 ACK 없으면 자동 재전송
흐름 제어(Flow Control)수신 버퍼 크기(Window Size)에 맞춰 전송 속도 조절
혼잡 제어(Congestion Control)네트워크 혼잡 시 전송 속도 자동 감소

TCP를 사용하는 주요 프로토콜

HTTP/HTTPS  — TCP 80/443   (웹)
SSH         — TCP 22        (원격 접속)
FTP         — TCP 20/21     (파일 전송)
SMTP        — TCP 25/587    (이메일 발송)
MySQL       — TCP 3306      (데이터베이스)
PostgreSQL  — TCP 5432      (데이터베이스)
Redis       — TCP 6379      (캐시)

💡개념

TCP 연결이 맺어지고 데이터가 오가기까지 — 수립부터 종료까지 5단계

nc example.com 80 한 줄, 또는 브라우저가 서버에 붙는 그 짧은 순간에 두 호스트 사이에서는 연결 수립(3-way) → 소켓 식별 → 데이터 전송 → 연결 종료(4-way) 가 순서대로 일어납니다. 이 흐름을 알면 "왜 refused지", "왜 timeout이지", "왜 reset이 뜨지"를 서로 다른 단계의 서로 다른 증상으로 구분할 수 있습니다. 같은 "연결 안 됨"도 어느 단계에서 끊겼는지에 따라 원인이 완전히 다릅니다.

TEXT
[클라이언트]  connect(example.com:80)   ← 출발지는 임시 포트(ephemeral) 자동 할당
   │
   ① SYN 전송           "연결하자" + 내 초기 시퀀스(ISN)
   │
   ② SYN-ACK 수신        서버 LISTEN 소켓이 수락 + 서버 ISN + 내 SYN 확인
   │
   ③ ACK 전송 → ESTABLISHED
   │      커널이 {출발IP·출발포트·목적IP·목적포트} 4-tuple로 이 연결을 식별
   │
   ④ 데이터 송수신        시퀀스 번호로 순서 보장 · ACK 없으면 재전송 · 윈도우로 흐름제어
   │
   ⑤ 4-way 종료          FIN ↔ ACK 를 양방향 각각 → 능동 종료측은 TIME_WAIT 로 잔류
   ▼
[연결 종료]   (UDP는 ①②③⑤가 전부 없음 — 그냥 목적지로 던진다)

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

단계하는 일여기서 막히면
① SYN 전송클라이언트가 목적지 IP·포트로 연결 요청. 커널이 출발지 임시 포트를 할당방화벽이 SYN을 DROP → SYN-ACK가 안 옴 → 커널이 SYN 재전송하다 Connection timed out(응답 없는 침묵)
② SYN-ACK 수신그 포트를 LISTEN 중인 소켓이 수락하고 자기 시퀀스를 실어 응답그 포트에 LISTEN 소켓이 없으면 커널이 즉시 RST 회신 → Connection refused(빠른 거절)
③ ACK·ESTABLISHED3-way 완료. 커널이 4-tuple로 소켓을 식별해 이후 패킷을 이 연결에 배달accept 큐가 꽉 차면 완성된 연결을 커널이 드랍 → 간헐적 접속 실패
④ 데이터 송수신시퀀스 번호로 순서를, ACK로 도착을 확인. 누락분은 재전송, 윈도우로 속도 조절이미 맺힌 연결을 상대가 강제로 끊거나 크래시 → RST 수신 → Connection reset by peer
⑤ 4-way 종료양쪽이 각자 방향의 FIN을 보내고 ACK로 확인. 능동 종료측은 2×MSL 동안 TIME_WAIT앱이 close()를 호출 안 하면 내 쪽 FIN이 안 나가 CLOSE_WAIT로 잔류(소켓 누수)

연결 실패는 어느 단계에서 끊겼는지가 곧 원인입니다 — Connection refused(빠름)는 목적지까지 도달했으나 그 포트에 LISTEN이 없다는 뜻(②, 서비스 안 뜸/포트 오타)이고, Connection timed out(느림·침묵)은 SYN이 응답 없이 사라졌다는 뜻(①, 방화벽 DROP/잘못된 IP)이며, Connection reset은 이미 맺힌 연결을 상대가 끊었다는 뜻(④, 서버 크래시·강제 종료)입니다. UDP는 ①②③⑤ 단계 자체가 없어 핸드셰이크 왕복이 없는 대신, 리스너가 없어도 즉시 알 수 없고(원래 응답이 없으니) ICMP port unreachable에 기대야 합니다. nc -zv(TCP)와 nc -zuv(UDP)의 결과가 다르게 읽히는 이유가 여기 있습니다.


💡개념

UDP와 Well-known Port 체계

DNS 쿼리가 간헐적으로 실패합니다. 서버는 살아있고 포트 53도 열려 있는데 왜 안 되는지 이해가 안 됩니다. 원인은 UDP 패킷 유실이었습니다. TCP라면 자동으로 재전송했겠지만 DNS는 UDP를 씁니다. UDP가 왜 신뢰성을 포기하는지, 어떤 서비스가 UDP를 쓰는지 알면 이런 간헐적 실패의 원인을 훨씬 빠르게 좁힐 수 있습니다.

UDP와 Well-known Port 체계 — UDP는 핸드셰이크·재전송 없이 빠르지만 패킷 유실을 복구 안 함(DNS 53·DHCP·NTP·실시간 스트리밍에 사용). Well-known 포트(0-1023)는 22 SSH·53 DNS·80 HTTP·443 HTTPS처럼 표준 할당. DNS 간헐 실패는 UDP 유실이 흔한 원인확대

UDP — 속도 우선, 신뢰성 포기

**UDP(User Datagram Protocol)**는 TCP의 연결 수립, 순서 보장, 재전송 메커니즘을 모두 제거하고 최소한의 기능만 가진 프로토콜입니다.

UDP 통신 — 핸드셰이크 없이 데이터를 일방적으로 전송하며 순서·재전송을 보장하지 않음확대

TCP vs UDP 비교

어떤 상황에서 TCP를 쓰고 어떤 상황에서 UDP를 쓸지는 각 프로토콜의 특성 차이에서 결정됩니다.

특성TCPUDP
연결 수립필요 (3-Way Handshake)불필요
순서 보장보장미보장
재전송자동 재전송없음
오류 검사헤더 + 데이터선택적 체크섬
속도상대적으로 느림빠름
오버헤드높음 (헤더 20~60바이트)낮음 (헤더 8바이트)
적합한 용도파일 전송, 웹, DBDNS, 스트리밍, 게임

UDP가 적합한 서비스

DNS (UDP 53)

클라이언트 → DNS 서버: "google.com이 뭐야?" (1패킷)
DNS 서버 → 클라이언트: "142.250.196.110" (1패킷)

요청과 응답이 각 1패킷으로 끝나는 단순한 질의응답 구조에서 TCP 연결 수립(3-Way Handshake)은 불필요한 오버헤드입니다. DNS는 UDP 53을 사용합니다.

NTP (UDP 123)

시간 동기화는 단발성 요청/응답이며, 일부 패킷 손실 시 재전송보다 다시 질의하는 것이 빠릅니다.

실시간 영상/음성 (RTP, UDP 기반)

1초에 30프레임을 전송하는 영상에서 한 프레임이 손실되어도 다음 프레임을 보여주는 것이 훨씬 자연스럽습니다. TCP 재전송으로 인한 지연은 화상통화를 불가능하게 만들 수 있습니다.

온라인 게임

캐릭터 위치 정보는 100ms마다 갱신됩니다. 오래된 위치 데이터를 재전송받는 것보다 최신 데이터를 새로 받는 것이 게임 경험에 유리합니다.

Well-known Port 체계

포트 번호는 0~65535 범위이며 세 구간으로 나뉩니다:

범위이름특징
0 ~ 1023Well-known Ports표준 서비스, root 권한 필요
1024 ~ 49151Registered Ports등록된 애플리케이션
49152 ~ 65535Dynamic/Ephemeral클라이언트 임시 포트

필수 암기 Well-known Ports

포트프로토콜서비스
20, 21TCPFTP (데이터, 제어)
22TCPSSH (보안 원격 접속)
23TCPTelnet (비암호화 원격)
25TCPSMTP (이메일 발송)
53UDP (+ TCP)DNS
67, 68UDPDHCP
80TCPHTTP
110TCPPOP3 (이메일 수신)
123UDPNTP (시간 동기화)
143TCPIMAP (이메일 수신)
443TCPHTTPS
587TCPSMTP Submission (이메일 발송, TLS)
3306TCPMySQL
5432TCPPostgreSQL
6379TCPRedis
8080TCPHTTP 대체 (개발/프록시)
27017TCPMongoDB

Ephemeral Port

클라이언트가 서버에 연결할 때, 운영체제가 자동으로 49152~65535 범위에서 임시 포트를 할당합니다.

소켓은 IP:PORT 쌍으로 연결을 식별 — 클라이언트는 임시 포트(ephemeral), 서버는 고정 서비스 포트 사용확대

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

# 현재 클라이언트 포트 범위 확인
$ cat /proc/sys/net/ipv4/ip_local_port_range
32768   60999
🔍실행 후 확인할 것
  • State 컬럼 — LISTEN 여부ss -tlnp 출력에서 State가 LISTEN인 줄만 서비스가 실제로 연결을 받을 수 있는 포트 — CLOSE-WAIT나 TIME-WAIT는 연결 종료 중인 소켓으로 새 요청을 받지 않음
  • Local Address:Port — 바인딩 주소 확인0.0.0.0:80은 모든 인터페이스에서 수신, 127.0.0.1:8080은 로컬호스트에서만 수신 — 외부 접근이 안 되면 127.0.0.1 바인딩인지 먼저 확인
  • Process 컬럼 — 소유 프로세스 확인users:(("nginx",pid=1234,fd=6)) 형태로 포트를 점유 중인 프로세스명과 PID를 확인 — 예상과 다른 프로세스가 포트를 점유하면 충돌 상태

nc로 TCP 서버 열고 통신 테스트하기

목표

nc(netcat)로 간단한 TCP 서버를 열고 클라이언트로 접속하여, 실제 포트 통신이 어떻게 이루어지는지 체험합니다.

전제 조건

로컬 터미널
# nc 설치 확인
$ which nc
# 없으면:
$ sudo apt install netcat-openbsd    # Ubuntu/Debian
$ sudo yum install nmap-ncat         # CentOS/RHEL

단계별 실습

1단계: TCP 서버 열기 (터미널 1)

로컬 터미널
# 8080 포트에서 연결 대기
$ nc -l 8080

# 또는 다음 옵션과 함께:
$ nc -l -p 8080 -v
# Listening on 0.0.0.0 8080

이 명령어를 실행하면 터미널이 대기 상태로 멈춥니다. 연결이 들어오기를 기다리는 중입니다.

2단계: 클라이언트로 접속 (터미널 2)

새 터미널을 열어 접속합니다:

로컬 터미널
# 같은 서버에서 접속 (loopback)
$ nc 127.0.0.1 8080

# 또는 서버 IP로 원격 접속
$ nc 10.0.0.5 8080

3단계: 양방향 메시지 전송

터미널 2에서 입력하면 터미널 1에서 수신됩니다:

OUTPUT
[터미널 2 - 클라이언트]              [터미널 1 - 서버]
$ nc 127.0.0.1 8080
Hello, Server!   --(전송)-->        Hello, Server!     (수신)
Hi, Client!      <--(수신)--        Hi, Client!        (입력)

4단계: 파일 전송 테스트

로컬 터미널
# 서버: 파일 수신 준비
$ nc -l 9000 > received_file.txt

# 클라이언트: 파일 전송
$ nc 127.0.0.1 9000 < /etc/hostname
# 전송 완료 후 Ctrl+D로 종료

# 서버 측에서 수신 확인
$ cat received_file.txt

5단계: 연결 종료

  • 클라이언트: Ctrl+D (EOF 전송) 또는 Ctrl+C
  • 서버: 클라이언트 연결 종료 후 자동 종료, 또는 Ctrl+C

확인 포인트

로컬 터미널
# 연결 중 상태 확인 (다른 터미널에서)
$ ss -tnp | grep 8080
# LISTEN  0  1  0.0.0.0:8080  0.0.0.0:*      서버 대기 중
# ESTAB   0  0  127.0.0.1:8080  127.0.0.1:54321   연결 수립됨

nc로 포트 개방 여부 확인하기

목표

nc를 클라이언트 모드로 사용하여 원격 서버의 특정 포트가 열려 있는지 확인합니다. 방화벽 정책 확인이나 서비스 응답 테스트에 활용됩니다.

단계별 실습

1단계: TCP 포트 개방 확인

로컬 터미널
# -z: 데이터 전송 없이 연결 시도만
# -v: 자세한 출력
# -w 3: 3초 타임아웃

# 포트가 열려 있는 경우
$ nc -zv google.com 443
# Connection to google.com 443 port [tcp/https] succeeded!

# 포트가 닫혀 있는 경우
$ nc -zv google.com 8888
# nc: connect to google.com port 8888 (tcp) failed: Connection refused

2단계: UDP 포트 확인

로컬 터미널
# -u: UDP 모드
$ nc -zvu 8.8.8.8 53
# Connection to 8.8.8.8 53 port [udp/domain] succeeded!

3단계: 여러 포트 범위 스캔

로컬 터미널
# 20~25 포트 순차 확인
$ nc -zv 192.168.1.1 20-25

# 출력:
# nc: connect to 192.168.1.1 port 20 failed: Connection refused
# nc: connect to 192.168.1.1 port 21 failed: Connection refused
# Connection to 192.168.1.1 port 22 (tcp) succeeded!  ← SSH 열림

4단계: 서비스 배너 확인

로컬 터미널
# SSH 서버 배너 확인 (연결 후 즉시 출력)
$ nc 10.0.0.5 22
# SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.6

# HTTP 서버 직접 요청
$ echo -e "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n" | nc example.com 80
# HTTP/1.0 200 OK
# Content-Type: text/html
# ...

5단계: 방화벽 정책 테스트 시나리오

로컬 터미널
# 방화벽 규칙 추가 전 테스트
$ nc -zv app-server 3306
# nc: connect to app-server port 3306 failed: Connection refused

# 방화벽 규칙 추가 후 재테스트
$ nc -zv app-server 3306
# Connection to app-server port 3306 (tcp) succeeded!

nc vs telnet vs curl 비교

도구강점약점
ncTCP/UDP 모두 지원, 스크립팅 용이TLS 미지원
telnet단순, 배너 확인 용이UDP 미지원, 구식
curlHTTPS 지원, 다양한 프로토콜단순 포트 체크엔 과함

방화벽 ACL 요청서 작성 실습

목표

실제 운영 환경에서 방화벽 ACL(Access Control List) 정책을 요청할 때 필요한 정보를 올바르게 명시하는 방법을 익힙니다.

시나리오

애플리케이션 서버(10.0.1.20)가 다음 서비스에 접근해야 합니다:

  1. 데이터베이스 서버(10.0.2.10)의 MySQL(3306/TCP)
  2. 외부 DNS 서버(8.8.8.8)의 DNS(53/UDP)
  3. 외부 HTTPS API(api.partner.com의 443/TCP)

올바른 ACL 요청서 작성

잘못된 요청 (흔한 실수)

"10.0.1.20에서 10.0.2.10 포트 3306 허용해 주세요"
→ TCP인지 UDP인지 명시 안 됨!
→ 방화벽 담당자가 TCP만 추가하면 이후 문제 발생 가능

올바른 요청

방화벽 ACL 요청

출발지: 10.0.1.20/32
목적지: 10.0.2.10/32
포트:   3306
프로토콜: TCP
방향:   Outbound (10.0.1.20 → 10.0.2.10)
목적:   애플리케이션 서버 → MySQL DB 접근

---

출발지: 10.0.1.20/32
목적지: 8.8.8.8/32
포트:   53
프로토콜: UDP       ← DNS는 반드시 UDP!
방향:   Outbound
목적:   DNS 질의

---

출발지: 10.0.1.20/32
목적지: ANY (api.partner.com 해당 IP 대역)
포트:   443
프로토콜: TCP
방향:   Outbound
목적:   파트너 API 연동

프로토콜 확인 방법

로컬 터미널
# 서비스별 기본 프로토콜 확인
$ grep mysql /etc/services
# mysql  3306/tcp
# mysql  3306/udp (등록되어 있지만 실제로는 TCP만 사용)

$ grep domain /etc/services
# domain  53/tcp
# domain  53/udp   ← UDP가 기본

$ grep https /etc/services
# https  443/tcp

열린 포트와 프로토콜 확인

로컬 터미널
# 서버에서 열린 포트 확인 (TCP)
$ ss -tlnp
# State   Recv-Q  Send-Q  Local Address:Port
# LISTEN  0       128     0.0.0.0:22        TCP 22 (SSH)
# LISTEN  0       80      0.0.0.0:3306      TCP 3306 (MySQL)

# UDP 포트 확인
$ ss -ulnp
# State   Recv-Q  Send-Q  Local Address:Port
# UNCONN  0       0       0.0.0.0:53        UDP 53 (DNS)
# UNCONN  0       0       0.0.0.0:123       UDP 123 (NTP)

증상

로컬 터미널
# DNS 질의가 타임아웃
$ dig @10.0.0.53 myapp.internal.com
# ;; connection timed out; no servers could be reached

# 또는 특정 도구에서만 DNS 실패
# curl은 되는데 dig는 안 됨 (또는 반대)

원인: TCP 53을 사용했거나 방화벽에서 TCP 53만 허용

방화벽 ACL에 TCP 53만 허용되고 UDP 53이 차단된 경우, 또는 dignslookup 옵션에 TCP가 강제 지정된 경우 발생합니다.

로컬 터미널
# 잘못된 ACL 요청의 예:
출발지: 10.0.0.0/24
목적지: 10.0.0.53/32
포트: 53
프로토콜: TCP   ← UDP가 빠짐!

진단 방법

로컬 터미널
# UDP 53으로 직접 테스트
$ dig @10.0.0.53 myapp.internal.com
# 타임아웃

# TCP 53으로 테스트
$ dig @10.0.0.53 myapp.internal.com +tcp
# 응답 옴!
# → 방화벽에서 UDP 53만 차단됨, 반대로 TCP는 열림

# UDP로 nc 테스트
$ nc -zuv 10.0.0.53 53
# nc: connect to 10.0.0.53 port 53 (udp) failed: Connection refused
# → UDP 53 차단 확인

해결 방법

방화벽 ACL 수정

기존:
출발지: 10.0.0.0/24 → 10.0.0.53:53/TCP ALLOW

추가 필요:
출발지: 10.0.0.0/24 → 10.0.0.53:53/UDP ALLOW

임시 우회 (TCP 강제)

방화벽 수정 전 임시로 TCP DNS를 사용:

로컬 터미널
# dig에서 TCP 강제 지정
$ dig @10.0.0.53 myapp.internal.com +tcp

# /etc/resolv.conf에 options 추가
$ echo "options use-vc" | sudo tee -a /etc/resolv.conf
# use-vc: 모든 DNS 질의를 TCP로 전송

DNS에서 TCP를 사용하는 정당한 경우

상황이유
Zone Transfer (AXFR)대용량 Zone 데이터 전송
응답 크기 > 512바이트UDP 한계 초과 시 TCP로 재시도
DNSSEC 응답서명 데이터로 응답 크기 큼
DNS over TLS (DoT)TCP 853 사용
로컬 터미널
# Zone Transfer 테스트 (TCP 53 사용)
$ dig @ns1.example.com example.com AXFR

예방 방법

방화벽 ACL 요청 시 DNS 관련 규칙은 반드시 TCP/UDP 양쪽을 명시합니다:

포트 53, 프로토콜: TCP AND UDP 모두 허용

증상

로컬 터미널
# 서버에서 로컬 확인
$ nc -zv 127.0.0.1 8080
# Connection to 127.0.0.1 8080 port [tcp] succeeded!

# 외부에서 접속 시도 (다른 서버에서)
$ nc -zv 10.0.0.5 8080
# nc: connect to 10.0.0.5 port 8080 (tcp) failed: Connection refused

원인 1: 서비스가 Localhost에만 바인딩

로컬 터미널
# 바인딩 주소 확인
$ ss -tlnp | grep 8080
# LISTEN 0 128 127.0.0.1:8080 ...   ← 127.0.0.1만 바인딩!
#                ↑
#          외부 접속 불가

서비스 설정에서 0.0.0.0:8080 대신 127.0.0.1:8080으로 바인딩되어 있습니다.

해결: 서비스 설정에서 바인딩 주소를 0.0.0.0 또는 서버의 실제 IP로 변경:

로컬 터미널
# 예: Python HTTP 서버
$ python3 -m http.server 8080 --bind 0.0.0.0

# 예: 애플리케이션 설정 파일
# server.host=0.0.0.0  (또는 server.bind-address=0.0.0.0)

원인 2: 방화벽(firewalld/iptables)에서 포트 차단

위험 명령어

이 명령은 방화벽 정책을 변경해 현재 접속 중인 세션이나 운영 트래픽에 즉시 영향을 줄 수 있습니다. 적용 전 허용 대상 IP·포트와 롤백 명령을 확인하세요.

로컬 터미널
# firewalld 확인 (RHEL/CentOS)
$ sudo firewall-cmd --list-ports
# 22/tcp 80/tcp 443/tcp   ← 8080 없음!

# 포트 추가
$ sudo firewall-cmd --add-port=8080/tcp --permanent
$ sudo firewall-cmd --reload

# iptables 확인 (직접 확인 필요 시)
$ sudo iptables -L INPUT -n | grep 8080

원인 3: 클라우드 보안 그룹 미설정

AWS EC2, GCP VM 등에서는 OS 방화벽 외에 **보안 그룹(Security Group)**에서도 포트를 허용해야 합니다.

# AWS 콘솔 → EC2 → 보안 그룹 → 인바운드 규칙 추가
유형: 사용자 지정 TCP
포트 범위: 8080
소스: 0.0.0.0/0 (또는 특정 IP)

진단 순서

외부 접속이 실패하면 다음 순서로 좁혀갑니다.

  1. 서버에서 ss -tlnp | grep [포트]
    • 0.0.0.0:[포트] → 바인딩 OK → 방화벽 확인으로
    • 127.0.0.1:[포트] → 바인딩 문제 → 서비스 설정 변경
  2. OS 방화벽 확인firewall-cmd --list-ports, iptables -L INPUT -n
  3. 클라우드 보안 그룹 확인 (AWS/GCP/Azure)

심화 — 핸드셰이크는 되는데 큰 데이터만 멈출 때 (MSS와 MTU)

💡개념

심화: SYN에서 합의되는 MSS와 경로 MTU 블랙홀

3-way 핸드셰이크는 연결만 맺는 게 아니라, 그 안에서 한 세그먼트에 실을 수 있는 최대 크기(MSS) 를 서로 알려줍니다. 이 값과 경로 중간의 MTU가 어긋나면, 연결은 멀쩡히 맺히는데 '큰 데이터만' 멈추는 기묘한 장애가 납니다.

  • MSS는 SYN에서 광고됩니다: TCP는 SYN·SYN-ACK에 MSS 옵션을 실어 '나는 한 세그먼트에 최대 이만큼 받겠다'를 알립니다. 보통 MSS = MTU − 40(IPv4에서 IP 20 + TCP 20 헤더). 이더넷 MTU 1500이면 MSS는 대개 1460입니다.
  • 경로 MTU가 더 작을 수 있습니다: 중간에 터널(VPN·IPsec·GRE)이나 PPPoE가 끼면 실제 경로 MTU가 1500보다 작아집니다(예: 1400). 송신 측이 1500짜리 패킷에 DF(Don't Fragment) 비트를 달아 보내면, 그 구간 라우터는 패킷을 못 넘기고 ICMP 'fragmentation needed'(Type 3 Code 4) 로 "MTU를 줄여라"를 알립니다. 이게 정상 PMTUD(경로 MTU 발견) 동작입니다.
  • 블랙홀은 그 ICMP를 누군가 막을 때 생깁니다: 방화벽이 ICMP를 통째로 DROP하면 송신 측은 "줄여라" 신호를 못 받고 큰 패킷을 계속 재전송합니다. 핸드셰이크(작은 패킷)와 짧은 요청은 통과하지만, 큰 응답·업로드는 영영 도착하지 못하고 멈춥니다 — 이게 PMTUD 블랙홀이며 "연결은 되는데 큰 전송만 죽는다"의 전형입니다.
  • 그래서 나오는 대응: 경계 라우터·방화벽에서 MSS clamping(TCPMSS --clamp-mss-to-pmtu)으로 SYN의 MSS를 경로 MTU에 맞춰 낮춰 주거나, ICMP Type 3 Code 4를 허용합니다. ping -M do -s로 DF를 걸고 크기를 키워 가며 어디서 막히는지 경로 MTU를 실측할 수 있습니다.

상황: 새로 붙인 사내망(또는 VPN) 너머 서버로 nc·curl 연결은 성공하고 짧은 API 응답도 잘 옵니다. ping도 정상입니다. 그런데 큰 파일 업로드나 대용량 목록 응답만 중간에 멈춰 타임아웃됩니다. 방화벽 포트는 열려 있습니다.

원인: 경로 중간에 낀 터널(VPN/IPsec)로 실제 경로 MTU가 1500보다 작은데, 그 구간에서 돌아와야 할 ICMP 'fragmentation needed'가 방화벽에 DROP돼 송신 측이 패킷 크기를 못 줄이는 PMTUD 블랙홀입니다. 작은 패킷(핸드셰이크·짧은 요청)은 MTU 안이라 통과하고, 큰 세그먼트만 소리 없이 사라집니다.

진단: ping -M do -s 1472 대상에서 시작해 크기를 낮춰 가며(1472는 IP 20 + ICMP 8 헤더 포함 1500) 어느 크기에서 'Frag needed and DF set' 또는 무응답이 되는지로 경로 MTU를 실측합니다. tcpdump로 큰 세그먼트가 재전송만 반복되는지, ICMP Type 3 Code 4가 돌아오는지 봅니다. 터널 인터페이스의 MTU(ip link)도 확인합니다.

해결: 경계 방화벽에서 ICMP 'fragmentation needed'(Type 3 Code 4)를 허용하는 게 근본입니다. 즉시 완화는 라우터/방화벽의 MSS clampingiptables ... -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu로 SYN의 MSS를 경로 MTU에 맞춰 낮춰, 애초에 큰 세그먼트가 생기지 않게 합니다. 터널 구간이면 인터페이스 MTU를 실제 값(예: 1400)으로 맞추는 것도 방법입니다.


💼
실무 맥락
현업 패턴

마이크로서비스 환경의 포트 관리

마이크로서비스 아키텍처에서는 서비스마다 포트를 할당하고 관리해야 합니다.

YAML
# docker-compose.yml에서 포트 매핑
services:
  auth-service:
    ports:
      - "3001:3001"   # HOST:CONTAINER TCP 기본

  catalog-service:
    ports:
      - "3002:3002"

  # 데이터베이스는 외부 노출 최소화
  postgres:
    ports:
      - "127.0.0.1:5432:5432"  # localhost에서만 접근 가능

포트 충돌 시 진단:

로컬 터미널
# 어떤 프로세스가 포트 사용 중인지 확인
$ ss -tlnp | grep :3001
$ lsof -i :3001

Kubernetes NetworkPolicy 작성 시

쿠버네티스 NetworkPolicy에서 포트와 프로토콜 명시:

YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-mysql
spec:
  podSelector:
    matchLabels:
      app: web
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: mysql
      ports:
        - protocol: TCP    ← 프로토콜 명시 필수
          port: 3306
    - ports:               ← DNS는 UDP 필수!
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

이 기술이 필요한 직군

직군활용 상황
백엔드 개발자서비스 포트 설계, 방화벽 ACL 요청, 로컬 개발 환경 구성
DevOps/SRE포트 충돌 진단, 방화벽 정책 검증, 서비스 헬스체크 구현
보안 엔지니어불필요한 포트 노출 감사, ACL 정책 리뷰
클라우드 엔지니어VPC 보안 그룹 설계, NLB/ALB 리스너 설정

알아두면 차별화되는 지식

QUIC과 HTTP/3 — UDP 443

HTTP/3은 TCP 대신 UDP 기반의 QUIC 프로토콜을 사용합니다. 기존 방화벽에서 TCP 443만 허용하면 HTTP/3이 작동하지 않아 HTTP/2로 폴백됩니다.

로컬 터미널
# QUIC(HTTP/3) 지원 여부 확인
$ curl -v --http3 https://cloudflare.com 2>&1 | grep "HTTP/"

SO_REUSEPORT — 포트 공유

여러 프로세스나 스레드가 동일 포트를 공유하는 기술:

로컬 터미널
# nginx 멀티 워커가 80 포트 공유하는 예
$ ss -tlnp | grep :80
# LISTEN  0  511  0.0.0.0:80  processes:(("nginx",pid=1234,...),("nginx",pid=1235,...))

TIME_WAIT 상태

TCP 연결 종료 후 약 2MSL(보통 60초) 동안 소켓이 TIME_WAIT 상태로 남습니다. 대량의 단기 연결을 처리하는 서버에서 포트 고갈이 발생할 수 있습니다:

로컬 터미널
# TIME_WAIT 연결 수 확인
$ ss -tan state time-wait | wc -l

# TIME_WAIT 빠른 재사용 설정
$ sudo sysctl net.ipv4.tcp_tw_reuse=1

핵심 명령어 요약

로컬 터미널
# nc 서버 모드 (포트 열기)
nc -l [포트]
nc -l -p [포트] -v

# nc 클라이언트 모드 (접속)
nc [IP] [포트]
nc -zv [IP] [포트]     # 포트 개방 확인 (데이터 전송 없음)
nc -zuv [IP] [포트]    # UDP 포트 확인

# 포트 현황 확인
ss -tlnp              # TCP LISTEN 포트 + 프로세스
ss -ulnp              # UDP LISTEN 포트 + 프로세스
ss -tnp               # 현재 TCP 연결 상태

# 서비스별 기본 포트 확인
grep [서비스명] /etc/services

체크리스트

방화벽 ACL 요청 전 확인 사항:

  • 포트 번호를 확인했는가?
  • 프로토콜이 TCP인지 UDP인지 명시했는가?
  • DNS(53)는 UDP와 TCP 모두 포함했는가?
  • 출발지/목적지 IP 또는 CIDR을 정확히 명시했는가?
  • 단방향인지 양방향인지 확인했는가?
  • ss -tlnp로 서비스가 올바른 주소에 바인딩되었는지 확인했는가?

관련 모듈로 더 깊이:

다음 모듈에서는 ip addrnmcli로 NIC IP를 설정하고 고정 IP를 영구 적용하는 방법을 다룹니다.

지식 확인

퀴즈 — 8문제

Q1

애플리케이션 배포 후 `tcpdump -i eth0 port 8080`으로 패킷을 캡처했습니다. 클라이언트 SYN은 보이는데 서버에서 SYN-ACK가 오지 않고 클라이언트가 SYN을 재전송합니다. 이 상황의 원인은?

Q2

UDP가 TCP 대신 사용되기에 적합한 서비스는?

Q3

사내 DNS 서버(UDP 53)를 운영 중입니다. 특정 서버에서 nslookup은 되는데 외부 도메인 zone transfer가 실패합니다. 원인으로 가장 적절한 것은?

Q4

방화벽 정책을 신청했는데 개통 전 포트 통신을 임시로 테스트하고 싶습니다. 상대 서버에 아무 서비스도 없을 때 연결 가능한지 확인하는 방법은?

Q5

방화벽 정책을 요청할 때 프로토콜 명시가 중요한 이유는?

Q6

0~1023 범위의 'Well-known 포트'(예: 22 SSH, 80 HTTP, 443 HTTPS, 53 DNS)가 약속된 이유와 운영상 함의는?

Q7

[심화] TCP 연결은 정상 수립되고 작은 요청도 잘 처리되는데, 큰 데이터 전송만 멈추는 'PMTUD 블랙홀'의 직접 원인은?

Q8

[심화] 'ping도 되고 작은 요청도 되는데 큰 업로드만 멈춘다'는 신고에서 경로 MTU 문제를 확인하는 진단으로 가장 적절한 것은?

0 / 8 답변

🧪 실습으로 확인하기

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

초급

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

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

이것도 배워보세요