서비스 포트를 열었다고 했지만 클라이언트는 여전히 연결하지 못합니다. 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 포트 통신을 직접 실습할 수 있다
nc -h 2>&1 | head -5ss -tlnpnc -l 9000터미널 B에서 'nc localhost 9000'으로 접속하면 양방향 통신 실습 가능
TCP — 신뢰성을 보장하는 연결 지향 프로토콜
마이크로서비스를 배포했는데 클러스터 내부에서는 헬스체크가 통과하지만 외부 모니터링은 타임아웃을 보고합니다. 방화벽도, 포트도, 프로세스도 문제없습니다. ss -tan 출력을 보면 SYN_RECV 상태로 쌓인 연결들이 보이는데, 이 상태가 무엇을 뜻하는지 해석조차 못 합니다. TCP의 연결 수립 과정을 이해하지 못하면 이 출력이 아무 의미 없는 숫자로만 보입니다.
확대
TCP란 무엇인가
**TCP(Transmission Control Protocol)**는 데이터를 순서대로, 빠짐없이, 오류 없이 전달하도록 설계된 전송 계층 프로토콜입니다. 이를 위해 연결 수립, 흐름 제어, 오류 감지 및 재전송 메커니즘을 갖추고 있습니다.
3-Way Handshake — 연결 수립
TCP 통신을 시작하기 전, 반드시 3단계 핸드셰이크로 연결을 수립합니다.
확대
각 단계의 의미:
- SYN: 클라이언트가 연결을 요청하며 초기 시퀀스 번호(ISN)를 전달
- SYN-ACK: 서버가 요청을 수락하고, 자신의 ISN을 전달하며 클라이언트의 SYN을 ACK
- ACK: 클라이언트가 서버의 SYN을 ACK하며 연결 수립 완료
연결 종료 — 4-Way Teardown
TCP 연결 종료는 4단계를 거칩니다:
확대
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이 뜨지"를 서로 다른 단계의 서로 다른 증상으로 구분할 수 있습니다. 같은 "연결 안 됨"도 어느 단계에서 끊겼는지에 따라 원인이 완전히 다릅니다.
[클라이언트] 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·ESTABLISHED | 3-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 — 속도 우선, 신뢰성 포기
**UDP(User Datagram Protocol)**는 TCP의 연결 수립, 순서 보장, 재전송 메커니즘을 모두 제거하고 최소한의 기능만 가진 프로토콜입니다.
확대
TCP vs UDP 비교
어떤 상황에서 TCP를 쓰고 어떤 상황에서 UDP를 쓸지는 각 프로토콜의 특성 차이에서 결정됩니다.
| 특성 | TCP | UDP |
|---|---|---|
| 연결 수립 | 필요 (3-Way Handshake) | 불필요 |
| 순서 보장 | 보장 | 미보장 |
| 재전송 | 자동 재전송 | 없음 |
| 오류 검사 | 헤더 + 데이터 | 선택적 체크섬 |
| 속도 | 상대적으로 느림 | 빠름 |
| 오버헤드 | 높음 (헤더 20~60바이트) | 낮음 (헤더 8바이트) |
| 적합한 용도 | 파일 전송, 웹, DB | DNS, 스트리밍, 게임 |
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 ~ 1023 | Well-known Ports | 표준 서비스, root 권한 필요 |
| 1024 ~ 49151 | Registered Ports | 등록된 애플리케이션 |
| 49152 ~ 65535 | Dynamic/Ephemeral | 클라이언트 임시 포트 |
필수 암기 Well-known Ports
| 포트 | 프로토콜 | 서비스 |
|---|---|---|
| 20, 21 | TCP | FTP (데이터, 제어) |
| 22 | TCP | SSH (보안 원격 접속) |
| 23 | TCP | Telnet (비암호화 원격) |
| 25 | TCP | SMTP (이메일 발송) |
| 53 | UDP (+ TCP) | DNS |
| 67, 68 | UDP | DHCP |
| 80 | TCP | HTTP |
| 110 | TCP | POP3 (이메일 수신) |
| 123 | UDP | NTP (시간 동기화) |
| 143 | TCP | IMAP (이메일 수신) |
| 443 | TCP | HTTPS |
| 587 | TCP | SMTP Submission (이메일 발송, TLS) |
| 3306 | TCP | MySQL |
| 5432 | TCP | PostgreSQL |
| 6379 | TCP | Redis |
| 8080 | TCP | HTTP 대체 (개발/프록시) |
| 27017 | TCP | MongoDB |
Ephemeral Port
클라이언트가 서버에 연결할 때, 운영체제가 자동으로 49152~65535 범위에서 임시 포트를 할당합니다.
확대
# 실습 디렉토리 준비
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(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에서 수신됩니다:
[터미널 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를 클라이언트 모드로 사용하여 원격 서버의 특정 포트가 열려 있는지 확인합니다. 방화벽 정책 확인이나 서비스 응답 테스트에 활용됩니다.
단계별 실습
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 비교
| 도구 | 강점 | 약점 |
|---|---|---|
nc | TCP/UDP 모두 지원, 스크립팅 용이 | TLS 미지원 |
telnet | 단순, 배너 확인 용이 | UDP 미지원, 구식 |
curl | HTTPS 지원, 다양한 프로토콜 | 단순 포트 체크엔 과함 |
목표
실제 운영 환경에서 방화벽 ACL(Access Control List) 정책을 요청할 때 필요한 정보를 올바르게 명시하는 방법을 익힙니다.
시나리오
애플리케이션 서버(10.0.1.20)가 다음 서비스에 접근해야 합니다:
- 데이터베이스 서버(10.0.2.10)의 MySQL(3306/TCP)
- 외부 DNS 서버(8.8.8.8)의 DNS(53/UDP)
- 외부 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이 차단된 경우, 또는 dig나 nslookup 옵션에 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)
진단 순서
외부 접속이 실패하면 다음 순서로 좁혀갑니다.
- 서버에서
ss -tlnp | grep [포트]0.0.0.0:[포트]→ 바인딩 OK → 방화벽 확인으로127.0.0.1:[포트]→ 바인딩 문제 → 서비스 설정 변경
- OS 방화벽 확인 —
firewall-cmd --list-ports,iptables -L INPUT -n - 클라우드 보안 그룹 확인 (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 clamping — iptables ... -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu로 SYN의 MSS를 경로 MTU에 맞춰 낮춰, 애초에 큰 세그먼트가 생기지 않게 합니다. 터널 구간이면 인터페이스 MTU를 실제 값(예: 1400)으로 맞추는 것도 방법입니다.
마이크로서비스 환경의 포트 관리
마이크로서비스 아키텍처에서는 서비스마다 포트를 할당하고 관리해야 합니다.
# 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에서 포트와 프로토콜 명시:
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로 서비스가 올바른 주소에 바인딩되었는지 확인했는가?
관련 모듈로 더 깊이:
- telnet과 nc(netcat) 명령어로 L4 포트 통신 상태 점검 — TCP/UDP 포트가 실제로 열려 있는지 직접 점검하는 도구
- netstat과 ss 명령어로 커넥션 상태(ESTABLISHED 등) 분석 —
ss로 TCP/UDP LISTEN 포트와 연결 상태를 확인하는 법 - 방화벽 정책 설계와 접근 제어 리스트(ACL) 구성 실무 — 포트와 프로토콜을 정확히 명시해 방화벽 ACL을 요청하는 법
다음 모듈에서는 ip addr과 nmcli로 NIC IP를 설정하고 고정 IP를 영구 적용하는 방법을 다룹니다.