배포한 서버에 갑자기 접속이 안 됩니다. 로그를 봐도 애플리케이션은 정상입니다. "어디서부터 확인해야 하나." 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 dnsutilsip -br addr showip route showOSI 모델과 Linux 네트워킹 스택
확대
curl로 외부 API를 호출하는데 응답이 없을 때, DNS 문제인지 라우팅 문제인지 방화벽 문제인지 바로 알기 어렵습니다. 체계 없이 접근하면 방화벽만 한참 뒤지다 결국 DNS 설정 오류였다는 결론에 도달하기도 합니다. OSI 7계층 모델을 리눅스 명령어와 연결해두면, 계층별로 "여기까지는 되고 여기서부터 안 된다"는 방식으로 원인을 좁힐 수 있습니다.
네트워크 문제를 진단할 때 어느 계층의 문제인지 빠르게 파악하는 것이 핵심입니다. OSI 7계층 모델을 Linux 명령어와 매핑하면 다음과 같습니다.
확대
| 계층 | 이름 | 역할 | Linux 도구 |
|---|---|---|---|
| L7 | 응용 계층 | HTTP, DNS, SSH 프로토콜 | curl, dig, ssh |
| L4 | 전송 계층 | TCP/UDP 포트, 연결 상태 | ss, netstat, tcpdump |
| L3 | 네트워크 계층 | IP 라우팅, ICMP | ip route, ping, traceroute, mtr |
| L2 | 데이터링크 계층 | MAC 주소, 이더넷 프레임 | ip link, arp |
트러블슈팅 원칙: L2 → L3 → L4 → L7 순서로 아래에서 위로 확인합니다.
- 인터페이스가 UP인가? (L2)
- IP가 올바르고 라우팅이 되는가? (L3)
- 포트가 열려 있는가? (L4)
- 애플리케이션이 올바르게 응답하는가? (L7)
주의: ifconfig와 netstat은 net-tools 패키지에 포함된 구버전 도구입니다. 현대 Linux에서는 ip 명령어(iproute2 패키지)와 ss로 대체되었습니다. 일부 배포판에서는 기본 설치되어 있지 않습니다.
패킷 한 개가 나가고 들어오는 길 — 소켓부터 NIC까지 5단계
curl이나 앱이 데이터를 보낼 때, 그 바이트가 실제로 리눅스 커널의 어느 지점을 지나 랜선으로 나가는지는 잘 보이지 않습니다. 앞의 OSI 표가 "어느 계층 도구로 진단하나"를 알려줬다면, 여기서는 "패킷이 앱에서 NIC까지 커널 스택을 어떻게 통과하나"를 순서대로 봅니다. 이 실제 경로를 알면 "왜 여기서 안 나가지"를 추상적인 계층이 아니라 커널이 하는 구체적 일 단위로 좁힐 수 있습니다. 송신은 위(앱)에서 아래(NIC)로 내려가고, 수신은 그 역순입니다.
[앱] socket write() (curl·nginx 등이 소켓에 바이트를 씀)
│
① 소켓 송신 버퍼 → 커널 TCP/IP 스택 진입
│
② L4(TCP/UDP): 출발·목적 포트 부여 · 세그먼트 분할 · 체크섬 계산
│
③ L3(IP): 목적지 IP로 라우팅 테이블 조회 → 출구 인터페이스·게이트웨이 결정
│
④ L2: ARP로 다음 홉 MAC 확인 → 이더넷 프레임 구성
│
⑤ netfilter(방화벽) 훅 통과 → NIC 송신 큐 → 랜선으로 전송
▼
[수신 측] 역순: NIC 수신 → netfilter → L3(IP) → L4(포트로 소켓 매칭) → 소켓 수신 버퍼 → 앱 read()
각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:
| 단계 | 하는 일 | 여기서 막히면 |
|---|---|---|
| ① 소켓→스택 | 앱이 write()한 바이트가 소켓 송신 버퍼에 쌓이고 커널 스택으로 넘어감 | 목적지에 TCP 연결부터 안 되면 connect()에서 실패 — 아래 ②③④를 순서대로 의심 |
| ② L4 포트·체크섬 | 출발·목적 포트를 붙이고 세그먼트로 나눠 체크섬 계산. 수신 측은 목적 포트로 어느 소켓·프로세스인지 판별 | 목적지에서 그 포트를 LISTEN하는 프로세스가 없으면 RST → Connection refused (ss -tlnp로 확인) |
| ③ L3 라우팅 | 목적지 IP를 라우팅 테이블(ip route)과 대조해 나갈 인터페이스·게이트웨이 결정 | default 경로가 없으면 외부로 못 나감 → Network is unreachable |
| ④ L2 ARP·프레임 | 다음 홉(게이트웨이 또는 같은 서브넷 호스트)의 MAC을 ARP로 찾아 이더넷 프레임 구성 | 인터페이스가 DOWN·링크 끊김(NO-CARRIER)이면 여기서 멈춤 (ip link로 확인) |
| ⑤ 방화벽·NIC 송신 | netfilter OUTPUT/FORWARD 훅을 통과한 뒤 NIC 송신 큐에 실려 물리 전송 | 방화벽이 DROP하면 에러 없이 조용히 사라져 타임아웃 / 큐 포화면 drop 카운터 증가 |
즉 "패킷이 안 나간다"는 이 다섯 지점 중 하나에서 끊긴 것이고, 이 모듈의 진단 순서(ip link → ip addr → ip route → ss → curl)는 정확히 이 경로를 아래(L2)에서 위(L7)로 되짚는 것입니다. 수신이 안 될 때도 같은 경로의 역순이므로, "내 쪽에선 나갔는데(④까지 OK) 상대가 못 받나"는 tcpdump를 양쪽에서 같은 지점에 걸어 어느 방향·어느 단계에서 프레임이 사라졌는지로 좁힙니다.
ip 명령어 체계 — ifconfig는 deprecated
확대
검색해서 나온 네트워크 설정 가이드가 ifconfig eth0 192.168.1.10으로 시작하는데, 그 명령이 서버에 없어서 당황한 경험이 있을 것입니다. Ubuntu 18.04부터 net-tools 패키지(ifconfig, netstat, route 포함)가 기본 설치에서 빠졌습니다. 현대 Linux에서는 ip 명령어 하나로 인터페이스 조회, IP 설정, 라우팅 관리를 모두 처리합니다. 구글에서 나온 오래된 예제와 현재 시스템 명령어가 다른 이유가 이 deprecated 때문이므로, 어떻게 대응하는지 한 번 익혀두면 혼란이 사라집니다.
ip 명령어는 iproute2 패키지의 핵심으로, 네트워크 인터페이스·라우팅·터널·정책 라우팅을 통합적으로 다룹니다.
기본 구조 — ip [옵션] <오브젝트> <커맨드> 형식으로 네트워크 오브젝트를 제어합니다:
ip [옵션] <오브젝트> <커맨드>
주요 오브젝트:
| 오브젝트 | 줄임 | 역할 |
|---|---|---|
address | addr, a | IP 주소 관리 |
link | l | 네트워크 인터페이스(L2) 상태 |
route | r | 라우팅 테이블 |
neighbour | neigh | ARP 테이블 |
ifconfig vs ip 대응표 — 구버전 ifconfig 명령을 현대적인 ip 명령으로 대체하는 치환 목록입니다:
| 구버전 (ifconfig) | 신버전 (ip) |
|---|---|
ifconfig | ip addr show |
ifconfig eth0 up | ip link set eth0 up |
ifconfig eth0 192.168.1.10/24 | ip addr add 192.168.1.10/24 dev eth0 |
route -n | ip route show |
route add default gw 192.168.1.1 | ip 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
이제 실습을 진행합니다.
현재 시스템의 모든 네트워크 인터페이스와 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— 최대 전송 단위 (바이트). 점보 프레임은 9000scope 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- 읽기 순서: 네트워크 진단은 link → addr → route 순으로 계층을 올라가며 확인한다. link가 DOWN이면 addr와 route를 볼 필요 없이 먼저 인터페이스를 올려야 한다
- ip link show 에서 인터페이스 이름 뒤에 <BROADCAST,MULTICAST,UP,LOWER_UP> 가 보이면 물리적으로도 연결된 정상 상태 — UP 만 있고 LOWER_UP 없으면 케이블/가상 링크 미연결
- ip addr show 결과에서 inet 줄이 없으면 IP가 미할당 상태 — DHCP 미응답 또는 수동 설정 누락. inet6 만 있고 inet 없으면 IPv4 통신 불가
- 판단 기준 — lo 인터페이스에 127.0.0.1/8 가 없으면 시스템 문제. eth0 또는 ens3 같은 메인 인터페이스에 inet 이 없으면 서버가 네트워크에 참여하지 못한 상태
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 각 플래그의 의미입니다:
| 플래그 | 의미 |
|---|---|
-t | TCP 소켓만 |
-u | UDP 소켓만 |
-l | LISTEN 상태만 |
-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- 읽기 순서: Local Address:Port → State → Process 순으로 본다. Local Address가 0.0.0.0이면 외부 접근 가능, 127.0.0.1이면 로컬 전용
- Recv-Q 값이 0 이 아니면 앱이 소켓 데이터를 제때 읽지 못하는 것 — 수십 KB 이상이면 CPU 병목 또는 앱 행 의심
- Send-Q 값이 0 이 아니면 네트워크가 느리거나 수신측이 ACK를 안 보내는 것 — 수신측 장애 또는 대역폭 포화
- 판단 기준 — timewait 수가 수천 개 이상이면 단기간 연결이 폭발적으로 열렸다 닫힌 것. 커넥션 풀 미사용 또는 공격 징후
- 조합 해석 — LISTEN 상태인 포트가 없는데 외부에서 connection refused라면: 서비스가 죽어 있는 것. LISTEN인데 refused라면: 방화벽 문제
패킷이 어떤 경로로 나가는지 확인합니다.
# 라우팅 테이블 전체 표시
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- 읽기 순서: ip route show 에서 default 줄부터 확인한다 — default 줄이 없으면 외부 인터넷 통신 불가. 있으면 via 뒤의 게이트웨이 IP를 ping으로 확인
- metric 값이 낮을수록 우선 경로다 — metric 100 과 metric 200 이 동시에 있으면 100 쪽으로만 나간다. 두 경로가 metric 같으면 ECMP(동등 비용 다중 경로) 로드밸런싱
- mtr --report Loss% 기준 — 최종 목적지(마지막 홉)에서 5% 이상이면 실제 패킷 손실 문제. 중간 홉에서만 100% 이고 이후 홉은 0%면 해당 라우터가 ICMP를 rate-limit하는 정상 동작
- ip route get 8.8.8.8 에서 via 가 없으면 목적지가 같은 서브넷 안에 있다는 뜻 — 게이트웨이를 거치지 않고 직접 전달
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는 모든 인터페이스) |
-n | IP/포트를 숫자로 (해석 안 함, 빠름) |
-v, -vv | 상세 출력 |
-A | 패킷 내용을 ASCII로 출력 |
-s0 | 패킷 전체 캡처 (기본은 일부만) |
-w file | 파일로 저장 (Wireshark에서 분석 가능) |
-c 100 | 100개 패킷 후 종료 |
실시간 출력 예시 — 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 디코딩 등이 가능합니다.
scp server:/tmp/cap.pcap ~/Downloads/
tcpdump -i eth0 port 80 -w /tmp/cap.pcap- tcpdump 출력에서 Flags [S] → [S.] → [.] 순서가 보이면 3-way handshake 성공. [S] 이후 [R] (RST)이 오면 서버가 연결 거부한 것
- 조합 해석 — [S] 만 보이고 [S.] 가 없으면: 서버가 응답을 안 보낸다. 방화벽이 드롭하거나 서버 프로세스가 없는 것. ping은 되는데 tcpdump에서 [S]만 보인다면 L4 차단이 확실
- 패킷 수가 급격히 많으면 -c 100 옵션으로 100개만 캡처해 분석 — 무제한 캡처 시 디스크가 순식간에 찰 수 있음 (1Gbps 트래픽 = 초당 약 125MB 파일)
- sudo tcpdump -i any -n port 53 에서 A 레코드 쿼리가 많은데 응답이 없으면 DNS 서버 연결 문제
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- 읽기 순서: curl -v 출력은 * → > → < 순으로 읽는다. *는 연결 메타정보, >는 내가 보낸 요청, <는 서버 응답. 가장 먼저 < HTTP/2 XXX 의 상태코드를 확인
- 판단 기준 — curl 응답 시간 목표값: DNS 0.05s 이하, TCP connect 0.1s 이하, TTFB(First byte) 1s 이하, Total 3s 이하. 초과하는 구간이 병목
- 조합 해석 — DNS 시간이 느리고(>0.5s) TCP connect는 빠르면: resolv.conf의 nameserver가 느린 것. DNS를 8.8.8.8로 바꾸거나 로컬 캐싱 DNS 구성 고려
- curl -o /dev/null -s -w '%{http_code}' 결과가 000이면 TCP 연결 자체 실패 — 서버 다운이거나 방화벽 DROP. 200-399는 성공, 400-499는 클라이언트 문제, 500-599는 서버 오류
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- 읽기 순서: /etc/resolv.conf 에서 nameserver 줄을 먼저 확인 → search/domain 줄 → options 순으로 본다
- /etc/resolv.conf 에서 nameserver 127.0.0.53 이 보이면 systemd-resolved가 프록시하는 것 — resolvectl status 로 실제 업스트림 DNS를 확인해야 진짜 DNS 서버를 알 수 있다
- dig example.com 결과에서 ANSWER SECTION 이 없고 NXDOMAIN 이 보이면 DNS에 해당 도메인이 없다. SERVFAIL이면 DNS 서버 자체 오류 — 다른 nameserver로 테스트 필요
- 조합 해석 — ping 은 되는데 curl 로 도메인 접속 실패라면: dig 로 먼저 DNS 해석 확인. dig 성공하면 방화벽/포트 문제, dig 실패하면 DNS 문제
- 판단 기준 — dig 의 Query time이 100ms 이상이면 DNS 레이턴시가 높다. search 도메인이 3개 이상이면 쿼리당 최대 3회 시도로 DNS 레이턴시가 3배 증가할 수 있다
증상: 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
빠른 진단 체크리스트:
ss -tlnp | grep <포트>— 리스닝 중인가? 어느 주소에 바인딩?iptables -L INPUT -n | grep <포트>— OS 방화벽 차단?- 클라우드 콘솔 — 보안 그룹/방화벽 룰 차단?
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 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 환경에서 문제를 일으킬 수 있어 사용하지 마세요.
심화 — 상태 방화벽과 NAT 뒤에서 조용히 버려지는 패킷
심화: 커널 연결 추적(conntrack) — 방화벽·NAT가 기억하는 연결 테이블
ip·ss로 소켓을 봤다면, 그 아래에서 커널이 따로 관리하는 '연결 장부'가 하나 더 있다는 것을 알아야 합니다. 상태 기반 방화벽(iptables/nftables의 state ESTABLISHED,RELATED)이나 NAT를 쓰는 순간, 커널의 conntrack이 모든 흐름을 기록하기 시작하고, 이 장부가 가득 차면 애플리케이션은 멀쩡한데 새 연결만 실패하는 이상한 장애가 납니다.
- conntrack이 하는 일: 커널은 오가는 모든 연결(TCP/UDP/ICMP)을
nf_conntrack테이블에 출발지·목적지·포트·상태로 기록합니다. 이 기록이 있어야 '이 응답 패킷은 내가 허용한 연결의 일부'라고 판단하거나(상태 방화벽), NAT에서 되돌아오는 패킷을 원래 주소로 되돌릴 수 있습니다. - 테이블에는 상한이 있습니다:
net.netfilter.nf_conntrack_max가 최대 항목 수입니다. 초당 수만 연결을 받는 게이트웨이·프록시·쿠버네티스 노드에서는 이 한도에 닿기 쉽고, 넘으면 커널이 새 연결의 패킷을 그냥 버립니다. - TIME_WAIT과는 다른 문제입니다: 앞서 본 포트 고갈은 '내가 나가는 연결'의 로컬 포트가 부족한 것이고, conntrack 고갈은 '이 박스를 통과하는 모든 연결'의 추적 슬롯이 부족한 것입니다. 짧게 왔다 사라지는 UDP 흐름(DNS 질의 등)도 conntrack 항목을 잡아먹어, 소켓은 없는데 테이블만 차오르기도 합니다.
- 증상이 애매합니다: 방화벽 로그도 조용하고(정책 위반이 아니라 테이블 초과라서), 기존 연결은 잘 되는데 신규만 간헐 실패합니다. 그래서 dmesg의
nf_conntrack: table full한 줄을 못 보면 한참 헤맵니다.
핵심은 conntrack은 소켓 위가 아니라 소켓 아래, 패킷 경로에 있는 별도 자원이라는 것입니다. 상태 방화벽·NAT를 켠 박스에서 '연결이 가끔 안 된다'면 소켓뿐 아니라 이 테이블을 함께 의심해야 합니다.
상황: NAT·방화벽 역할을 겸하는 서버를 통과하는 요청 중 일부가 간헐적으로 타임아웃납니다. 그 서버에 직접 접속(SSH)은 잘 되고, ss로 봐도 소켓은 한도에 한참 못 미칩니다. ping은 통하는데 새 TCP 연결만 실패하니 원인이 잡히지 않습니다.
원인: 이 서버는 상태 기반 방화벽과 SNAT를 쓰고 있어 통과하는 모든 흐름을 conntrack 테이블에 기록합니다. 트래픽 '양'은 평소와 같아도 짧은 연결이 많아지면서 추적 항목 수가 nf_conntrack_max에 닿았고, 초과분의 새 연결 패킷이 커널에서 버려지고 있었습니다. 소켓 수(ss)로는 안 보이는데, 짧게 왔다 가는 연결과 UDP 흐름까지 테이블을 채우기 때문입니다.
진단: 소켓이 아니라 conntrack 테이블을 봅니다.
# 현재 추적 항목 수 vs 최대치
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# count가 max에 근접·도달했으면 고갈
# 커널 로그에 결정적 증거
dmesg | grep -i 'nf_conntrack: table full'
# nf_conntrack: table full, dropping packet
# 무엇이 테이블을 채우는지(상태·프로토콜별)
sudo conntrack -L | awk '{print $1,$4}' | sort | uniq -c | sort -rn | head
해결: 급하게는 sudo sysctl net.netfilter.nf_conntrack_max=262144처럼 한도를 늘려 숨통을 틔웁니다(메모리 여유를 보고 조정, 영구화는 /etc/sysctl.d/). 근본적으로는 짧은 연결을 재사용(Keep-Alive·커넥션 풀)해 흐름 수 자체를 줄이고, TIME_WAIT류 흐름의 conntrack 타임아웃을 상황에 맞게 조정합니다. 또 이 박스가 정말 상태 추적·NAT를 해야 하는지 재검토해, 불필요하면 해당 트래픽을 추적에서 빼(NOTRACK) 테이블 압력을 낮춥니다. 재발 방지로 nf_conntrack_count와 max의 비율을 모니터링해 임계 전에 알림을 겁니다.
상황: 새 서비스를 시작하려는데 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와 흔한 장애
확대
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 Solicitation | ARP 역할 — 통신 불가 |
| 136 (NA) | Neighbor Advertisement | ARP 역할 — 통신 불가 |
| 128 (Echo Request) | ping6 | ping 실패 |
# 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(Maximum Transmission Unit) 불일치는 "일부 사이트만 안 됨", "작은 패킷은 되는데 큰 파일 전송이 실패" 같은 간헐적 장애를 만듭니다.
MTU 기본값과 문제 상황 — 환경별 MTU 기본값과 불일치 시 발생하는 증상을 정리했습니다:
| 환경 | 일반적인 MTU |
|---|---|
| Ethernet | 1500 bytes |
| VPN (OpenVPN/WireGuard) | 1420~1450 bytes |
| AWS VPC | 9001 bytes (Jumbo Frame) |
| Docker bridge | 1500 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
라우팅 트러블슈팅 — 게이트웨이·정적 라우트·정책 라우팅
확대
서버를 재부팅했더니 외부 통신이 안 됩니다. 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
명령어·단축키 빠른 참조
이 모듈에서 다룬 네트워크 진단 명령을 OSI 계층 순서(L2→L7)로 모았습니다. ip·ss가 ifconfig·netstat를 대체하는 현대 표준입니다.
| 명령어/단축키 | 용도 | 자주 쓰는 예 |
|---|---|---|
ip addr show | 인터페이스·IP(inet) 확인 | ip -br addr show (한 줄 요약) |
ip link show / ip link set | L2 인터페이스 상태·UP/DOWN | ip link set eth0 up |
ip route show / get / add | 라우팅 테이블·경로·기본 게이트웨이 | ip route get 8.8.8.8 / ip route add default via <GW> |
ss -tlnp / ss -ulnp | LISTEN 중 TCP/UDP 포트와 프로세스 | ss -tlnp sport = :8080 |
ss -s / ss -ant state time-wait | 소켓 상태 요약·TIME_WAIT 폭증 확인 | ss -ant | grep TIME-WAIT | wc -l |
ping / ping -M do -s | L3 도달성 / MTU 탐색(DF 비트) | ping -M do -s 1472 8.8.8.8 |
traceroute / mtr --report | 홉별 지연·손실 추적 | mtr --report --report-cycles 10 8.8.8.8 |
tcpdump -i eth0 | 패킷 캡처(파일 저장 후 Wireshark) | sudo tcpdump -i eth0 port 80 -w /tmp/cap.pcap |
curl -v / -I / -w | L7 HTTP 단계별 디버깅·응답시간 측정 | curl -o /dev/null -s -w "%{http_code}" <url> |
dig / nslookup / getent hosts | DNS 이름 해석 확인 | dig +short example.com / dig example.com @8.8.8.8 |
nc -zv / telnet | 특정 포트 TCP 연결 테스트 | nc -zv 10.0.1.15 8080 |
lsof -i / fuser | 포트를 점유한 프로세스 찾기 | sudo lsof -i :8080 / sudo fuser 8080/tcp |
iptables -L / ufw status | 방화벽 인바운드 차단 여부 확인 | sudo iptables -L INPUT -n | grep 8080 |
관련 모듈로 더 깊이:
- ss/netstat/lsof 명령어로 네트워크 연결 장애 진단 — ss/netstat/lsof로 연결 단위 장애를 한 단계 더 파고드는 법
- DNS 확인 과정과 resolv.conf, /etc/hosts 에러 트러블슈팅 — IP가 연결돼도 이름 해석이 안 될 때의 진단 흐름
- firewalld & iptables로 특정 IP/포트 차단 및 허용 규칙 설정 — 라우팅 다음 계층인 패킷 필터링·NAT 규칙
다음 모듈에서는 DNS 심화 — /etc/resolv.conf, systemd-resolved, dig로 이름 해석 과정을 단계별로 추적하고 DNS 장애를 진단하는 방법을 다룹니다.