[Linux] 네트워킹 기초 (Networking Basics)

2026. 5. 22. 22:26·Linux
SMALL
시나리오

배포한 서버에 갑자기 접속이 안 됩니다. 로그를 봐도 애플리케이션은 정상입니다. "어디서부터 확인해야 하나." 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 라우팅, 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로 대체되었습니다. 일부 배포판에서는 기본 설치되어 있지 않습니다.

개념
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 [옵션] <오브젝트> <커맨드>

주요 오브젝트:

오브젝트 줄임 역할
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 플래그로 간결한 브리프 형식을 사용합니다:

baship -c -br addr show

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

bash# 실습 디렉토리 준비
mkdir -p /tmp/linux/part4/exam_1 && cd /tmp/linux/part4/exam_1

이제 실습을 진행합니다.

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

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

bash# 모든 인터페이스 표시
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 주소를 한 줄로 빠르게 확인합니다:

baship 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>
baship addr show
Step 2. 리스닝 포트와 프로세스 확인

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

bash# 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로 소켓 상태별 총 개수를 빠르게 파악합니다:

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

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

bash# 라우팅 테이블 전체 표시
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으로 특정 목적지까지 실제로 어떤 경로를 쓰는지 확인합니다:

baship 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 — 목적지까지 각 홉을 추적해 어디서 지연이 발생하는지 파악합니다:

bash# 경로 추적 (각 홉의 지연)
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하는 것일 수 있습니다.

baship route show
Step 4. 패킷 캡처 기초

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

bash# 기본: 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 디코딩 등이 가능합니다.

bashscp server:/tmp/cap.pcap ~/Downloads/
bashtcpdump -i eth0 port 80 -w /tmp/cap.pcap
Step 5. curl로 HTTP 디버깅

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

bash# 상세 출력 (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        ← 전체 소요 시간
bashcurl -v https://example.com
Step 6. DNS 설정 파일 확인

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

bash# DNS 서버 설정 확인
cat /etc/resolv.conf
# Generated by systemd-resolved
nameserver 127.0.0.53
options edns0 trust-ad
search example.internal
bash# 로컬 호스트 오버라이드 확인
cat /etc/hosts
127.0.0.1   localhost
::1         localhost ip6-localhost
10.0.1.100  db.internal postgres.internal
bash# 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` — 이름 해석 우선순위 결정:

bashgrep hosts /etc/nsswitch.conf
# hosts: files dns myhostname
# files(=/etc/hosts) → dns 순서

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

  • 컨테이너 내 /etc/resolv.conf는 호스트와 다름 (Docker가 자동 생성)
  • K8s Pod은 CoreDNS를 nameserver로 사용
  • search 도메인이 길면 DNS 쿼리가 여러 번 시도됨 (레이턴시 증가)
bashcat /etc/resolv.conf && cat /etc/hosts
트러블슈팅
포트는 열려있는데 외부에서 접속이 안 됨

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

원인 분류 및 진단:

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

bashss -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 방화벽 확인 — 방화벽 규칙에서 해당 포트가 허용됐는지 확인합니다:

bash# 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로 포트 허용 — 방화벽에서 해당 포트의 인바운드 트래픽을 허용합니다:

bash# 포트 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으로 클라이언트에서 직접 포트 연결을 테스트합니다:

bash# 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 연결 가능?
트러블슈팅
Temporary failure in name resolution (DNS 조회 실패)

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

단계별 진단:

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

bash# 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에 올바른 네임서버가 설정됐는지 확인합니다:

bashcat /etc/resolv.conf

# nameserver가 비어있거나 없는 경우
# nameserver가 응답 없는 서버를 가리키는 경우
bash# 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 상태를 확인합니다:

bashsystemctl status systemd-resolved

# 재시작
sudo systemctl restart systemd-resolved

# stub resolver 상태
resolvectl status

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

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

bash# 컨테이너 내부에서 확인
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
트러블슈팅
TIME_WAIT 소켓 과다로 인한 포트 고갈 (port exhaustion)

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

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

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

bash# 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 포트 범위 확인 — 사용 가능한 클라이언트 포트 범위가 좁으면 포트 고갈이 빨리 옵니다:

bashcat /proc/sys/net/ipv4/ip_local_port_range
# 32768   60999  ← 사용 가능 포트: 28231개

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

bash# 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 오류가 발생합니다.

bash$ 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를 찾습니다:

bash# 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로 프로세스 이름과 실행 경로를 확인합니다:

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

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

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

bash# 정상 종료 시도
kill -15 4521

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

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

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

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

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

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

bashsudo systemctl start my-service
sudo systemctl status my-service
ss -tlnp sport = :8081

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

bashif 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) 연결 가능 여부를 먼저 확인합니다:

bash# 게이트웨이 응답 확인
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로 어느 홉에서 패킷이 멈추는지 확인합니다:

bash# 목적지까지 각 홉 지연 측정
mtr --report --report-cycles 20 10.0.2.50

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

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

bash# 연결 상태 요약
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로 패킷이 실제로 도달하는지 확인합니다:

bash# 백엔드 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과 서비스 로그로 앱 레벨 문제를 확인합니다:

bash# 응답 시간 분해
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 주소의 의미를 파악합니다:

bash# 현재 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 실패
bash# 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 경로로 우회 접근이 가능한 취약점이 생깁니다:

bash# 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
Ethernet 1500 bytes
VPN (OpenVPN/WireGuard) 1420~1450 bytes
AWS VPC 9001 bytes (Jumbo Frame)
Docker bridge 1500 bytes (호스트와 동일)

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

bash# 현재 인터페이스 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를 낮게 설정합니다:

bash# 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가 두 개인데 두 인터페이스로 들어온 트래픽이 뒤섞이면서 예상치 못한 경로로 나가는 비대칭 라우팅 문제도 있습니다. 라우팅 문제는 "어디까지는 되고 어디서부터 안 되는지"를 단계별로 좁혀가는 게 핵심입니다.

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

bash# 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

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

bash# 임시 (재부팅 시 사라짐)
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 장애를 진단하는 방법을 다룹니다.

반응형
LIST

'Linux' 카테고리의 다른 글

[Linux] 포트 & 네트워크 진단  (0) 2026.05.22
[Linux] DNS & 이름 해석 트러블슈팅  (0) 2026.05.22
[Linux] 디스크 트러블슈팅  (0) 2026.05.22
[Linux] LVM & 볼륨 관리  (0) 2026.05.22
[Linux] 디스크와 스토리지 관리  (0) 2026.05.22
'Linux' 카테고리의 다른 글
  • [Linux] 포트 & 네트워크 진단
  • [Linux] DNS & 이름 해석 트러블슈팅
  • [Linux] 디스크 트러블슈팅
  • [Linux] LVM & 볼륨 관리
cumo
cumo
  • cumo
    이것저것
    cumo
    • 분류 전체보기 (147) N
      • 이것저것 (1)
      • 보안뉴스 (15)
      • Project (12)
      • wargame (1)
      • Cloud (25)
      • DevOps (21)
      • Linux (43)
      • 네트워크 (23)
      • AWS Developer BootCamp (1)
      • WEB&WAS (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 도구모음 사이트
    • 참고 기술 블로그
  • 공지사항

  • 인기 글

  • 태그

    ubuntu
    텍스트편집기
    논리볼륨
    눅스네트워킹
    데몬
    인프라
    vim
    포트진단
    부팅서비스
    디렉토리구조
    Volume Group
    Linux
    스토리지확장
    bash입문
    리눅스
    터미널멀티플렉서
    서버관리
    nano
    vi
    서버편집
  • 최근 댓글

  • 최근 글

  • 반응형
  • hELLO· Designed By정상우.v4.10.3
cumo
[Linux] 네트워킹 기초 (Networking Basics)
상단으로

티스토리툴바