infra
Platform

모듈 맵

[Linux] ip/ifconfig 명령어로 네트워크 카드 정보 파악 및 라우팅 테이블

0 / 37 완료

펼치기
0 / 37 완료0%

Linux · 18 / 37

[Linux] ip/ifconfig 명령어로 네트워크 카드 정보 파악 및 라우팅 테이블

ip, ss, netstat, tcpdump, curl로 Linux 네트워크를 진단하고 트러블슈팅합니다

🚨INCIDENT ALERT
HIGH

배포한 서버에 갑자기 접속이 안 됩니다. 로그를 봐도 애플리케이션은 정상입니다. "어디서부터 확인해야 하나." IP가 잘못 설정된 건지, 포트가 닫힌 건지, DNS 문제인지 — 체계 없이 방화벽만 한참 뒤지다 결국 DNS 설정 오류였다는 결론에 도달하기도 합니다.

OSI 계층 순서대로 ping → ss → dig → curl을 돌리면 대부분의 네트워크 장애를 스스로 추적할 수 있습니다.

네트워킹 기초 (Networking Basics)

이번 챕터에서 배울 것
  • 1OSI 7계층 모델과 Linux 명령어를 매핑하고 계층별로 장애를 좁힐 수 있다
  • 2ip 명령어로 인터페이스·IP 주소·라우팅 테이블을 조회하고 설정할 수 있다
  • 3ss/netstat으로 LISTEN 중인 포트와 소켓 상태를 확인할 수 있다
  • 4tcpdump로 패킷을 캡처하고 curl로 HTTP 요청을 단계별로 디버깅할 수 있다
  • 5DNS 설정(resolv.conf, /etc/hosts)과 dig/nslookup으로 이름 해석 문제를 진단할 수 있다
실습 환경 준비
네트워크 도구 설치 확인
which ip ss tcpdump curl dig || sudo apt-get install -y iproute2 tcpdump curl dnsutils
현재 인터페이스 및 IP 주소 확인
ip -br addr show
기본 게이트웨이와 라우팅 테이블 확인
ip route show
💡개념

OSI 모델과 Linux 네트워킹 스택

OSI 7계층과 Linux 네트워킹 스택 매핑

curl로 외부 API를 호출하는데 응답이 없을 때, DNS 문제인지 라우팅 문제인지 방화벽 문제인지 바로 알기 어렵습니다. 체계 없이 접근하면 방화벽만 한참 뒤지다 결국 DNS 설정 오류였다는 결론에 도달하기도 합니다. OSI 7계층 모델을 리눅스 명령어와 연결해두면, 계층별로 "여기까지는 되고 여기서부터 안 된다"는 방식으로 원인을 좁힐 수 있습니다.

네트워크 문제를 진단할 때 어느 계층의 문제인지 빠르게 파악하는 것이 핵심입니다. OSI 7계층 모델을 Linux 명령어와 매핑하면 다음과 같습니다.

OSI 계층별 Linux 진단 명령어 — 계층 순서로 장애 원인 좁히기

계층이름역할Linux 도구
L7응용 계층HTTP, DNS, SSH 프로토콜curl, dig, ssh
L4전송 계층TCP/UDP 포트, 연결 상태ss, netstat, tcpdump
L3네트워크 계층IP 라우팅, ICMPip route, ping, traceroute, mtr
L2데이터링크 계층MAC 주소, 이더넷 프레임ip link, arp

트러블슈팅 원칙: L2 → L3 → L4 → L7 순서로 아래에서 위로 확인합니다.

  • 인터페이스가 UP인가? (L2)
  • IP가 올바르고 라우팅이 되는가? (L3)
  • 포트가 열려 있는가? (L4)
  • 애플리케이션이 올바르게 응답하는가? (L7)

주의: ifconfignetstatnet-tools 패키지에 포함된 구버전 도구입니다. 현대 Linux에서는 ip 명령어(iproute2 패키지)와 ss로 대체되었습니다. 일부 배포판에서는 기본 설치되어 있지 않습니다.

💡개념

ip 명령어 체계 — ifconfig는 deprecated

ip 명령어 체계 — ifconfig 대응표와 출력 읽기

검색해서 나온 네트워크 설정 가이드가 ifconfig eth0 192.168.1.10으로 시작하는데, 그 명령이 서버에 없어서 당황한 경험이 있을 것입니다. Ubuntu 18.04부터 net-tools 패키지(ifconfig, netstat, route 포함)가 기본 설치에서 빠졌습니다. 현대 Linux에서는 ip 명령어 하나로 인터페이스 조회, IP 설정, 라우팅 관리를 모두 처리합니다. 구글에서 나온 오래된 예제와 현재 시스템 명령어가 다른 이유가 이 deprecated 때문이므로, 어떻게 대응하는지 한 번 익혀두면 혼란이 사라집니다.

ip 명령어는 iproute2 패키지의 핵심으로, 네트워크 인터페이스·라우팅·터널·정책 라우팅을 통합적으로 다룹니다.

기본 구조ip [옵션] <오브젝트> <커맨드> 형식으로 네트워크 오브젝트를 제어합니다:

ip [옵션] <오브젝트> <커맨드>

주요 오브젝트:

오브젝트줄임역할
addressaddr, aIP 주소 관리
linkl네트워크 인터페이스(L2) 상태
router라우팅 테이블
neighbourneighARP 테이블

ifconfig vs ip 대응표 — 구버전 ifconfig 명령을 현대적인 ip 명령으로 대체하는 치환 목록입니다:

구버전 (ifconfig)신버전 (ip)
ifconfigip addr show
ifconfig eth0 upip link set eth0 up
ifconfig eth0 192.168.1.10/24ip addr add 192.168.1.10/24 dev eth0
route -nip route show
route add default gw 192.168.1.1ip route add default via 192.168.1.1

출력을 사람이 읽기 좋게 만들려면 -c 플래그로 컬러 출력을, -br 플래그로 간결한 브리프 형식을 사용합니다:

로컬 터미널
ip -c -br addr show

실습 전 디렉토리와 예제 파일을 먼저 준비합니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part4/exam_17 && cd /tmp/linux/part4/exam_17
로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part4/exam_1 && cd /tmp/linux/part4/exam_1

이제 실습을 진행합니다.

1네트워크 인터페이스와 IP 주소 확인

현재 시스템의 모든 네트워크 인터페이스와 IP 주소를 확인합니다.

로컬 터미널
# 모든 인터페이스 표시
ip addr show

# 특정 인터페이스만
ip addr show eth0

# 간결한 형식 (브리프)
ip -br addr show

출력 예시ip addr show의 실제 출력에서 각 줄이 의미하는 것을 확인합니다:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
    link/ether 52:54:00:ab:cd:ef brd ff:ff:ff:ff:ff:ff
    inet 10.0.1.15/24 brd 10.0.1.255 scope global eth0
    inet6 fe80::5054:ff:feab:cdef/64 scope link

읽는 법:

  • UP,LOWER_UP — 인터페이스가 활성화되어 물리 링크도 연결됨
  • inet 10.0.1.15/24 — IPv4 주소와 서브넷 마스크
  • mtu 1500 — 최대 전송 단위 (바이트). 점보 프레임은 9000
  • scope global — 외부 통신 가능한 주소 (host는 루프백 전용)

인터페이스 상태만 빠르게 확인 — UP/DOWN 상태와 IP 주소를 한 줄로 빠르게 확인합니다:

로컬 터미널
ip link show
# 또는
ip -br link show
lo               UNKNOWN        00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
eth0             UP             52:54:00:ab:cd:ef <BROADCAST,MULTICAST,UP,LOWER_UP>
docker0          DOWN           02:42:a3:1b:2c:4d <NO-CARRIER,BROADCAST,MULTICAST,UP>
ip addr show
🔍실행 후 확인할 것
  • ip link show 출력에서 활성 인터페이스는 'UP' 상태, 비활성은 'DOWN' 상태로 표시된다
  • ip addr show 로 각 인터페이스에 할당된 IP 주소(inet)와 서브넷 마스크를 확인한다
  • lo(루프백) 인터페이스에 127.0.0.1/8 주소가 있는 것을 확인한다
  • ip -br link show 로 간결한 형식으로 인터페이스 이름, 상태, MAC 주소를 한눈에 볼 수 있다
2리스닝 포트와 프로세스 확인

ss(socket statistics)는 netstat의 현대적 대체 도구입니다. 커널 직접 조회로 netstat보다 빠릅니다.

로컬 터미널
# TCP 리스닝 포트와 프로세스 표시
ss -tlnp

# UDP 리스닝 소켓
ss -ulnp

# TCP/UDP 모두, ESTABLISHED 포함
ss -anp

# 특정 포트 필터
ss -tlnp sport = :8080

# 특정 프로세스 ID로 필터
ss -tlnp | grep pid=1234

주요 플래그-t, -u, -l, -n, -p 각 플래그의 의미입니다:

플래그의미
-tTCP 소켓만
-uUDP 소켓만
-lLISTEN 상태만
-n포트를 숫자로 (이름 해석 안 함, 빠름)
-p프로세스 정보 표시 (root 권한 필요)
-a모든 상태 (LISTEN + ESTABLISHED 등)

출력 예시ss -tulnp 실제 출력에서 각 컬럼이 의미하는 정보입니다:

State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
LISTEN  0       128     0.0.0.0:22          0.0.0.0:*          users:(("sshd",pid=987,fd=3))
LISTEN  0       511     0.0.0.0:80          0.0.0.0:*          users:(("nginx",pid=1234,fd=6))
LISTEN  0       128     127.0.0.1:5432      0.0.0.0:*          users:(("postgres",pid=2345,fd=5))
LISTEN  0       128     0.0.0.0:8080        0.0.0.0:*          users:(("java",pid=3456,fd=12))

읽는 법:

  • 0.0.0.0:80 — 모든 인터페이스에서 수신 (외부 접근 가능)
  • 127.0.0.1:5432 — 루프백만 수신 (로컬 전용, 외부 차단)
  • Recv-Q — 수신 버퍼에 쌓인 바이트 (0이 정상; 높으면 애플리케이션이 처리 못 함)
  • Send-Q — 송신 버퍼에 쌓인 바이트 (0이 정상; 높으면 네트워크 병목)

연결 상태별 요약 통계ss -s로 소켓 상태별 총 개수를 빠르게 파악합니다:

로컬 터미널
ss -s
Total: 412
TCP:   87 (estab 43, closed 12, orphaned 0, timewait 32)
ss -tlnp
3라우팅 테이블 확인과 경로 추적

패킷이 어떤 경로로 나가는지 확인합니다.

로컬 터미널
# 라우팅 테이블 전체 표시
ip route show

# 특정 목적지로 가는 경로 조회
ip route get 8.8.8.8

출력 예시ip route show에서 기본 라우팅 테이블을 읽는 법입니다:

default via 10.0.1.1 dev eth0 proto dhcp src 10.0.1.15 metric 100
10.0.1.0/24 dev eth0 proto kernel scope link src 10.0.1.15
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1

읽는 법:

  • default via 10.0.1.1 — 기본 게이트웨이 (나머지 모든 트래픽 여기로)
  • 10.0.1.0/24 dev eth0 ... scope link — 같은 서브넷, 직접 연결
  • metric 100 — 경로 우선순위 (낮을수록 우선)

특정 IP로 가는 실제 경로ip route get으로 특정 목적지까지 실제로 어떤 경로를 쓰는지 확인합니다:

로컬 터미널
ip route get 8.8.8.8
# 8.8.8.8 via 10.0.1.1 dev eth0 src 10.0.1.15 uid 0

경로 추적 — traceroute / mtr — 목적지까지 각 홉을 추적해 어디서 지연이 발생하는지 파악합니다:

로컬 터미널
# 경로 추적 (각 홉의 지연)
traceroute 8.8.8.8

# mtr: traceroute + ping 실시간 통합 (더 유용)
mtr --report --report-cycles 10 8.8.8.8
Start: 2026-03-26T03:45:00+0000
HOST: web-server-01          Loss%   Snt   Last   Avg  Best  Wrst StDev
  1.|-- 10.0.1.1              0.0%    10    0.4   0.5   0.4   0.7   0.1
  2.|-- 203.0.113.1           0.0%    10    1.2   1.3   1.1   1.6   0.2
  3.|-- 8.8.8.8               0.0%    10   11.4  11.6  11.2  12.1   0.3

Loss%가 중간 홉에서만 높고 최종 목적지는 정상이면, 해당 홉이 ICMP를 rate-limit하는 것일 수 있습니다.

ip route show
4패킷 캡처 기초

tcpdump는 L3/L4 수준의 실제 패킷을 캡처해 분석합니다. root 또는 CAP_NET_RAW 권한이 필요합니다.

로컬 터미널
# 기본: eth0에서 포트 80 트래픽 캡처, 파일로 저장
sudo tcpdump -i eth0 port 80 -w /tmp/cap.pcap

# 화면에서 실시간으로 읽기 (저장 안 함)
sudo tcpdump -i eth0 port 80 -n

# 저장된 파일 읽기
tcpdump -r /tmp/cap.pcap

# 특정 호스트와의 트래픽만
sudo tcpdump -i eth0 host 10.0.1.20 -n

# HTTP GET 요청만 (문자열 필터)
sudo tcpdump -i eth0 -A -s0 port 80 | grep "GET\|POST\|HTTP"

# SYN 패킷만 (연결 시도)
sudo tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0' -n

주요 플래그 — tcpdump 옵션의 의미입니다:

플래그의미
-i eth0인터페이스 지정 (-i any는 모든 인터페이스)
-nIP/포트를 숫자로 (해석 안 함, 빠름)
-v, -vv상세 출력
-A패킷 내용을 ASCII로 출력
-s0패킷 전체 캡처 (기본은 일부만)
-w file파일로 저장 (Wireshark에서 분석 가능)
-c 100100개 패킷 후 종료

실시간 출력 예시 — mtr이 각 홉의 지연시간과 패킷 손실률을 실시간으로 출력하는 형태입니다:

03:45:10.123456 IP 10.0.1.20.52341 > 10.0.1.15.80: Flags [S], seq 1234567890, win 64240
03:45:10.123789 IP 10.0.1.15.80 > 10.0.1.20.52341: Flags [S.], seq 987654321, ack 1234567891, win 65535
03:45:10.124012 IP 10.0.1.20.52341 > 10.0.1.15.80: Flags [.], ack 1, win 502

[S] = SYN, [S.] = SYN-ACK, [.] = ACK — TCP 3-way handshake가 정상임을 확인할 수 있습니다.

Wireshark로 심층 분석: .pcap 파일을 로컬로 복사해 Wireshark GUI에서 열면 스트림 재조립, HTTP 디코딩 등이 가능합니다.

SSH 접속 후
scp server:/tmp/cap.pcap ~/Downloads/
tcpdump -i eth0 port 80 -w /tmp/cap.pcap
5curl로 HTTP 디버깅

curl은 HTTP(S) 요청을 직접 보내 애플리케이션 계층(L7)을 검증하는 필수 도구입니다.

로컬 또는 서버
# 상세 출력 (TLS 핸드셰이크, 헤더, 응답 포함)
curl -v https://example.com

# 응답 헤더만 확인 (HEAD 요청)
curl -I https://example.com

# 응답 시간 측정 (성능 분석)
curl -o /dev/null -s -w "DNS: %{time_namelookup}s\nTCP connect: %{time_connect}s\nTLS: %{time_appconnect}s\nFirst byte: %{time_starttransfer}s\nTotal: %{time_total}s\n" https://example.com

# POST 요청
curl -X POST https://api.example.com/v1/items \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"name": "widget", "qty": 5}'

# 특정 IP로 요청 (DNS 우회, 로드밸런서 뒤 특정 서버 테스트)
curl -v --resolve example.com:443:10.0.1.15 https://example.com

# 응답 코드만 확인
curl -o /dev/null -s -w "%{http_code}" https://example.com

curl -v 출력 읽는 법> 는 요청, < 는 응답 헤더를 나타내며 DNS→TCP→HTTP 단계를 추적합니다:

*   Trying 93.184.216.34:443...         ← TCP 연결 시도
* Connected to example.com port 443     ← TCP 연결 성공
* TLSv1.3, TLS handshake               ← TLS 협상 시작
* SSL connection using TLSv1.3          ← TLS 연결 성공
> GET / HTTP/2                          ← 요청 헤더 (> 는 송신)
> Host: example.com
> User-Agent: curl/8.4.0
>
< HTTP/2 200                            ← 응답 헤더 (< 는 수신)
< content-type: text/html
< content-length: 1256
<
<!doctype html>...                      ← 응답 바디

응답 시간 분석 예시curl -w 포맷으로 각 단계별 소요 시간을 측정합니다:

DNS: 0.003s          ← DNS 조회 (느리면 resolv.conf 확인)
TCP connect: 0.012s  ← TCP handshake (느리면 네트워크 레이턴시)
TLS: 0.045s          ← TLS 협상 (느리면 인증서 체인 문제)
First byte: 0.234s   ← 서버 처리 시간 (TTFB)
Total: 0.891s        ← 전체 소요 시간
curl -v https://example.com
6DNS 설정 파일 확인

DNS 해석 실패의 대부분은 이 두 파일과 systemd-resolved 설정에서 비롯됩니다.

로컬 터미널
# DNS 서버 설정 확인
cat /etc/resolv.conf
# Generated by systemd-resolved
nameserver 127.0.0.53
options edns0 trust-ad
search example.internal
로컬 터미널
# 로컬 호스트 오버라이드 확인
cat /etc/hosts
127.0.0.1   localhost
::1         localhost ip6-localhost
10.0.1.100  db.internal postgres.internal
로컬 터미널
# DNS 해석 테스트 (dig - 가장 상세)
dig example.com
dig example.com @8.8.8.8    # 특정 DNS 서버로 조회
dig +short example.com       # IP만 출력

# nslookup (간단)
nslookup example.com

# getent: /etc/hosts + DNS 통합 해석 (애플리케이션과 동일한 경로)
getent hosts example.com

# systemd-resolved 상태
systemd-resolve --status
resolvectl status

/etc/nsswitch.conf — 이름 해석 우선순위 결정:

로컬 터미널
grep hosts /etc/nsswitch.conf
# hosts: files dns myhostname
# files(=/etc/hosts) → dns 순서

컨테이너/쿠버네티스 환경 주의사항:

  • 컨테이너 내 /etc/resolv.conf는 호스트와 다름 (Docker가 자동 생성)
  • K8s Pod은 CoreDNS를 nameserver로 사용
  • search 도메인이 길면 DNS 쿼리가 여러 번 시도됨 (레이턴시 증가)
cat /etc/resolv.conf && cat /etc/hosts

증상: ss -tlnp로 포트가 LISTEN 중인 것을 확인했는데, 외부 클라이언트에서 접속이 거부됩니다.

원인 분류 및 진단:

1단계: 바인딩 주소 확인 — 서비스가 어떤 주소에 바인드됐는지 확인합니다:

로컬 터미널
ss -tlnp | grep :8080
# LISTEN 0 128 127.0.0.1:8080  ← 루프백만! 외부 접근 불가
# LISTEN 0 128 0.0.0.0:8080    ← 정상 (모든 인터페이스)

애플리케이션이 127.0.0.1에만 바인딩했다면 설정 파일에서 0.0.0.0 또는 :: (IPv6)로 변경합니다.

2단계: iptables/nftables 방화벽 확인 — 방화벽 규칙에서 해당 포트가 허용됐는지 확인합니다:

로컬 터미널
# iptables 규칙 확인
sudo iptables -L INPUT -n -v --line-numbers
sudo iptables -L FORWARD -n -v

# 특정 포트 관련 규칙만 필터
sudo iptables -L -n | grep 8080

# nftables (최신 배포판)
sudo nft list ruleset

# ufw (Ubuntu)
sudo ufw status verbose

2단계-b: iptables로 포트 허용 — 방화벽에서 해당 포트의 인바운드 트래픽을 허용합니다:

로컬 터미널
# 포트 8080 허용 (영구 저장은 iptables-save 또는 ufw 사용)
sudo iptables -A INPUT -p tcp --dport 8080 -j ACCEPT

# ufw 사용 시
sudo ufw allow 8080/tcp

# firewalld (RHEL/CentOS 계열)
sudo firewall-cmd --add-port=8080/tcp --permanent
sudo firewall-cmd --reload

3단계: 클라우드/하이퍼바이저 보안 그룹

AWS Security Group, GCP Firewall Rules, Azure NSG는 OS 내부 방화벽과 별개입니다. 클라우드 콘솔에서 인바운드 규칙을 확인합니다.

4단계: 클라이언트 측에서 연결 테스트 — nc나 telnet으로 클라이언트에서 직접 포트 연결을 테스트합니다:

로컬 터미널
# TCP 연결 가능 여부 확인
nc -zv 10.0.1.15 8080
# Connection to 10.0.1.15 8080 port [tcp/*] succeeded!

# curl로 HTTP 확인
curl -v http://10.0.1.15:8080/health

# telnet (기본 도구)
telnet 10.0.1.15 8080

빠른 진단 체크리스트:

  1. ss -tlnp | grep <포트> — 리스닝 중인가? 어느 주소에 바인딩?
  2. iptables -L INPUT -n | grep <포트> — OS 방화벽 차단?
  3. 클라우드 콘솔 — 보안 그룹/방화벽 룰 차단?
  4. nc -zv <서버IP> <포트> — 실제 TCP 연결 가능?

증상: curl https://api.example.com에서 Could not resolve host: api.example.com 오류 발생.

단계별 진단:

1단계: 기본 DNS 해석 테스트 — dig, nslookup으로 DNS 해석이 정상 동작하는지 확인합니다:

로컬 또는 서버
# DNS 없이 IP 직접 연결 테스트 (DNS 문제인지 확인)
curl -v --resolve api.example.com:443:93.184.216.34 https://api.example.com

# 공용 DNS로 직접 조회
dig api.example.com @8.8.8.8
dig api.example.com @1.1.1.1

# 로컬 DNS 서버로 조회
dig api.example.com

2단계: resolv.conf 확인 — /etc/resolv.conf에 올바른 네임서버가 설정됐는지 확인합니다:

로컬 터미널
cat /etc/resolv.conf

# nameserver가 비어있거나 없는 경우
# nameserver가 응답 없는 서버를 가리키는 경우
로컬 터미널
# nameserver에 직접 ping
ping -c 3 $(grep nameserver /etc/resolv.conf | head -1 | awk '{print $2}')

3단계: systemd-resolved 확인 (Ubuntu 18.04+) — Ubuntu 18.04 이후 systemd-resolved 상태를 확인합니다:

서버 터미널
systemctl status systemd-resolved

# 재시작
sudo systemctl restart systemd-resolved

# stub resolver 상태
resolvectl status

# DNS 캐시 초기화
sudo resolvectl flush-caches

4단계: 컨테이너 환경 — 컨테이너에서 DNS가 안 되면 Docker 내부 DNS 설정을 확인합니다:

Docker
# 컨테이너 내부에서 확인
docker exec -it <container> cat /etc/resolv.conf
docker exec -it <container> nslookup google.com

# Docker 데몬 DNS 설정
cat /etc/docker/daemon.json
# {"dns": ["8.8.8.8", "8.8.4.4"]}

일반적인 원인과 해결 — DNS 장애의 흔한 원인별 증상과 해결 방법을 정리했습니다:

원인증상해결
nameserver 미설정/etc/resolv.conf 비어있음nameserver 8.8.8.8 추가
systemd-resolved 다운127.0.0.53 응답 없음systemctl restart systemd-resolved
/etc/hosts 잘못된 항목특정 호스트만 실패/etc/hosts 확인 및 수정
내부 DNS 서버 다운사내 도메인만 실패내부 DNS 서버 상태 확인
방화벽 UDP 53 차단외부 도메인 실패iptables -A INPUT -p udp --dport 53 -j ACCEPT

증상: 고트래픽 서버에서 connect: Cannot assign requested address 오류, 또는 새 연결을 못 맺음.

원인: TCP 연결 종료 후 OS는 일정 시간(기본 60초) 동안 소켓을 TIME_WAIT 상태로 유지합니다. 클라이언트 측 ephemeral 포트(기본 32768-60999, 약 28231개)가 소진되면 새 연결을 못 맺습니다.

1단계: 현재 상태 확인 — TIME_WAIT 소켓이 얼마나 쌓여 있는지 확인합니다:

로컬 터미널
# TIME_WAIT 소켓 수 확인
ss -ant | grep TIME-WAIT | wc -l

# 상태별 통계
ss -s

# 상세 TIME_WAIT 소켓 목록
ss -ant state time-wait | head -20
TIME-WAIT  0  0  10.0.1.15:52341  10.0.1.20:80
TIME-WAIT  0  0  10.0.1.15:52342  10.0.1.20:80
... (수천 개)

2단계: Ephemeral 포트 범위 확인 — 사용 가능한 클라이언트 포트 범위가 좁으면 포트 고갈이 빨리 옵니다:

로컬 터미널
cat /proc/sys/net/ipv4/ip_local_port_range
# 32768   60999  ← 사용 가능 포트: 28231개

3단계: 커널 파라미터 튜닝 — sysctl로 포트 범위를 넓히고 TIME_WAIT 재사용을 활성화합니다:

로컬 터미널
# Ephemeral 포트 범위 확장
sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535"

# TIME_WAIT 소켓을 새 연결에 재사용 (아웃바운드)
sudo sysctl -w net.ipv4.tcp_tw_reuse=1

# TIME_WAIT 타임아웃 확인 (변경 권장 안 함)
cat /proc/sys/net/ipv4/tcp_fin_timeout

# 영구 적용 (/etc/sysctl.conf 또는 /etc/sysctl.d/99-network.conf)
cat >> /etc/sysctl.d/99-network.conf << 'EOF'
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_tw_reuse = 1
EOF
sudo sysctl -p /etc/sysctl.d/99-network.conf

4단계: 근본 원인 해결 (권장)

  • HTTP Keep-Alive 활성화 (연결 재사용으로 TIME_WAIT 감소)
  • 연결 풀링 사용 (DB 연결, 백엔드 API 호출)
  • 로드밸런서에서 연결 수명 관리

주의: tcp_tw_recycle은 Linux 4.12에서 제거됨. NAT 환경에서 문제를 일으킬 수 있어 사용하지 마세요.

💼
실무 맥락서비스 포트 충돌 디버깅 — already in use
현업 패턴

상황: 새 서비스를 시작하려는데 bind: address already in use 오류가 발생합니다.

로컬 터미널
$ sudo systemctl start my-service
Job for my-service.service failed. See "journalctl -xe" for details.

$ journalctl -u my-service -n 20
Error: listen tcp 0.0.0.0:8080: bind: address already in use

체계적 진단 순서:

1. 어떤 프로세스가 포트를 점유하고 있는가? — ss나 lsof로 해당 포트를 사용 중인 PID를 찾습니다:

로컬 터미널
# ss로 확인 (가장 빠름)
sudo ss -tlnp sport = :8080

# lsof으로 확인 (파일 기반)
sudo lsof -i :8080

# fuser으로 확인
sudo fuser 8080/tcp

출력 예시:

LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=4521,fd=15))

2. 해당 프로세스가 무엇인가? — PID로 프로세스 이름과 실행 경로를 확인합니다:

로컬 터미널
# PID로 프로세스 상세 확인
ps aux | grep 4521
cat /proc/4521/cmdline | tr '\0' ' '

# 어떤 서비스인지 확인
systemctl status $(ps -o unit= -p 4521)

3. 의도치 않은 프로세스라면 종료 — 의도하지 않은 프로세스가 포트를 점유 중이면 종료합니다:

로컬 터미널
# 정상 종료 시도
kill -15 4521

# 서비스로 관리되는 경우
sudo systemctl stop old-service

# 강제 종료 (최후 수단)
kill -9 4521

4. 의도적인 충돌이라면 포트 변경 — 두 서비스가 같은 포트를 쓰면 한쪽을 변경합니다:

로컬 터미널
# 새 서비스 설정 파일에서 포트 변경
# 예: /etc/my-service/config.yaml
port: 8081

# 또는 환경변수로
PORT=8081 ./my-service

5. 서비스 재시작 후 확인 — 포트 변경 후 서비스를 재시작하고 정상 바인딩됐는지 확인합니다:

서버 터미널
sudo systemctl start my-service
sudo systemctl status my-service
ss -tlnp sport = :8081

: 서비스 시작 스크립트에 미리 포트 검사를 추가하면 충돌을 조기에 발견할 수 있습니다:

로컬 터미널
if ss -tlnp | grep -q ":$PORT "; then
  echo "ERROR: Port $PORT is already in use"
  exit 1
fi
💼
실무 맥락프로덕션 네트워크 지연 원인 파악
현업 패턴

상황: 프로덕션 API 응답 시간이 평소 50ms에서 갑자기 2초로 증가했습니다. 알림이 울렸고 빠르게 원인을 파악해야 합니다.

체계적 트리아지 순서 (L2 → L3 → L4 → L7):

1단계: 기본 연결성 (L3) — 1분 — ping으로 3계층(IP) 연결 가능 여부를 먼저 확인합니다:

로컬 터미널
# 게이트웨이 응답 확인
ping -c 5 $(ip route show default | awk '{print $3}')

# 외부 인터넷 연결
ping -c 5 8.8.8.8

# DNS 응답 시간
time dig api.upstream.com @8.8.8.8

2단계: 경로 확인 (L3) — 2분 — traceroute나 mtr로 어느 홉에서 패킷이 멈추는지 확인합니다:

로컬 터미널
# 목적지까지 각 홉 지연 측정
mtr --report --report-cycles 20 10.0.2.50

# 특정 구간에서 패킷 로스가 있는가?

3단계: 소켓 상태 (L4) — 2분 — ss로 포트가 열려 있고 서비스가 바인드됐는지 확인합니다:

로컬 터미널
# 연결 상태 요약
ss -s

# CLOSE_WAIT, TIME_WAIT 과다 여부
ss -ant | awk '{print $1}' | sort | uniq -c | sort -rn

# Recv-Q가 쌓인 소켓 (처리 백로그)
ss -tnp | awk '$2 > 0 {print}'

# SYN backlog (SYN flood 또는 accept 큐 포화)
ss -lnt | awk 'NR>1 {print $2, $4}' | grep -v "^0 "

4단계: 실시간 패킷 분석 (L4) — 3분 — tcpdump로 패킷이 실제로 도달하는지 확인합니다:

로컬 터미널
# 백엔드 API 포트 트래픽 캡처 (30초)
sudo tcpdump -i eth0 host 10.0.2.50 and port 8080 -w /tmp/prod-$(date +%s).pcap -G 30 -W 1

# 재전송(Retransmit) 패킷 수 실시간 확인
watch -n 1 'cat /proc/net/snmp | grep -E "^Tcp:" | awk NR==2 | awk "{print \"Retrans:\", \$13, \"OutSegs:\", \$11}"'

5단계: 애플리케이션 계층 (L7) — 2분 — curl과 서비스 로그로 앱 레벨 문제를 확인합니다:

로컬 또는 서버
# 응답 시간 분해
curl -o /dev/null -s -w \
  "DNS:%{time_namelookup} TCP:%{time_connect} TLS:%{time_appconnect} TTFB:%{time_starttransfer} Total:%{time_total}\n" \
  https://api.upstream.com/health

# 에러율 확인 (nginx/haproxy 로그)
tail -f /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c

# 애플리케이션 로그 확인
journalctl -u my-service -n 100 --since "10 min ago" | grep -E "ERROR|WARN|timeout"

일반적인 원인과 해결 패턴 — 증상별로 가장 흔한 원인과 해결 방법을 정리했습니다:

증상원인해결
mtr에서 특정 홉 레이턴시 급등네트워크 장비 문제네트워크 팀 에스컬레이션
Recv-Q 큰 소켓 다수애플리케이션 처리 병목스레드/프로세스 증가, 프로파일링
TCP Retransmit 급증패킷 로스네트워크 품질 확인, NIC 드라이버
TTFB만 높음DB 쿼리 슬로우, 외부 API 지연DB slow query 로그, 의존 서비스 확인
TIME_WAIT 급증연결 풀 없는 단발성 연결Keep-Alive 또는 연결 풀 적용

기억할 원칙: 네트워크 지연은 대부분 단일 원인이 아닙니다. 계층별로 데이터를 수집하고, 정상 상태의 베이스라인과 비교해야 합니다. 프로덕션에서 tcpdump를 장시간 실행하면 디스크가 꽉 찰 수 있으니 -G(rotate interval)와 -W(file count) 플래그로 제한합니다.

💡개념

IPv6 기본 — Neighbor Discovery·ICMPv6와 흔한 장애

IPv6 Neighbor Discovery와 ICMPv6 필수 허용 규칙

IPv4만 설정하고 IPv6를 방치하면 "IPv6로만 통신하는 서비스에 접속 안 됨" 또는 "IPv4 방화벽은 열었는데 IPv6 경로로 차단" 같은 장애가 발생합니다.

IPv6 주소 구조 기초 — IPv6 주소 형식과 Link-Local, Loopback 주소의 의미를 파악합니다:

로컬 터미널
# 현재 IPv6 주소 확인
ip -6 addr show
# inet6 2001:db8::1/64 scope global  ← 글로벌 유니캐스트 (인터넷 라우팅 가능)
# inet6 fe80::1/64 scope link        ← 링크-로컬 (같은 세그먼트 내 통신)
# inet6 ::1/128 scope host           ← 루프백 (127.0.0.1에 해당)

# IPv6 라우팅 테이블 확인
ip -6 route show

ICMPv6 — IPv6의 핵심 제어 프로토콜 (차단 금지):

ICMPv6은 IPv4의 ARP 역할(Neighbor Discovery)을 담당하므로 차단하면 IPv6 통신 자체가 불가합니다.

ICMPv6 타입역할차단 시 영향
133 (RS)Router Solicitation기본 게이트웨이 미발견
134 (RA)Router Advertisement자동 주소 설정 실패
135 (NS)Neighbor SolicitationARP 역할 — 통신 불가
136 (NA)Neighbor AdvertisementARP 역할 — 통신 불가
128 (Echo Request)ping6ping 실패
로컬 터미널
# IPv6 연결 테스트
ping6 -c 3 2606:4700:4700::1111  # Cloudflare DNS
ping6 -c 3 ipv6.google.com

# IPv6 전용 연결 확인 (curl)
curl -6 -v https://ipv6.google.com 2>&1 | head -20

# IPv6 라우팅 문제 진단
traceroute6 ipv6.google.com

흔한 IPv4-only 방화벽 장애 — IPv4 방화벽만 설정하면 IPv6 경로로 우회 접근이 가능한 취약점이 생깁니다:

로컬 터미널
# iptables로 IPv4만 열고 ip6tables는 기본 DROP인 경우
# → IPv6로 접속 시도 시 silently dropped

# 확인: ip6tables 기본 정책
sudo ip6tables -L -n | grep "policy"

# 임시 해결: IPv6 전체 허용 (보안 설정은 추후)
sudo ip6tables -P INPUT ACCEPT
sudo ip6tables -P FORWARD ACCEPT
sudo ip6tables -P OUTPUT ACCEPT
💡개념

MTU/PMTUD 이슈 — 패킷 분할 문제 진단

MTU/PMTUD 이슈 — 패킷 분할 문제와 ICMP Fragmentation Needed

MTU(Maximum Transmission Unit) 불일치는 "일부 사이트만 안 됨", "작은 패킷은 되는데 큰 파일 전송이 실패" 같은 간헐적 장애를 만듭니다.

MTU 기본값과 문제 상황 — 환경별 MTU 기본값과 불일치 시 발생하는 증상을 정리했습니다:

환경일반적인 MTU
Ethernet1500 bytes
VPN (OpenVPN/WireGuard)1420~1450 bytes
AWS VPC9001 bytes (Jumbo Frame)
Docker bridge1500 bytes (호스트와 동일)

MTU 진단 명령 — ping의 -M do 옵션으로 경로 MTU를 단계적으로 탐색합니다:

로컬 터미널
# 현재 인터페이스 MTU 확인
ip link show | grep mtu

# PMTUD 테스트 — 분할 금지(DF bit) 패킷으로 최대 MTU 측정
ping -M do -s 1472 8.8.8.8
# -M do: Don't Fragment 설정
# -s 1472: 데이터 크기 (1472 + 28 ICMP 헤더 = 1500)
# 실패하면 "Frag needed" — MTU 이슈 확인

# 작동하는 최대 크기 찾기 (이진 탐색)
for size in 1472 1400 1300 1200; do
    if ping -M do -s $size -c 1 -W 2 8.8.8.8 &>/dev/null; then
        echo "MTU OK: $((size + 28))"
        break
    fi
done

# tracepath로 경로상 MTU 확인
tracepath 8.8.8.8
# 결과에서 "pmtu XXXX" 가 경로상 최소 MTU

VPN 환경 MTU 조정 — VPN 터널 오버헤드를 고려해 MTU를 낮게 설정합니다:

로컬 터미널
# WireGuard 인터페이스 MTU 조정 (/etc/wireguard/wg0.conf)
[Interface]
MTU = 1420  # 1500 - 80 (WireGuard 오버헤드)

# OpenVPN 클라이언트 설정
tun-mtu 1420
fragment 1300
mssfix 1260
💡개념

라우팅 트러블슈팅 — 게이트웨이·정적 라우트·정책 라우팅

라우팅 트러블슈팅 의사결정 트리 — localhost→게이트웨이→DNS

서버를 재부팅했더니 외부 통신이 안 됩니다. ping 8.8.8.8도 안 되고, curl도 안 됩니다. 그런데 같은 서브넷의 다른 서버로는 통신이 됩니다. 이런 상황에서 열이면 아홉은 기본 게이트웨이가 없거나 라우팅 테이블이 잘못된 경우입니다. 또는 서버에 NIC가 두 개인데 두 인터페이스로 들어온 트래픽이 뒤섞이면서 예상치 못한 경로로 나가는 비대칭 라우팅 문제도 있습니다. 라우팅 문제는 "어디까지는 되고 어디서부터 안 되는지"를 단계별로 좁혀가는 게 핵심입니다.

기본 라우팅 문제 진단 순서 — 기본 게이트웨이 누락 여부부터 단계적으로 라우팅 문제를 좁혀갑니다:

로컬 터미널
# 1. 현재 라우팅 테이블 확인
ip route show
# default via 192.168.1.1 dev eth0  ← 기본 게이트웨이
# 192.168.1.0/24 dev eth0 proto kernel  ← 직접 연결 네트워크

# 2. 특정 목적지로의 라우트 확인
ip route get 8.8.8.8
# 8.8.8.8 via 192.168.1.1 dev eth0 src 192.168.1.100

# 3. 게이트웨이 연결 확인
ping -c 3 192.168.1.1

# 4. 라우팅 트레이스
traceroute 8.8.8.8

정적 라우트 추가 (임시/영구) — 특정 네트워크로 가는 경로를 수동으로 추가합니다:

로컬 터미널
# 임시 (재부팅 시 사라짐)
sudo ip route add 10.0.0.0/8 via 192.168.1.254

# 영구 (Ubuntu/Debian — netplan)
# /etc/netplan/00-installer-config.yaml
network:
  ethernets:
    eth0:
      routes:
        - to: 10.0.0.0/8
          via: 192.168.1.254

sudo netplan apply

# 영구 (RHEL — nmcli)
sudo nmcli connection modify eth0 +ipv4.routes "10.0.0.0/8 192.168.1.254"
sudo nmcli connection up eth0

다음 모듈에서는 DNS 심화 — /etc/resolv.conf, systemd-resolved, dig로 이름 해석 과정을 단계별로 추적하고 DNS 장애를 진단하는 방법을 다룹니다.

지식 확인

퀴즈 — 5문제

Q1

ip addr show 명령어 출력에서 inet 주소가 의미하는 것은?

Q2

ss -tulnp 명령어에서 -l 옵션의 의미는?

Q3

/etc/resolv.conf 파일의 역할은?

Q4

ping은 정상적으로 응답하는데 curl로 HTTP 요청이 실패할 때 가장 가능성 높은 원인은?

Q5

기본 게이트웨이(Default Gateway)의 역할은?

0 / 5 답변

🧪 실습으로 확인하기

컨테이너가 서로 못 찾는다 — Docker 네트워크 디버깅

중급

배포 직후 React 앱이 백엔드 API를 못 찾고 있다. 두 컨테이너가 같은 서버에서 돌고 있는데 왜 통신이 안 될까? docker network를 해부하고 직접 연결 복구까지 마친다.

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

이것도 배워보세요

linux중급 · 60
[Linux] ss/netstat/lsof 명령어로 네트워크 연결 장애 진단
Linux 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점