📟 장애 알림 — 2026-06-10 09:17 심각도: HIGH | 서비스: api.company.com | 담당자: 인프라팀 배정됨
"개발팀 전원 SSH 불가. 웹서비스(80/443)는 정상. 어제 밤 배포 이후 발생. 방화벽 건드린 사람 없다고 하지만 확인 필요. 배포 스크립트에 iptables 관련 명령 있었다는 제보."
당신은 지금 온콜 인프라 엔지니어입니다. 개발팀 슬랙에 "서버에 아무도 못 들어감"이라는 메시지가 쏟아지고 있습니다. 새벽 배포 후 발생했고, 웹서비스는 살아있으니 서버 자체가 죽은 건 아닙니다.
지금 해야 할 일: 현재 방화벽 상태를 있는 그대로 파악하고, SSH 차단 원인을 찾아야 합니다. 읽기 전에 수정하지 않습니다.
절대 하면 안 되는 일: iptables -F로 전체 초기화. 초기화하면 HTTP/HTTPS 규칙도 사라져 웹서비스까지 장애가 됩니다. "방화벽을 일단 다 열고 나중에 닫자"는 접근도 보안 사고로 이어집니다.
이 모듈을 마치면 이 장애를 5분 안에 혼자 진단하고 복구할 수 있습니다.
- 1netfilter 프레임워크와 훅 포인트 — 패킷이 커널을 통과하는 정확한 경로를 설명할 수 있다
- 2iptables 4개 테이블(filter/nat/mangle/raw)의 역할 차이와 오늘 장애와 관련된 테이블이 어느 것인지 판단할 수 있다
- 3iptables -L INPUT -n -v --line-numbers 출력을 한 줄씩 정확하게 해석할 수 있다
- 4policy DROP 환경에서 규칙 순서가 SSH 차단에 어떤 영향을 주는지 추적할 수 있다
- 5ESTABLISHED/RELATED 상태 규칙이 없을 때 어떤 장애가 발생하는지 설명할 수 있다
- 6SSH 허용 규칙을 안전한 위치(-I vs -A)에 추가하고, 실수 시 iptables -D로 롤백할 수 있다
- 7DROP과 REJECT의 차이를 알고 상황에 맞게 선택할 수 있다
- 8IP 기반 접근 제어로 특정 IP에서만 SSH를 허용하는 규칙을 작성할 수 있다
- 9iptables LOG 타깃으로 차단된 패킷을 추적하고 /var/log/kern.log에서 확인할 수 있다
- 10iptables-save/restore로 규칙을 영구 저장하고 부팅 후 자동 복원을 설정할 수 있다
- 11iptables-legacy vs nftables 혼용 환경에서 발생하는 에러를 진단할 수 있다
Part 1 — netfilter가 패킷을 처리하는 방식: 커널 레벨에서 일어나는 일
iptables가 어떻게 동작하는지 이해하려면 iptables 이전에 있는 netfilter를 먼저 알아야 합니다. iptables는 도구이고, netfilter는 그 도구가 올라타는 커널 프레임워크입니다.
netfilter는 리눅스 커널 2.4(2001년)부터 내장된 패킷 처리 프레임워크입니다. 네트워크 스택 곳곳에 훅(hook) 포인트 5개를 박아두고, 패킷이 그 지점을 통과할 때 등록된 함수들을 실행합니다. iptables는 그 훅에 "이 패킷을 허용할지 차단할지" 판단하는 함수를 등록하는 인터페이스입니다.
훅 포인트 5개는 다음과 같습니다.
NF_INET_PRE_ROUTING → PREROUTING 체인에 대응
NF_INET_LOCAL_IN → INPUT 체인에 대응
NF_INET_FORWARD → FORWARD 체인에 대응
NF_INET_LOCAL_OUT → OUTPUT 체인에 대응
NF_INET_POST_ROUTING → POSTROUTING 체인에 대응
패킷이 서버에 도착해서 나가기까지의 전체 경로입니다.
확대
이 경로에서 오늘 장애와 관련된 지점은 딱 하나입니다. NF_INET_LOCAL_IN, 즉 INPUT 체인입니다. 외부 개발팀이 SSH로 접속하면 그 패킷은 NIC → PREROUTING → 라우팅 결정(이 서버가 목적지) → INPUT 체인 순서로 통과합니다. INPUT 체인에서 ACCEPT되어야 sshd 프로세스까지 전달됩니다.
테이블이 4개인 이유 — 역할이 완전히 다르기 때문입니다
같은 훅 포인트를 여러 테이블이 공유합니다. 예를 들어 PREROUTING 훅에는 raw, mangle, nat 테이블이 모두 걸려있습니다. 우선순위 순서로 raw가 가장 먼저, nat이 가장 나중에 실행됩니다. 왜 하나로 합치지 않았을까요? 패킷에 해야 할 작업의 성격이 완전히 다르기 때문입니다.
확대
filter 테이블 — 허용/차단 판단
오늘 다루는 테이블입니다. "이 패킷을 통과시킬 것인가, 버릴 것인가"만 판단합니다. INPUT, FORWARD, OUTPUT 체인이 있습니다. iptables -L의 기본값이 이 테이블입니다. -t filter를 생략해도 filter 테이블이 선택됩니다.
iptables -L INPUT -n # -t filter 생략, 기본값으로 filter 선택
iptables -t filter -L INPUT -n # 명시적으로 filter 테이블 지정, 동일한 결과
nat 테이블 — 주소 변환
패킷의 소스나 목적지 IP/포트를 바꿉니다. 포트 포워딩이 이 테이블에서 일어납니다.
# 외부 포트 80을 내부 서버 8080으로 포워딩 (DNAT)
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 192.168.1.10:8080
# 내부 서버들이 외부로 나갈 때 공인 IP로 위장 (MASQUERADE/SNAT)
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
mangle 테이블 — 패킷 헤더 수정
TTL 조작, ToS 마킹, QoS 우선순위 설정 등 패킷 자체를 변형합니다. 일반 서버에서는 거의 쓸 일이 없습니다.
# 나가는 패킷의 TTL을 64로 고정 (망 사업자 검출 우회 등에 사용)
iptables -t mangle -A OUTPUT -j TTL --ttl-set 64
raw 테이블 — conntrack 제외
특정 패킷을 연결 추적(conntrack)에서 제외합니다. conntrack은 모든 연결을 추적하는 비용이 있습니다. 초고속 트래픽 처리 서버(DDoS 방어 장비 등)에서 conntrack 오버헤드를 줄이는 용도입니다.
# UDP 53번 포트(DNS) 트래픽을 conntrack에서 제외
iptables -t raw -A PREROUTING -p udp --dport 53 -j NOTRACK
오늘 장애와 관련된 것은 전부 filter 테이블의 INPUT 체인입니다. 다른 테이블은 이번 장애 원인이 아닙니다.
INPUT / OUTPUT / FORWARD — 패킷 방향으로 구분합니다
세 체인이 헷갈리는 이유는 이름이 직관적이지 않기 때문입니다. 기준은 하나입니다. 패킷의 출발지와 목적지 중 이 서버 자신이 있는가, 없는가.
INPUT 체인
외부에서 이 서버로 들어오는 트래픽이 통과하는 체인입니다. 정확히는 "이 서버의 프로세스가 수신하는 패킷"입니다. SSH 클라이언트가 보낸 SYN 패킷, HTTP 요청, 데이터베이스 쿼리 — 이 서버에 접속하려는 모든 패킷이 INPUT을 통과합니다. 오늘 장애의 핵심입니다.
오해하기 쉬운 것: "서버에서 나가는 응답 패킷도 INPUT인가?" — 아닙니다. 응답 패킷은 OUTPUT 체인을 통과합니다. 하지만 그 응답에 대한 클라이언트의 ACK는 다시 INPUT을 통과합니다.
OUTPUT 체인
이 서버의 프로세스가 시작하는 아웃바운드 트래픽이 통과하는 체인입니다. curl, apt install, git clone, 데이터베이스 클라이언트가 외부 DB에 접속하는 것 — 이 서버가 먼저 연결을 시작하는 모든 것이 OUTPUT을 통과합니다.
# OUTPUT에 DROP이 있으면 서버에서 외부로 어떤 요청도 보낼 수 없습니다
iptables -A OUTPUT -p tcp --dport 443 -j DROP
# 이 규칙이 있으면 서버에서 https로 접근하는 apt, curl, pip 등이 전부 실패합니다
FORWARD 체인
이 서버를 거쳐 다른 목적지로 전달되는 트래픽이 통과하는 체인입니다. 이 서버가 출발지도 목적지도 아닐 때 사용됩니다. 라우터나 게이트웨이 역할, 또는 Docker가 컨테이너로 트래픽을 전달할 때 FORWARD 체인이 관여합니다.
# Docker를 사용하면 FORWARD 체인에 자동으로 규칙이 생깁니다
iptables -L FORWARD -n
# Docker 컨테이너로 가는 트래픽 허용 규칙이 자동 생성된 것을 볼 수 있습니다
일반 웹 서버로만 사용하는 경우 FORWARD는 기본 DROP으로 두면 됩니다.
정리
| 시나리오 | 관련 체인 |
|---|---|
| SSH 클라이언트 → 서버 접속 | INPUT |
| 서버 → apt install, curl | OUTPUT |
| 서버 → DB 서버 쿼리 (클라이언트 역할) | OUTPUT (요청) + INPUT (응답) |
| 외부 → 서버 → 내부 네트워크 (라우터) | FORWARD |
| Docker 외부 포트 → 컨테이너 | FORWARD + PREROUTING(nat) |
오늘 장애: 외부 개발팀 → 서버 SSH(22) → INPUT 체인에서 차단.
확대
conntrack — iptables를 stateful로 만드는 핵심
단순한 방화벽은 패킷 하나하나를 독립적으로 봅니다. "포트 22 TCP면 허용, 아니면 차단"처럼 패킷의 현재 정보만 판단합니다. 이것을 stateless 방화벽이라고 합니다.
iptables는 여기서 더 나아가 연결의 **상태(state)**를 추적합니다. 이것이 stateful 방화벽입니다. 커널의 nf_conntrack 모듈이 현재 활성화된 모든 연결을 메모리 테이블에 기록합니다.
현재 추적 중인 연결을 직접 볼 수 있습니다.
cat /proc/net/nf_conntrack
# 또는
conntrack -L 2>/dev/null || cat /proc/net/ip_conntrack
tcp 6 431999 ESTABLISHED src=203.0.113.5 dst=10.0.0.1 sport=52341 dport=22
src=10.0.0.1 dst=203.0.113.5 sport=22 dport=52341 [ASSURED] mark=0 use=1
이 출력은 203.0.113.5:52341 → 서버:22 의 SSH 연결이 ESTABLISHED 상태임을 보여줍니다. conntrack이 이 정보를 가지고 있기 때문에 응답 패킷이 INPUT 체인에 도달했을 때 "이것은 이미 수립된 연결의 응답"임을 알 수 있습니다.
각 패킷에 붙는 conntrack 상태입니다.
NEW — 새 연결을 시작하는 첫 번째 패킷. TCP라면 SYN 패킷이 여기 해당합니다. 서버에 처음 접속을 시도하는 패킷입니다.
ESTABLISHED — 이미 수립된 연결의 패킷. SYN-ACK 이후의 모든 패킷입니다. 서버가 curl로 외부에 요청을 보내면, 그 응답 패킷은 ESTABLISHED 상태로 INPUT 체인에 도달합니다.
RELATED — 기존 연결과 관련된 새 연결의 패킷. FTP 활성 모드의 데이터 채널, ICMP 에러 응답 등이 해당합니다.
INVALID — conntrack이 어떤 연결에도 속하지 않는다고 판단한 패킷. 손상된 패킷, 보안 스캔 패킷, 연결 추적 테이블에서 만료된 연결의 패킷 등입니다.
확대
실제 서버 규칙에서 자주 보이는 패턴입니다.
ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
이 규칙 하나가 없으면 어떻게 될까요? policy DROP 환경에서 apt install nginx를 실행해봅니다.
- 서버 → 외부 apt 서버: SYN 패킷 (OUTPUT 체인 통과, 허용)
- 외부 apt 서버 → 서버: SYN-ACK 패킷 (INPUT 체인 도달)
- INPUT 체인에 ESTABLISHED 허용 규칙 없음 → policy DROP → SYN-ACK 차단
- apt가 응답을 받지 못해 타임아웃
결과: curl google.com, apt update, git clone, pip install 전부 실패합니다. 서버가 외부와 통신하는 거의 모든 것이 동작하지 않습니다.
# ESTABLISHED,RELATED 규칙이 있는지 확인
iptables -L INPUT -n | grep -i established
# 아무것도 안 나오면 이 규칙이 없는 것 — 서버에서 외부로 요청하는 모든 것이 응답을 못 받음
INVALID 패킷 처리
INVALID 패킷은 명시적으로 DROP하는 것이 권장됩니다.
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
이 패킷들을 ACCEPT하면 손상된 패킷이나 보안 스캔 트래픽이 서비스에 영향을 줄 수 있습니다.
conntrack 테이블 포화 문제
트래픽이 많은 서버에서 conntrack 테이블이 가득 차면 새 연결이 전부 실패합니다.
# 현재 conntrack 사용량 확인
cat /proc/sys/net/netfilter/nf_conntrack_count # 현재 추적 중인 연결 수
cat /proc/sys/net/netfilter/nf_conntrack_max # 최대 허용 연결 수
nf_conntrack: table full, dropping packet
이 로그가 /var/log/kern.log에 나오면 conntrack 테이블이 포화 상태입니다. 해결은 nf_conntrack_max 값을 늘리거나, 불필요한 연결 추적을 raw 테이블에서 제외하는 것입니다.
policy ACCEPT vs policy DROP — 보안 철학의 차이
체인의 기본 정책(policy)은 "어떤 규칙에도 매칭되지 않은 패킷을 최종적으로 어떻게 처리하는가"를 결정합니다.
policy ACCEPT — 블랙리스트 방식
기본이 허용이고, 명시적으로 막은 것만 차단합니다. 새 서비스를 올릴 때 방화벽 규칙을 추가할 필요가 없습니다. 하지만 누군가 실수로 백도어 포트를 열거나, 예상치 못한 서비스가 포트를 열어도 기본적으로 허용됩니다. 규칙을 잘못 추가해도 서비스가 중단되지 않습니다. 개발 환경에서 편의를 위해 쓰기도 하지만 운영 서버에는 권장하지 않습니다.
policy DROP — 화이트리스트 방식
기본이 차단이고, 명시적으로 허용한 것만 통과합니다. 새 서비스를 올리면 반드시 방화벽 규칙도 함께 추가해야 합니다. 실수로 서비스를 추가해도 방화벽이 자동으로 차단합니다. 규칙 관리가 엄격해야 하지만 보안 수준이 높습니다. 운영 서버의 표준 설정입니다.
중요한 것: policy DROP은 -j DROP 규칙이 아닙니다. -P INPUT DROP 명령으로 설정하는 체인의 기본 정책입니다. 두 가지는 동작 방식이 다릅니다.
# policy DROP 설정 (아무 규칙에도 매칭 안 된 패킷의 기본 처리)
iptables -P INPUT DROP
# 명시적 DROP 규칙 (규칙 평가 중 이 규칙에 매칭된 패킷 차단)
iptables -A INPUT -j DROP
오늘 장애 서버는 policy DROP입니다. policy DROP 환경에서 SSH 허용 규칙이 없으면 SSH는 무조건 차단됩니다.
규칙 순서가 생사를 가르는 이유 — 첫 매칭 즉시 종료
iptables의 규칙 평가 알고리즘은 단순하지만 치명적인 함정이 있습니다.
for 규칙 in 체인:
if 패킷이 규칙의 조건에 매칭됨:
규칙의 타깃(ACCEPT/DROP/REJECT/LOG/...) 실행
평가 종료 ← 이후 규칙은 절대 보지 않음
어떤 규칙에도 매칭 안 됨 → policy 적용
이것 때문에 같은 규칙이라도 위치에 따라 결과가 완전히 달라집니다.
# 케이스 A — SSH 됨
1: ACCEPT tcp dpt:22 ← SSH 패킷이 여기서 ACCEPT. 평가 종료.
2: DROP all ← 이 규칙은 SSH 패킷에게 평가되지 않음
# 케이스 B — SSH 안 됨 (오늘 장애 상황)
1: DROP all ← SSH 패킷이 여기서 DROP. 평가 종료.
2: ACCEPT tcp dpt:22 ← 영원히 평가되지 않음
확대
케이스 B에서 pkts를 확인해보면:
1 9201 1.2M DROP all -- * * 0.0.0.0/0 0.0.0.0/0
2 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
2번 규칙의 pkts가 0입니다. 한 번도 평가된 적이 없다는 뜻입니다. 이 패턴이 보이면 규칙 순서 문제입니다.
-I INPUT 1로 맨 앞에 삽입하는 이유가 바로 이것입니다. -A(Append)로 끝에 추가하면 이미 앞에 있는 DROP 규칙에 막힐 수 있습니다. -I INPUT 1은 어떤 규칙이 있든 맨 앞에 오므로 안전합니다.
패킷 한 개가 iptables를 지나는 순서 — 수신부터 최종 판정까지
지금까지 훅·테이블·체인·conntrack·규칙 순서를 따로 봤습니다. 하지만 실제로 SSH 패킷 하나가 서버에 도착하면 이 조각들이 정해진 순서로 한 번에 작동합니다. 이 순서를 통째로 따라가면 "SSH가 왜 막혔나"를 어느 단계에서 끊겼는지로 좁힐 수 있습니다.
[외부에서 SSH SYN 도착] dst = 이 서버:22
│
① NIC 수신 → PREROUTING (nat 테이블)
│ DNAT·리다이렉트가 있으면 여기서 목적지가 바뀜
│
② 라우팅 판단: 목적지가 "이 서버 자신"인가?
│ ├─ 예(로컬 프로세스행) → INPUT 체인으로
│ └─ 아니오(경유·전달) → FORWARD 체인으로
│
③ INPUT 체인 규칙을 1번부터 순차 평가
│ 매 규칙마다 "이 패킷이 조건에 맞나?" 대조
│
④ 첫 매칭 규칙의 타깃 실행 → 즉시 평가 종료
│ ACCEPT(통과) / DROP(무응답 폐기) / REJECT(거부 응답)
│
⑤ 어떤 규칙에도 안 맞으면 → 체인 기본 정책(policy)
│ policy DROP이면 조용히 폐기
▼
[④에서 ACCEPT된 경우만] sshd 프로세스로 전달
각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:
| 단계 | 하는 일 | 여기서 막히면 |
|---|---|---|
| ① PREROUTING(nat) | NIC로 받은 패킷이 가장 먼저 nat 테이블 PREROUTING을 지난다. 포트포워딩(DNAT)·리다이렉트가 있으면 목적지 IP·포트가 여기서 바뀐다 | 일반 서버엔 규칙이 없어 그대로 통과 / Docker·포워딩 서버는 목적지가 바뀌어 예상과 다른 체인(FORWARD)을 타기도 함 |
| ② 라우팅 판단 | 커널이 목적지 IP를 보고 "이 서버 자신 행"인지 "경유 행"인지 결정. 로컬행이면 INPUT, 경유면 FORWARD로 분기 | 목적지·라우팅 자체가 틀리면 INPUT까지 오지도 못함(방화벽이 아닌 라우팅 문제) |
| ③ INPUT 순차 평가 | 로컬행 패킷은 filter 테이블 INPUT 체인을 1번 규칙부터 순서대로 검사 | 앞 번호에 DROP all이 있으면 뒤의 dpt:22 ACCEPT까지 도달 못 함 → 그 ACCEPT의 pkts가 0(순서 함정) |
| ④ 첫 매칭 타깃 | 조건에 맞는 첫 규칙의 타깃을 실행하고 평가를 즉시 끝냄 | DROP이면 무응답 폐기 → 클라이언트 Connection timed out(수십 초) / REJECT면 → 즉시 Connection refused |
| ⑤ 기본 정책 | 어떤 규칙에도 안 맞은 패킷을 체인 policy로 최종 처리 | policy DROP인데 dpt:22 ACCEPT 규칙이 아예 없음 → SSH 무조건 차단(오늘 장애의 전형) |
즉 "SSH 접속 성공"은 ②에서 INPUT으로 분기되고 ④에서 ACCEPT를 만난다는 뜻입니다. 접속이 안 될 때는 iptables -L INPUT -n -v --line-numbers로 policy와 규칙 순서를 읽어 이 중 어느 단계에서 끊겼는지 좁힙니다 — pkts가 0인 ACCEPT 규칙이 보이면 ③ 순서 함정, dpt:22 규칙 자체가 없으면 ⑤ 정책 차단입니다. 증상까지 갈리는데, 무응답 timeout이면 DROP, 즉시 refused면 REJECT입니다.
Part 2 — iptables 출력 완전 해석: 한 글자도 빠뜨리지 않기
iptables를 다룰 수 있는 사람과 못 다루는 사람의 차이는 이 출력을 얼마나 정확하게 읽을 수 있는가입니다. 출력을 완전히 이해하면 진단이 자동으로 됩니다.
iptables -L INPUT -n -v --line-numbers
Chain INPUT (policy DROP)
num pkts bytes target prot opt in out source destination
1 4523 320K ACCEPT all -- lo any 0.0.0.0/0 0.0.0.0/0
2 1205 86K ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
3 120 8640 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
4 89 6408 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
Chain INPUT (policy DROP)
첫 줄 전체가 중요한 정보입니다. 체인 이름이 INPUT이고 기본 정책이 DROP입니다. 이것 하나로 오늘 장애의 95%가 설명됩니다. policy DROP이면 SSH 규칙이 없을 때 차단이 확정입니다.
num 컬럼 — 규칙 평가 순서이자 조작 핸들
1번부터 순서대로 평가합니다. 이 번호는 규칙을 삭제하거나 특정 위치에 삽입할 때 사용합니다.
iptables -D INPUT 3 # 3번 규칙 삭제
iptables -I INPUT 2 ... # 2번 위치에 새 규칙 삽입 (기존 2번은 3번으로 밀림)
iptables -R INPUT 3 ... # 3번 규칙을 새 규칙으로 교체
pkts / bytes 컬럼 — 이 규칙에 실제로 매칭된 트래픽
이 컬럼이 진단에서 핵심 역할을 합니다.
- pkts가 0이면 이 규칙은 한 번도 매칭된 적이 없습니다. 의도한 트래픽이 여기 도달하지 못하고 있거나, 앞 규칙에서 이미 처리된 것입니다.
- pkts가 계속 증가하는 DROP 규칙이 있다면 실제로 차단되고 있는 트래픽이 있다는 의미입니다.
# 카운터 초기화 (서버 재시작 없이 통계를 리셋할 때)
iptables -Z INPUT
# 특정 규칙만 초기화
iptables -Z INPUT 3
# 실시간으로 카운터 변화 관찰 (watch + iptables -L)
watch -n 1 'iptables -L INPUT -n -v --line-numbers'
target 컬럼 — 매칭 시 수행하는 동작
ACCEPT— 패킷을 다음 처리 단계로 통과시킵니다.DROP— 패킷을 조용히 버립니다. 발신자에게 아무 응답도 하지 않습니다. 발신자는 타임아웃(보통 수십 초)을 기다립니다.REJECT— 패킷을 버리되 발신자에게 거부 메시지(ICMP Port Unreachable 등)를 보냅니다. 발신자가 즉시 실패를 인지합니다.LOG— 패킷을 차단하지 않고 /var/log/kern.log에 기록합니다. 보통 LOG 다음에 DROP 규칙이 따라옵니다.RETURN— 현재 체인에서 빠져나가 호출한 체인으로 돌아갑니다. 서브체인에서 사용됩니다.
prot 컬럼 — 프로토콜
tcp— TCP 프로토콜만 매칭udp— UDP 프로토콜만 매칭icmp— ICMP(ping 등)만 매칭all— 모든 프로토콜 매칭
in / out 컬럼 — 인터페이스
lo— 루프백 인터페이스 (127.0.0.1, ::1)eth0— 특정 물리 인터페이스*— 모든 인터페이스
source / destination 컬럼 — 소스/목적지 IP
0.0.0.0/0— 모든 IP (any)203.0.113.0/24— 특정 CIDR 대역만 매칭203.0.113.5/32— 단일 IP만 매칭
오른쪽 끝 조건 컬럼
기본 컬럼 이후 추가 매칭 조건이 붙습니다.
tcp dpt:22 → 목적지 포트 22
tcp spt:1024:65535 → 소스 포트 1024~65535 범위
state RELATED,ESTABLISHED → conntrack 상태 조건
multiport dports 80,443 → 여러 포트 동시 매칭
limit: avg 5/min burst 10 → 속도 제한 (초당/분당 N개)
이제 앞의 출력을 문장으로 완전히 읽어봅니다.
1번 규칙: "루프백 인터페이스(lo)에서 들어오는 모든 프로토콜의 패킷은 허용한다." 서버 내부에서 127.0.0.1로 통신하는 프로세스끼리 막히지 않게 하는 필수 규칙입니다. 이 규칙이 없으면 PostgreSQL, Redis 등 로컬 서비스 통신이 전부 차단됩니다.
2번 규칙: "모든 인터페이스에서 들어오는 TCP 패킷 중 conntrack 상태가 RELATED 또는 ESTABLISHED인 것은 허용한다." 이 서버에서 먼저 시작한 연결의 응답 패킷을 받기 위한 규칙입니다. 이것이 없으면 apt, curl, git 등이 응답을 못 받아 모두 실패합니다.
3번 규칙: "모든 인터페이스에서 들어오는 TCP 패킷 중 목적지 포트가 80인 것은 허용한다." HTTP 트래픽 허용입니다. pkts=120은 지금까지 HTTP 요청이 120번 들어왔다는 의미입니다.
4번 규칙: "목적지 포트 443인 TCP 패킷은 허용한다." HTTPS 트래픽 허용입니다.
결론: dpt:22(SSH)를 허용하는 규칙이 없습니다. policy DROP이므로 SSH 패킷은 어떤 규칙에도 매칭되지 않아 자동으로 차단됩니다. 원인 확정입니다.
Part 3 — 실전 진단 및 복구
장애 문의를 받았을 때 가장 먼저 해야 할 일은 현재 방화벽 상태를 수정 없이 파악하는 것입니다.
각 옵션의 의미입니다.
-L INPUT: INPUT 체인만 출력합니다. -L만 쓰면 INPUT/FORWARD/OUTPUT 전부 나와 읽기 불편합니다. 인바운드 트래픽 문제이므로 INPUT만 봅니다.
-n: IP 주소와 포트를 숫자 그대로 표시합니다. 이 옵션이 없으면 iptables가 각 IP에 대해 DNS 역조회를 시도해 출력이 느려집니다. 규칙이 많으면 수십 초가 걸릴 수 있습니다. 항상 붙이는 습관을 들이세요.
-v: verbose 모드. 패킷 카운터(pkts), 바이트 카운터(bytes), 인터페이스(in/out) 정보를 포함합니다. 이 정보가 없으면 각 규칙이 실제로 동작하고 있는지 파악하기 어렵습니다.
--line-numbers: 규칙 번호를 표시합니다. 규칙을 삭제하거나 특정 위치에 삽입할 때 이 번호가 필요합니다. 이것도 항상 붙이는 습관을 들이세요.
iptables -L INPUT -n -v --line-numbers- 첫 줄 "Chain INPUT (policy ___)" — policy가 ACCEPT인지 DROP인지 먼저 확인한다. DROP이면 화이트리스트 환경이므로 SSH 허용 규칙이 반드시 명시적으로 있어야 한다.
- num 컬럼 — 번호가 낮을수록 먼저 평가된다. DROP all 규칙이 있다면 그 번호가 SSH ACCEPT 규칙 번호보다 낮은지 확인한다.
- pkts 컬럼 — 0인 규칙이 있다면 의도한 트래픽이 그 규칙까지 도달하지 못하고 있는 것이다.
- dpt:22를 허용하는 ACCEPT 규칙이 있는지 확인한다. 없으면 SSH 차단 원인 확정이다.
- RELATED,ESTABLISHED 규칙이 있는지 확인한다. 없으면 이 서버에서 외부로 나가는 요청의 응답을 못 받는다.
Chain INPUT (policy DROP)
num pkts bytes target prot opt in out source destination
1 4523 320K ACCEPT all -- lo any 0.0.0.0/0 0.0.0.0/0
2 1205 86K ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
3 120 8640 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
4 89 6408 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
policy DROP, dpt:22 없음 — 원인 확정입니다. 80, 443은 있으니 웹서비스가 정상인 이유도 설명됩니다.
전체 규칙이 수십 개일 때 SSH 관련 줄만 빠르게 확인합니다. 결과가 비어 있으면 SSH 허용 규칙이 전혀 없다는 뜻입니다.
grep 22가 아닌 grep dpt:22를 써야 하는 이유: 22라는 숫자는 pkts 카운터, bytes, IP 주소 끝자리 등 다른 컬럼에서도 나올 수 있습니다. dpt:22는 목적지 포트 22를 의미하는 것으로, 오탐 없이 SSH 규칙만 찾습니다.
또한 sshd_config에서 포트를 변경한 서버(예: Port 2222)라면 dpt:2222를 찾아야 합니다. 장애 확인 전에 sshd가 실제로 어떤 포트를 리스닝하는지 확인하는 것이 좋습니다.
# sshd가 리스닝하는 실제 포트 확인
ss -tlnp | grep sshd
# 또는
netstat -tlnp | grep sshd
iptables -L INPUT -n -v | grep -E 'dpt:22|ssh|SSH'- 결과가 비어 있으면 SSH(22번 포트) 허용 규칙이 없는 것이다. policy DROP이라면 이것이 SSH 차단 원인.
- 결과에 ACCEPT가 있어도 그 규칙보다 앞 번호에 DROP all이 있으면 동작하지 않는다. --line-numbers로 순서를 확인해야 한다.
- 결과에 명시적 DROP dpt:22가 있으면 의도적으로 SSH를 차단한 규칙이다. 삭제 전에 왜 추가됐는지 확인해야 한다.
(아무것도 출력되지 않음)
SSH 허용 규칙이 없습니다. policy DROP 환경에서 원인 확정입니다.
INPUT 외에 FORWARD, OUTPUT 체인의 상태도 확인합니다. SSH가 안 되는 원인이 INPUT이 아닌 다른 곳에 있을 수도 있기 때문입니다.
# 전체 체인 확인
iptables -L -n -v --line-numbers
# nat 테이블도 확인 (포트 포워딩 규칙이 있을 경우)
iptables -t nat -L -n -v --line-numbers
FORWARD 체인에 의도치 않은 DROP 규칙이 있거나, OUTPUT 체인에 제한이 있는 경우 SSH 연결에 영향을 줄 수 있습니다.
iptables -L -n -v --line-numbers- FORWARD 체인 policy — Docker를 사용한다면 FORWARD에 Docker가 추가한 규칙들이 있을 것이다.
- OUTPUT 체인 — 서버에서 외부로 나가는 트래픽에 DROP이 있으면 SSH 연결 수립 후 응답 패킷이 차단될 수 있다.
- nat 테이블 PREROUTING — 포트 포워딩 규칙이 SSH 포트에 영향을 주는지 확인한다.
원인이 확정됐으니 복구합니다. 각 옵션의 의미와 이유입니다.
-I INPUT 1: Insert. INPUT 체인의 1번 위치에 삽입합니다. 기존 1번이 2번으로, 2번이 3번으로 밀립니다. 모든 규칙보다 앞서 평가되도록 맨 앞에 넣습니다.
왜 -A(Append, 끝에 추가)가 아닌 -I 1인가? -A로 추가하면 현재 체인의 마지막에 붙습니다. 만약 체인에 이미 DROP all 규칙이 있다면, ACCEPT 규칙은 그 뒤에 오게 되어 영원히 평가되지 않습니다. -I 1은 어떤 상황에서도 맨 앞에 오므로 안전합니다.
-p tcp: TCP 프로토콜만 매칭합니다. SSH는 TCP 기반입니다. -p를 생략하면 all이 되어 UDP, ICMP 등도 포트 22에 매칭하려 합니다(의미 없지만 불필요한 조건).
--dport 22: 목적지 포트 22. sshd_config에서 포트를 변경했다면 그 번호를 써야 합니다. -p tcp가 없으면 --dport를 쓸 수 없습니다.
-j ACCEPT: 매칭된 패킷을 허용합니다.
중요: 이 명령을 실행한 직후 현재 SSH 세션을 끊지 말고 다른 터미널을 열어 SSH 재접속 테스트를 먼저 해야 합니다. 규칙을 잘못 추가했을 경우 현재 세션에서 롤백해야 합니다.
iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT- 규칙 추가 직후: iptables -L INPUT -n --line-numbers 재실행 — 1번 위치에 dpt:22 ACCEPT 규칙이 생겼는지 확인.
- 현재 세션을 유지하면서 다른 터미널에서 ssh user@서버IP 테스트. 성공하면 1번 규칙의 pkts가 증가한다.
- SSH 재접속 성공 확인 후에만 iptables-save로 영구 저장. 저장 전에 세션을 끊으면 재시작 시 규칙이 사라질 수 있다.
# 규칙 추가 직후 확인
iptables -L INPUT -n --line-numbers
Chain INPUT (policy DROP)
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
2 4523 320K ACCEPT all -- lo any 0.0.0.0/0 0.0.0.0/0
3 1205 86K ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
4 120 8640 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80
5 89 6408 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
1번 규칙이 맨 앞에 추가됐습니다. pkts=0은 방금 추가했기 때문입니다. 다른 터미널에서 SSH 접속을 테스트하면 pkts가 증가합니다.
# SSH 재접속 성공 확인 후 영구 저장
iptables-save > /etc/iptables/rules.v4
# Debian/Ubuntu에서 netfilter-persistent를 쓰는 경우
netfilter-persistent save
규칙을 잘못 추가했거나 테스트 목적으로 추가한 규칙을 제거합니다. -D는 Delete입니다.
# 번호로 삭제 (--line-numbers로 번호 확인 후)
iptables -D INPUT 1 # 1번 규칙 삭제
# 규칙 내용으로 삭제 (번호 모를 때)
iptables -D INPUT -p tcp --dport 22 -j ACCEPT
두 방법 모두 동작합니다. 번호로 삭제하는 것이 더 빠르지만, 동일한 규칙이 여러 개 있을 경우 내용으로 삭제하면 첫 번째 매칭되는 것만 삭제합니다.
주의: 삭제 전에 현재 세션을 유지하면서 다른 터미널로 테스트하세요. 잘못 삭제하면 현재 세션이 끊길 수 있습니다.
iptables -D INPUT 1Part 4 — IP 기반 접근 제어: 보안 수준을 한 단계 높이기
모든 IP에서 SSH를 허용하는 것(0.0.0.0/0)은 무차별 브루트포스 공격에 노출됩니다. 실무에서는 SSH 접근을 특정 IP 또는 대역으로 제한하는 것이 표준입니다.
소스 IP 제한으로 SSH 보안 강화
특정 IP 또는 대역에서만 SSH를 허용하는 규칙입니다.
# 단일 IP에서만 SSH 허용
iptables -I INPUT 1 -p tcp --dport 22 -s 203.0.113.5 -j ACCEPT
# 특정 CIDR 대역에서만 허용 (사무실 네트워크 예시)
iptables -I INPUT 1 -p tcp --dport 22 -s 203.0.113.0/24 -j ACCEPT
# 여러 IP 대역을 허용할 때는 규칙을 여러 개 추가
iptables -I INPUT 1 -p tcp --dport 22 -s 203.0.113.0/24 -j ACCEPT
iptables -I INPUT 1 -p tcp --dport 22 -s 198.51.100.0/24 -j ACCEPT
-s 옵션에 IP 또는 CIDR 표기를 씁니다. 이렇게 하면 지정한 소스 IP에서 온 패킷만 dpt:22 ACCEPT 규칙에 매칭됩니다. 다른 IP에서의 SSH 시도는 매칭 규칙이 없어 policy DROP에 의해 차단됩니다.
IP 범위를 지정할 때 iprange 모듈을 쓸 수도 있습니다.
# 192.168.1.10 ~ 192.168.1.50 범위만 허용
iptables -I INPUT 1 -p tcp --dport 22 \
-m iprange --src-range 192.168.1.10-192.168.1.50 -j ACCEPT
SSH 소스 IP를 제한하면 /var/log/auth.log에 기록되는 브루트포스 시도가 극적으로 줄어듭니다. 실무에서 SSH를 0.0.0.0/0으로 열어두면 하루에 수만 건의 로그인 시도가 들어옵니다.
현재 어떤 소스에서 SSH 접근이 허용되어 있는지 확인합니다.
# SSH 관련 규칙 상세 확인
iptables -L INPUT -n -v | grep dpt:22
# 전체 허용
12 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
# IP 제한된 경우
12 ACCEPT tcp -- * * 203.0.113.0/24 0.0.0.0/0 tcp dpt:22
source 컬럼이 0.0.0.0/0이면 전 세계에서 접근 가능한 상태입니다. 운영 서버라면 반드시 특정 IP로 제한하는 것을 검토해야 합니다.
# 기존 전체 허용 SSH 규칙 번호 확인 후 삭제
iptables -L INPUT -n --line-numbers | grep dpt:22
# 예: 1번에 0.0.0.0/0 규칙이 있다면
iptables -D INPUT 1
# 사무실 IP 대역만 허용으로 교체
iptables -I INPUT 1 -p tcp --dport 22 -s 203.0.113.0/24 -j ACCEPT
iptables -L INPUT -n -v | grep dpt:22Part 5 — DROP vs REJECT: 침묵과 거절의 차이
DROP은 조용히 버리고, REJECT는 이유를 알려줍니다
두 타깃의 차이는 발신자 경험에서 극명하게 드러납니다.
DROP
패킷을 조용히 버립니다. 발신자는 응답을 기다리다 타임아웃 후 실패를 인지합니다. TCP의 경우 기본 타임아웃이 수십 초에서 수 분입니다.
# DROP이면 클라이언트에서 이런 현상이 나타남
ssh user@서버IP
# 아무 응답 없이 수십 초 후 "Connection timed out" 에러
REJECT
패킷을 버리되 발신자에게 ICMP 에러 메시지를 보냅니다. 발신자가 즉시 실패를 인지합니다.
# REJECT이면 즉각 에러
ssh user@서버IP
# 즉시 "Connection refused" 에러
# REJECT 타입 지정
iptables -A INPUT -p tcp --dport 22 -j REJECT --reject-with tcp-reset
iptables -A INPUT -p udp -j REJECT --reject-with icmp-port-unreachable
--reject-with 옵션으로 거부 메시지 타입을 지정할 수 있습니다. tcp-reset은 TCP RST를 보내 즉각 연결 종료를 알립니다. icmp-port-unreachable은 ICMP 메시지를 보냅니다.
언제 어떤 것을 쓰는가
| 상황 | 권장 | 이유 |
|---|---|---|
| 외부 공격자 차단 | DROP | 포트 스캔에 포트 존재 여부를 알리지 않음 |
| 내부 서비스 간 통신 오류 | REJECT | 빠른 실패로 디버깅 용이 |
| 개발 환경 | REJECT | 타임아웃 없이 즉각 에러로 개발 속도 향상 |
| 로드밸런서 뒤 서버 | DROP | 헬스체크 실패 시 자연스러운 제거 |
policy DROP은 타임아웃 방식이므로, 내부 서비스가 외부로 잘못된 포트에 연결을 시도하면 타임아웃까지 수십 초를 기다립니다. 내부 네트워크에서는 REJECT를 쓰면 빠른 실패로 디버깅이 훨씬 쉽습니다.
확대
Part 6 — iptables LOG: 차단된 패킷 추적하기
방화벽이 어떤 트래픽을 차단하고 있는지 보이지 않으면 진단이 어렵습니다. LOG 타깃을 사용하면 차단 전에 패킷 정보를 /var/log/kern.log에 기록할 수 있습니다.
LOG 타깃으로 차단 트래픽 가시화
LOG는 패킷을 차단하지 않고 기록만 합니다. 보통 LOG 다음에 DROP 규칙을 추가하는 패턴을 씁니다.
# 차단되기 직전 패킷을 로그로 남기고 DROP
iptables -A INPUT -j LOG --log-prefix "INPUT_DROP: " --log-level 4
iptables -A INPUT -j DROP # 위 LOG 다음에 DROP
# 또는 policy DROP이 이미 있으면 LOG만 추가해도 됨 (policy가 DROP을 담당)
--log-prefix "INPUT_DROP: ": 로그에 붙는 식별 문자열입니다. kern.log에서 grep "INPUT_DROP:" 으로 찾을 수 있습니다.
--log-level 4: 로그 우선순위입니다. 4는 WARNING 수준으로, /var/log/kern.log에 기록됩니다.
로그 확인 방법입니다.
# 실시간 차단 로그 확인
tail -f /var/log/kern.log | grep "INPUT_DROP:"
# 특정 IP에서의 접근 시도 확인
grep "INPUT_DROP:" /var/log/kern.log | grep "SRC=203.0.113.5"
로그 출력 예시입니다.
Jun 10 09:17:43 server kernel: [12345.678] INPUT_DROP: IN=eth0 OUT= MAC=...
SRC=203.0.113.5 DST=10.0.0.1 LEN=60 TTL=64 ID=12345 PROTO=TCP
SPT=52341 DPT=22 FLAGS=S WINDOW=65535
이 로그에서 확인할 수 있는 정보입니다.
SRC=203.0.113.5: 접속을 시도한 소스 IPDPT=22: 목적지 포트 (SSH 시도)FLAGS=S: SYN 플래그 (새 연결 시도)PROTO=TCP: TCP 프로토콜
이 정보로 누가 어떤 포트에 접근을 시도하는지 실시간으로 파악할 수 있습니다.
로그 폭발 방지 — rate limiting
LOG 규칙이 있으면 DDoS 공격 시 로그 파일이 폭발적으로 커질 수 있습니다. -m limit으로 속도를 제한합니다.
# 분당 최대 5개, 버스트 10개까지만 로그
iptables -A INPUT -m limit --limit 5/min --limit-burst 10 \
-j LOG --log-prefix "INPUT_DROP: " --log-level 4
이 규칙은 분당 5개, 순간 최대 10개까지만 로그를 남깁니다. 그 이상은 로그 없이 policy DROP으로 처리됩니다.
SSH 접속 시도가 차단될 때 로그를 남기도록 설정합니다. 이렇게 하면 누가 언제 SSH를 시도했는지 추적할 수 있습니다.
# SSH 차단 전에 로그 먼저 기록
iptables -I INPUT 1 -p tcp --dport 22 \
-m limit --limit 10/min --limit-burst 20 \
-j LOG --log-prefix "SSH_BLOCKED: " --log-level 4
# 로그 확인 (실시간)
tail -f /var/log/kern.log | grep "SSH_BLOCKED:"
실제 브루트포스 공격이 들어오면 이런 로그가 쏟아집니다.
Jun 10 09:20:01 server kernel: SSH_BLOCKED: SRC=45.142.212.100 DPT=22 FLAGS=S
Jun 10 09:20:02 server kernel: SSH_BLOCKED: SRC=45.142.212.100 DPT=22 FLAGS=S
Jun 10 09:20:03 server kernel: SSH_BLOCKED: SRC=45.142.212.100 DPT=22 FLAGS=S
이 IP를 확인하고 반복 시도를 하는 IP는 명시적으로 차단할 수 있습니다.
# 특정 IP 영구 차단
iptables -I INPUT 1 -s 45.142.212.100 -j DROP
iptables -I INPUT 1 -p tcp --dport 22 -j LOG --log-prefix 'SSH_BLOCKED: 'Part 7 — 규칙 영구화: 재시작해도 살아있게
iptables 규칙은 메모리에만 존재합니다. 서버를 재시작하면 모든 규칙이 사라집니다. 이것을 많은 사람이 모르고 있다가 재시작 후 "규칙이 다 사라졌다"는 장애를 겪습니다.
현재 메모리에 있는 iptables 규칙 전체를 파일로 내보냅니다.
# 현재 규칙 저장
iptables-save > /etc/iptables/rules.v4
# 저장된 내용 확인
cat /etc/iptables/rules.v4
# Generated by iptables-save v1.8.7 on Tue Jun 10 09:25:00 2026
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
COMMIT
이 파일 형식은 iptables-restore로 그대로 다시 불러올 수 있습니다.
# 파일에서 규칙 복원
iptables-restore < /etc/iptables/rules.v4
iptables-save > /etc/iptables/rules.v4서버 재시작 후 자동으로 규칙이 복원되도록 설정합니다.
# Debian/Ubuntu — netfilter-persistent 설치 및 설정
apt install netfilter-persistent
netfilter-persistent save
# /etc/iptables/rules.v4 와 /etc/iptables/rules.v6 에 저장되고
# 부팅 시 systemd 서비스가 자동으로 복원합니다
# 서비스 상태 확인
systemctl status netfilter-persistent
# RHEL/CentOS — iptables-services 사용
yum install iptables-services
systemctl enable iptables
service iptables save
수동으로 systemd 서비스를 만들 수도 있습니다.
# /etc/systemd/system/iptables-restore.service
cat > /etc/systemd/system/iptables-restore.service << 'EOF'
[Unit]
Description=Restore iptables rules
Before=network.target
[Service]
Type=oneshot
ExecStart=/sbin/iptables-restore /etc/iptables/rules.v4
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl enable iptables-restore
systemctl start iptables-restore
apt install netfilter-persistent && netfilter-persistent savePart 8 — 흔한 실수와 복구법
ACCEPT 규칙을 추가했는데 SSH가 열리지 않는 가장 흔한 원인입니다.
원인: -A(Append)는 체인의 맨 끝에 추가합니다. 이미 DROP all 규칙이 있다면 ACCEPT는 그 뒤에 오게 되어 영원히 평가되지 않습니다.
iptables -L INPUT -n --line-numbers
Chain INPUT (policy DROP)
1 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0
2 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
2번 규칙의 pkts가 0입니다. 1번 DROP all이 모든 패킷을 먼저 차단하므로 2번은 평가되지 않습니다.
진단: pkts=0인 규칙 중 앞 번호에 DROP all이 있는지 확인합니다.
해결:
# 2번 잘못된 규칙 삭제
iptables -D INPUT 2
# 맨 앞에 올바르게 삽입
iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT
# 저장
iptables-save > /etc/iptables/rules.v4
교훈: policy DROP 환경에서는 **항상 -I INPUT 1**을 씁니다. -A는 policy ACCEPT 환경이거나 체인에 DROP all 규칙이 없음을 확인한 경우에만 씁니다.
SSH는 됐는데 이것은 해결이 아닙니다. 방화벽 자체가 무력화된 상태입니다.
원인: -j ACCEPT에 프로토콜(-p)과 포트(--dport) 조건을 붙이지 않으면 모든 트래픽을 허용하는 규칙이 됩니다.
Chain INPUT (policy DROP)
1 8392 820K ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0
prot 컬럼이 all, 오른쪽 조건 컬럼이 비어있습니다. 이 서버는 지금 모든 포트가 열려있는 상태입니다. 데이터베이스 포트, 내부 관리 포트 전부 노출됩니다.
즉각 롤백:
# 1번 규칙 즉시 삭제
iptables -D INPUT 1
# 올바른 규칙으로 교체
iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT
# 저장
iptables-save > /etc/iptables/rules.v4
교훈: 규칙 추가 후 iptables -L INPUT -n --line-numbers로 반드시 prot 컬럼과 조건 컬럼을 확인합니다. all + 조건 없음 = 전체 허용 = 방화벽 무력화.
긴급 상황에서 "일단 방화벽을 초기화하자"는 판단이 더 큰 장애를 만드는 대표적인 사례입니다.
원인: iptables -F는 모든 체인의 모든 규칙을 삭제합니다. 하지만 체인의 policy는 그대로입니다. policy DROP이 설정된 서버에서 -F를 실행하면 모든 규칙이 사라지고, 매칭되는 규칙이 없으니 policy DROP이 모든 패킷을 차단합니다. SSH, HTTP, HTTPS 전부 즉시 차단됩니다.
# 이렇게 하면 절대 안 됨 — policy DROP 서버에서 모든 통신 즉시 두절
# 기존 규칙 삭제(-F/-X/-Z)는 복구 불가이므로 이 배치 예제에서는 실행하지 않습니다.
# 백업한 파일을 `iptables-restore --test`로 검증한 뒤 단일 ruleset으로 교체하세요.
현재 세션(SSH)은 ESTABLISHED 상태라 잠깐 유지되지만, 새 연결은 전부 차단됩니다. -F 직후 SSH 세션을 닫으면 다시 들어올 방법이 없습니다.
복구 방법: 콘솔 접속(OOB: Out-of-Band)이 필요합니다. 클라우드라면 시리얼 콘솔, 온프레미스라면 KVM/IPMI/iDRAC 등 네트워크 없이 접속할 수 있는 방법으로 접속 후 규칙을 재설정합니다.
# 콘솔에서 검증된 전체 ruleset을 준비하고 일괄 복구합니다.
# /etc/iptables/rules.v4를 sudoedit로 수정한 뒤, 허용할 포트와 출발지 대역을 검토하세요.
sudo iptables-restore --test /etc/iptables/rules.v4
sudo iptables-restore /etc/iptables/rules.v4
sudo iptables-save | sudo tee /etc/iptables/rules.v4 >/dev/null
교훈: 원격에서 policy를 ACCEPT로 바꾸고 -F하는 순서를 실행하지 않습니다. 백업한 ruleset을 iptables-restore --test로 먼저 검증한 뒤 일괄 적용하고, 콘솔/OOB를 확보해 복구 가능성을 남깁니다.
# 준비한 파일의 문법을 검증한 후, 전체 ruleset을 교체하는 패턴
sudo iptables-restore --test /etc/iptables/rules.v4
sudo iptables-restore /etc/iptables/rules.v4
서버 재시작 후 방화벽 규칙이 전부 초기화되는 문제입니다. iptables를 처음 쓰는 사람이 가장 많이 겪는 실수입니다.
원인: iptables 규칙은 기본적으로 메모리에만 존재합니다. iptables-save로 파일에 저장하지 않으면 재시작 시 사라집니다.
진단:
# iptables-save 파일이 있는지 확인
ls -la /etc/iptables/rules.v4
# 없으면 규칙이 재시작 후 초기화될 것임
# netfilter-persistent 설치 여부 확인
systemctl status netfilter-persistent 2>/dev/null || echo "netfilter-persistent 없음"
해결:
# Debian/Ubuntu
apt install netfilter-persistent
netfilter-persistent save
systemctl enable netfilter-persistent
# RHEL/CentOS
yum install iptables-services
service iptables save
systemctl enable iptables
교훈: 규칙을 추가할 때마다 저장을 습관화합니다. 또는 배포 스크립트에 항상 iptables-save > /etc/iptables/rules.v4를 포함합니다.
Ubuntu 22.04 이상 또는 RHEL 9에서 iptables 명령이 이 에러를 냅니다.
원인: 시스템이 nftables 백엔드를 기본으로 사용하고 있는데, iptables 명령이 nftables 위에서 동작하는 iptables-nft를 가리키는 경우 발생합니다. 기존에 iptables-legacy로 작성된 규칙이 남아있거나 혼용할 때 충돌합니다.
진단:
# 현재 iptables 명령이 어느 백엔드를 사용하는지 확인
iptables --version
iptables v1.8.7 (nf_tables) ← nftables 백엔드
iptables v1.8.7 (legacy) ← 구 iptables 백엔드
# 두 백엔드의 규칙 각각 확인
iptables-legacy -L INPUT -n
iptables-nft -L INPUT -n
# 어느 쪽에 규칙이 있는지 확인
iptables-legacy -L -n | grep -v "^$\|^Chain\|^target" | wc -l
iptables-nft -L -n | grep -v "^$\|^Chain\|^target" | wc -l
해결 방법:
# 백엔드를 통일한다 (Ubuntu/Debian)
update-alternatives --config iptables
# 선택지에서 /usr/sbin/iptables-legacy 또는 /usr/sbin/iptables-nft 선택
# 장기적으로는 nftables 문법으로 마이그레이션 권장
# iptables-save 형식을 nftables 형식으로 변환
iptables-save | iptables-translate > /etc/nftables.conf
nftables는 iptables의 후계자로, 동일한 기능을 더 효율적으로 처리합니다. Ubuntu 22.04 이상에서는 nftables를 기본으로 쓰는 것을 권장합니다.
배포 자동화 스크립트에 iptables -F가 포함되어 있어 배포할 때마다 방화벽 규칙이 초기화되는 문제입니다. 오늘 장애의 실제 원인일 가능성이 높습니다.
진단:
# 배포 스크립트에서 iptables 관련 명령 확인
grep -rn "iptables" /opt/deploy/ /home/deploy/ /etc/deploy/ 2>/dev/null
grep -rn "iptables" /var/lib/jenkins/workspace/ 2>/dev/null
# 마지막으로 실행된 스크립트 확인
cat /var/log/deploy.log | tail -50 | grep -i iptables
# 스크립트 안에 이런 라인이 있으면 문제
# iptables -F # 전체 초기화 — 복사·실행하지 마세요
# iptables -P INPUT ACCEPT # policy를 ACCEPT로 변경 — 복사·실행하지 마세요
해결:
배포 스크립트에서 iptables -F를 제거하고, 개별 규칙을 추가/삭제하는 방식으로 변경합니다. 규칙 전체를 재설정해야 한다면 앞서 설명한 안전한 재설정 스크립트 패턴을 따릅니다.
# 배포 스크립트에 방화벽 검증 단계 추가
# 배포 완료 후 SSH 포트 접근 가능 여부 자동 확인
nc -z -w5 서버IP 22 && echo "SSH OK" || echo "SSH FAIL - 방화벽 확인 필요"
교훈: 배포 스크립트는 방화벽을 건드리지 않는 것이 원칙입니다. 반드시 건드려야 한다면 스크립트 마지막에 검증 단계를 추가하고, 실패 시 이전 상태로 롤백하는 로직을 포함합니다.
Part 9 — Docker가 iptables를 자동으로 바꾼다
Docker는 시작할 때 iptables를 몰래 수정한다
Docker 데몬이 시작되면 아무 말 없이 iptables에 체인과 규칙을 추가합니다. DOCKER, DOCKER-USER, DOCKER-ISOLATION-STAGE-1, DOCKER-ISOLATION-STAGE-2 체인이 생기고, FORWARD 체인에 Docker 관련 규칙이 삽입됩니다. 이 때문에 Docker를 쓰는 서버에서는 수동으로 FORWARD 규칙을 만들어도 Docker 재시작 시 덮어쓰여지는 문제가 발생합니다.
Docker가 iptables를 관리하는 이유는 컨테이너 포트 포워딩을 자동화하기 위해서입니다. docker run -p 8080:80을 실행하면 Docker는 nat 테이블 PREROUTING에 DNAT 규칙을 추가하고, filter 테이블 FORWARD에 해당 트래픽 허용 규칙을 추가합니다. 운영자가 직접 할 필요 없이 자동으로 처리해주는 것입니다.
실제 문제: Docker 포트와 INPUT 체인의 관계
docker run -p 8080:80 nginx를 실행하면 외부에서 서버 8080 포트로 접근이 됩니다. 그런데 iptables -L INPUT -n에는 8080 허용 규칙이 없습니다. 이상하게 보이지만 정상입니다.
# Docker 포트 매핑 확인
docker ps
CONTAINER ID IMAGE COMMAND PORTS
a1b2c3d4e5f6 nginx ... 0.0.0.0:8080->80/tcp
# INPUT 체인에는 8080 규칙 없음 — 그런데 외부에서 접근 가능
iptables -L INPUT -n -v
# 실제로는 nat 테이블 PREROUTING에 DNAT 규칙이 있음
iptables -t nat -L PREROUTING -n -v
# DOCKER 체인에 FORWARD 허용 규칙이 있음
iptables -L DOCKER -n -v
Docker는 nat 테이블 + FORWARD 체인 경로를 사용합니다. 패킷이 INPUT 체인을 통과하지 않으므로, INPUT 체인에 8080 DROP 규칙을 추가해도 Docker 컨테이너 포트는 차단되지 않습니다.
확대
DOCKER-USER 체인: 커스텀 규칙의 안전한 공간
Docker가 제공하는 DOCKER-USER 체인은 "운영자가 커스텀 규칙을 넣어도 되는 체인"입니다. Docker가 관리하는 DOCKER 체인과 달리, DOCKER-USER에 추가한 규칙은 Docker 재시작 후에도 유지됩니다.
# 203.0.113.0/24 대역 외의 외부 IP에서 Docker 컨테이너 접근 차단
# eth0에서 들어오는 패킷 중 203.0.113.0/24가 아닌 소스 IP를 DROP
iptables -I DOCKER-USER -i eth0 ! -s 203.0.113.0/24 -j DROP
# 적용 확인
iptables -L DOCKER-USER -n -v
Chain DOCKER-USER (1 references)
num pkts bytes target prot opt in out source destination
1 0 0 DROP all -- eth0 * !203.0.113.0/24 0.0.0.0/0
2 0 0 RETURN all -- * * 0.0.0.0/0 0.0.0.0/0
! -s 203.0.113.0/24는 "203.0.113.0/24가 아닌 소스 IP" 조건입니다. 해당 대역 외 IP에서 오는 모든 컨테이너 접근 요청이 차단됩니다. DOCKER-USER 체인 마지막의 RETURN은 매칭되지 않은 패킷을 DOCKER 체인으로 돌려보냅니다.
iptables -I DOCKER-USER -i eth0 ! -s 203.0.113.0/24 -j DROPDocker 컨테이너의 포트 매핑을 INPUT 체인으로 차단하려는 시도입니다.
원인: Docker는 NAT 테이블의 PREROUTING(DNAT)과 filter 테이블의 FORWARD 체인을 사용해 포트를 외부에 노출합니다. 패킷 경로가 INPUT 체인을 우회하므로 INPUT에 DROP 규칙을 추가해도 컨테이너 포트는 차단되지 않습니다.
# 이렇게 해도 Docker 컨테이너 8080 포트는 차단되지 않음
iptables -I INPUT 1 -p tcp --dport 8080 -j DROP # 효과 없음
# 올바른 방법: DOCKER-USER 체인 사용
iptables -I DOCKER-USER -i eth0 ! -s 203.0.113.0/24 -j DROP
교훈: Docker 환경에서 컨테이너 접근 제어는 반드시 DOCKER-USER 체인을 사용합니다. INPUT 체인은 Docker 포트 포워딩 경로와 무관합니다.
Part 10 — ipset: 수백 개 IP를 효율적으로 차단
iptables 규칙 100개 vs ipset 해시 테이블
IP 100개를 개별 iptables 규칙으로 차단하면 패킷마다 100번 순차 비교가 일어납니다. IP가 10만 개라면 성능 문제가 심각해집니다. ipset은 해시 테이블을 사용해 수만 개의 IP를 **O(1)**에 검색합니다. 공격 IP 목록 차단, 지역 IP 차단, 화이트리스트 접근 제어 등 대량 IP 관리에 필수 도구입니다.
설치 및 기본 사용
# ipset 설치
apt install ipset # Debian/Ubuntu
yum install ipset # RHEL/CentOS
# IP 집합 생성 (hash:ip 타입 — 단일 IP 저장)
ipset create BLOCKED_IPS hash:ip
# IP 추가
ipset add BLOCKED_IPS 1.2.3.4
ipset add BLOCKED_IPS 5.6.7.8
ipset add BLOCKED_IPS 9.10.11.12
# 집합 내용 확인
ipset list BLOCKED_IPS
Name: BLOCKED_IPS
Type: hash:ip
Revision: 6
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 440
References: 0
Number of entries: 3
Members:
1.2.3.4
5.6.7.8
9.10.11.12
# iptables에서 ipset 매칭 — BLOCKED_IPS에 있는 소스 IP 차단
iptables -I INPUT 1 -m set --match-set BLOCKED_IPS src -j DROP
# 특정 IP를 집합에서 제거
ipset del BLOCKED_IPS 1.2.3.4
# 집합 전체 삭제
ipset destroy BLOCKED_IPS
실무 활용: 공격 IP 목록 자동 로드
#!/bin/bash
# 공격 IP 목록 파일에서 ipset 자동 구성
BLOCKLIST_FILE="/etc/ipset-blocklist.txt"
SET_NAME="BLOCKED_IPS"
# 기존 집합이 있으면 삭제 후 재생성
ipset destroy $SET_NAME 2>/dev/null
ipset create $SET_NAME hash:ip maxelem 1000000
# 파일에서 IP 로드 (주석 및 빈 줄 제외)
while IFS= read -r ip; do
[[ "$ip" =~ ^#.*$ ]] && continue
[[ -z "$ip" ]] && continue
ipset add $SET_NAME "$ip" 2>/dev/null
done < "$BLOCKLIST_FILE"
echo "로드 완료: $(ipset list $SET_NAME | grep 'Number of entries' | awk '{print $4}')개 IP 차단"
# iptables 규칙이 없으면 추가
iptables -C INPUT -m set --match-set $SET_NAME src -j DROP 2>/dev/null || \
iptables -I INPUT 1 -m set --match-set $SET_NAME src -j DROP
영구화: ipset-save와 ipset-restore
iptables와 마찬가지로 ipset도 재시작 시 초기화됩니다. ipset-save와 ipset-restore로 별도로 저장해야 합니다.
# ipset 집합 저장
ipset save > /etc/ipset/rules.v4
# 복원
ipset restore < /etc/ipset/rules.v4
# 부팅 시 자동 복원 — /etc/rc.local 또는 systemd 서비스
# netfilter-persistent가 ipset을 지원하지 않으므로 별도 처리 필요
cat > /etc/systemd/system/ipset-restore.service << 'EOF'
[Unit]
Description=Restore ipset rules
Before=netfilter-persistent.service
[Service]
Type=oneshot
ExecStart=/sbin/ipset restore -f /etc/ipset/rules.v4
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl enable ipset-restore
# 테스트용 ipset 생성
ipset create TEST_BLOCK hash:ip
# 테스트 IP 추가
ipset add TEST_BLOCK 192.168.100.1
ipset add TEST_BLOCK 192.168.100.2
# iptables 연동
iptables -I INPUT 1 -m set --match-set TEST_BLOCK src -j DROP
# 적용 확인
iptables -L INPUT -n -v --line-numbers | head -5
ipset list TEST_BLOCK
ipset create TEST_BLOCK hash:ip && ipset add TEST_BLOCK 192.168.100.1# 저장 디렉터리 생성
mkdir -p /etc/ipset
# ipset 집합 저장
ipset save > /etc/ipset/rules.v4
# 저장 내용 확인
cat /etc/ipset/rules.v4
create TEST_BLOCK hash:ip family inet hashsize 1024 maxelem 65536
add TEST_BLOCK 192.168.100.1
add TEST_BLOCK 192.168.100.2
ipset save > /etc/ipset/rules.v4iptables는 IPv4 패킷만 처리합니다. IPv6 트래픽은 ip6tables(또는 nftables)로 별도 설정해야 합니다. Ubuntu 서버의 기본 ip6tables policy가 ACCEPT라면 IPv6 주소로 직접 접근이 가능합니다.
진단:
# IPv6 방화벽 현재 상태 확인
ip6tables -L INPUT -n -v
# policy가 ACCEPT이고 규칙이 없으면 IPv6 완전 개방 상태
Chain INPUT (policy ACCEPT)
num pkts bytes target prot opt in out source destination
해결: ip6tables로 동일한 규칙 적용:
# 루프백 허용
ip6tables -A INPUT -i lo -j ACCEPT
# 기존 연결 허용
ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# SSH 허용
ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT
# HTTP/HTTPS 허용
ip6tables -A INPUT -p tcp --dport 80 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT
# ICMPv6 필수 허용 (IPv6 동작에 필요)
ip6tables -A INPUT -p icmpv6 -j ACCEPT
# 나머지 차단
ip6tables -P INPUT DROP
# 저장 (netfilter-persistent가 rules.v6도 함께 관리)
netfilter-persistent save
교훈: IPv4 방화벽만 설정하는 것은 반쪽짜리 보안입니다. IPv4 규칙을 변경할 때마다 ip6tables도 동일하게 업데이트하거나, nftables로 통합 관리하는 것을 권장합니다.
/var/log/kern.log에 이 메시지가 반복되면 conntrack(연결 추적) 테이블이 가득 찬 것입니다. 새 연결이 모두 차단되어 서비스 불가 상태가 됩니다.
진단:
# 현재 conntrack 항목 수 vs 최대값 비교
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
# 커널 로그에서 메시지 확인
dmesg | grep nf_conntrack | tail -20
grep "nf_conntrack" /var/log/kern.log | tail -20
# count가 max에 근접하거나 초과
131072
131072
임시 해결 (즉시 적용):
sysctl -w net.netfilter.nf_conntrack_max=262144
영구 설정:
# /etc/sysctl.conf에 추가
echo "net.netfilter.nf_conntrack_max=262144" >> /etc/sysctl.conf
sysctl -p
# TIME_WAIT 타임아웃 단축으로 테이블 빠른 회수
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=30
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_fin_wait=30
근본 원인 파악:
# conntrack 상태별 연결 수 확인
conntrack -L 2>/dev/null | awk '{print $4}' | sort | uniq -c | sort -rn
# TIME_WAIT 연결이 비정상적으로 많으면 타임아웃 조정 필요
# 연결 수가 급격히 증가하면 DDoS 또는 애플리케이션 연결 누수 의심
교훈: 트래픽이 많은 서버는 nf_conntrack_max를 미리 충분히 크게 설정하고, 모니터링에 nf_conntrack_count 지표를 포함해 조기에 감지합니다.
심화 — 규칙은 통과시켜도 큰 패킷이 사라진다
심화: MSS 클램핑과 PMTU 블랙홀 — 방화벽 규칙 밖의 함정
iptables 규칙을 아무리 맞게 짜도, 터널·VPN·PPPoE 뒤에서는 '작은 요청은 되는데 큰 전송만 멈추는' 이상한 장애가 납니다. 원인이 규칙이 아니라 경로의 MTU와 그걸 알려 주는 ICMP를 방화벽이 막은 데 있어서, iptables -L만 봐선 영영 못 찾습니다.
- 경로 MTU가 1500보다 작을 수 있다: VPN·GRE·PPPoE는 헤더 오버헤드로 실제 전송 가능 크기가 1500보다 작습니다(예: 1400). 이보다 큰 패킷은 쪼개거나(fragment) 경로에서 버려집니다.
- DF 비트 + PMTUD 실패 = 블랙홀: 대용량 TCP 패킷은 보통 DF(Don't Fragment)가 켜져 있어, 중간 장비가 쪼개는 대신 'ICMP fragmentation needed'로 크기를 낮추라고 알립니다. 방화벽이 그 ICMP를 DROP하면 송신자는 계속 큰 패킷만 보내고 전부 사라집니다(PMTU 블랙홀).
- 증상이 '규칙 문제'로 오인된다: handshake와 짧은 요청(작은 패킷)은 통과하므로 포트는 열려 보이고, 대용량 응답·파일 전송·큰 헤더 요청만 멈춥니다.
- 해결은 규칙이 아니라 MSS 클램핑: FORWARD/mangle에
TCPMSS --clamp-mss-to-pmtu로 handshake의 MSS를 경로 MTU에 맞춰 낮추면, 애초에 큰 세그먼트가 생기지 않아 블랙홀을 피합니다. 더불어 'fragmentation needed' ICMP는 반드시 허용해야 PMTUD가 동작합니다.
즉 방화벽 트러블슈팅의 마지막 층은 '무엇을 허용/차단했나'를 넘어 '허용된 패킷이 경로를 통째로 통과할 만큼 작은가'까지 봐야 합니다.
상황: VPN(또는 GRE 터널) 너머 서버에 SSH는 붙고 whoami 같은 짧은 명령은 즉답인데, 출력이 큰 명령(ls -l /usr/bin, 큰 파일 cat)이나 scp 전송이 중간에 멈춥니다. 방화벽 규칙을 몇 번을 봐도 포트는 다 허용돼 있습니다.
원인: 터널 오버헤드로 경로 MTU가 1500보다 작은데(예: 1400), 대용량 TCP 세그먼트는 DF 비트가 켜져 있어 중간에서 쪼갤 수 없습니다. 크기를 낮추라는 'ICMP fragmentation needed(type 3 code 4)'가 경로의 방화벽에서 DROP돼, 송신자는 그 사실을 모른 채 큰 패킷만 반복 전송하고 모두 사라집니다(PMTU 블랙홀). 작은 패킷만 통과하니 '되다 안 되다' 하는 것처럼 보입니다.
진단: 조각화 없이 보낼 수 있는 최대 크기를 이진 탐색해 경로 MTU를 찾습니다.
# 응답이 오는 가장 큰 -s 값 + 28(헤더)이 경로 MTU
ping -M do -s 1472 -c1 대상 # 1500 MTU 가정 → 실패하면 크기를 줄여 재시도
ping -M do -s 1372 -c1 대상 # 1400 MTU면 이 근처에서 성공
# 큰 세그먼트가 ACK 없이 재전송되는지, ICMP type 3 code 4가 오는지 확인
sudo tcpdump -ni any 'icmp[icmptype] == 3 or (tcp port 22)'
해결: FORWARD 체인 mangle에 MSS 클램핑을 겁니다 — handshake 단계에서 MSS를 경로 MTU에 맞춰 낮춰 큰 세그먼트가 아예 안 생기게 합니다.
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
방화벽에서 'fragmentation needed(ICMP type 3 code 4)'를 허용해 PMTUD가 정상 동작하도록 합니다.
실제 출력
Chain INPUT (policy DROP) num pkts bytes target prot opt in out source destination 1 120 8640 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 2 89 6408 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 3 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0
❓ 이 상태에서 10.0.0.8에서 SSH(포트 22) 접속을 시도하면 어떻게 되나요? 어떤 규칙이 원인이고, 3번 규칙의 pkts=0이 의미하는 것은 무엇인가요?
실제 출력
Chain INPUT (policy ACCEPT) num pkts bytes target prot opt in out source destination 1 9201 1.2M DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 2 45 3240 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22
❓ policy ACCEPT이고 2번에 dpt:22 ACCEPT 규칙이 있습니다. 그런데 SSH가 안 됩니다. 왜 그런가요? 2번 규칙의 pkts=45는 무엇을 의미하나요?
실제 출력
Chain INPUT (policy DROP) num pkts bytes target prot opt in out source destination 1 1205 86K ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED 2 12 864 ACCEPT tcp -- * * 203.0.113.0/24 0.0.0.0/0 tcp dpt:22 3 120 8640 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 4 89 6408 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
❓ 개발팀 일부(203.0.113.0/24 대역)는 SSH가 되고 나머지는 안 됩니다. 원인과 해결 방법을 설명하세요. 재택근무자 IP(198.51.100.5)도 허용해야 한다면 어떤 명령을 쓰나요?
실제 출력
Chain INPUT (policy DROP) num pkts bytes target prot opt in out source destination 1 0 0 ACCEPT all -- lo any 0.0.0.0/0 0.0.0.0/0 2 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 3 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 4 0 0 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443
❓ SSH 접속은 되고 HTTP/HTTPS 서비스도 외부에서 접근 가능합니다. 그런데 서버에서 apt update를 실행하면 응답이 없습니다. 이 출력에서 원인을 찾으세요.
실제 출력
Chain INPUT (policy ACCEPT) num pkts bytes target prot opt in out source destination 1 0 0 DROP tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 CONTAINER ID IMAGE PORTS a1b2c3d4e5f6 nginx 0.0.0.0:8080->80/tcp
❓ INPUT 체인에 8080 DROP 규칙을 추가했는데도 외부에서 컨테이너 8080포트에 접근됩니다. 왜인가요?
실제 출력
Chain INPUT (policy ACCEPT) num pkts bytes target prot opt in out source destination Chain FORWARD (policy ACCEPT) num pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT) num pkts bytes target prot opt in out source destination
❓ 방화벽 규칙을 열심히 설정했는데 재시작 후 모두 사라졌습니다. 어떤 단계를 빠뜨린 것인가요?
혼자 해보기: HTTP가 안 된다는 문의
iptables 기본을 이해했다면 3분 안에 풀 수 있어야 합니다.
시나리오
같은 서버에서 이번엔 "HTTPS(443)는 되는데 HTTP(80)가 안 된다"는 문의가 왔습니다. iptables -L INPUT -n -v --line-numbers 출력: Chain INPUT (policy DROP) num pkts bytes target prot opt in out source destination 1 4523 320K ACCEPT all -- lo any 0.0.0.0/0 0.0.0.0/0 2 1205 86K ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED 3 89 6408 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 4 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0
원인을 진단하고, HTTP(80) 트래픽을 허용하는 규칙을 올바른 위치에 추가하는 명령어를 작성하세요.
힌트 (0/3 공개)
새벽 2시 복합 장애 — 세 가지 문제를 찾아라
이 모듈을 완전히 이해했다면 15분 안에 전부 진단할 수 있습니다.
시나리오
새벽 2시 장애 알림. 서버 A에서 다음 출력이 나옵니다. [ iptables -L INPUT -n -v --line-numbers ] Chain INPUT (policy DROP) num pkts bytes target prot opt in out source destination 1 9201 1.2M ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 2 4523 320K ACCEPT all -- lo any 0.0.0.0/0 0.0.0.0/0 3 1205 86K ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED 4 120 8640 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 5 89 6408 ACCEPT tcp -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 [ 추가 정보 ] - HTTP(80)는 접속 불가 신고가 들어오고 있습니다. - SSH는 현재 접속된 세션에서만 가능합니다. - 1시간 전 서버 재시작이 있었고 그 이후 이상하다는 제보입니다. - cat /etc/iptables/rules.v4 결과가 없습니다 (파일 없음).
다음 세 가지를 진단하고 해결 명령어를 작성하세요. 문제 1: 1번 규칙이 위험한 이유와 즉각 처리 방법 문제 2: HTTP(80)가 안 된다는 신고가 있는데 4번 규칙이 있습니다. 왜 안 될까요? 문제 3: 재시작 후 규칙이 이상해진 근본 원인과 재발 방지
힌트 (0/3 공개)
기술지원 SSH 장애 대응 플로우 (5분 목표)
고객이 "SSH가 안 된다"고 연락했을 때 기술지원 엔지니어가 따르는 표준 순서입니다.
1단계 — 현황 파악 (1분)
iptables -L INPUT -n -v --line-numbers
→ policy 확인 → ESTABLISHED,RELATED 규칙 확인 → SSH 규칙 확인 → DROP all 위치 확인
2단계 — 원인 분류 (30초)
A: SSH 규칙 없음 + policy DROP → 규칙 추가로 해결
B: SSH 규칙 있음 + DROP all이 앞 번호 → 규칙 순서 조정
C: 명시적 DROP 22 규칙 있음 → 규칙 삭제
D: iptables 정상인데 SSH 안 됨 → sshd 상태, 포트, 클라우드 보안그룹 확인
3단계 — 복구 (1분)
A/B: iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT
C: iptables -D INPUT <번호>
D: systemctl status sshd / ss -tlnp | grep ssh / 보안그룹 확인
4단계 — 검증 (1분)
iptables -L INPUT -n --line-numbers (규칙 확인)
다른 터미널에서 ssh user@서버IP (접속 테스트)
5단계 — 저장 및 원인 파악 (2분)
iptables-save > /etc/iptables/rules.v4
배포 스크립트 점검: grep -rn "iptables" 배포경로/
고객에게 기술 용어 없이 설명하는 방법
"서버 방화벽 허용 목록에서 SSH 접속 항목이 누락된 상태였습니다. 어젯밤 배포 스크립트에서 방화벽 설정을 초기화한 뒤 SSH 항목을 다시 추가하지 않은 것이 원인입니다. 허용 항목을 복구했고, 재시작 후에도 유지되도록 영구 저장 완료했습니다. 재발 방지를 위해 배포 스크립트에서 방화벽을 초기화하는 부분을 제거했습니다."
장애 보고서 작성 포인트
제목: [인프라] 배포 후 SSH 접속 장애 (2026-06-10 09:17 ~ 09:22)
심각도: HIGH
영향 범위: 개발팀 전체 SSH 불가 (5분간), 웹서비스 정상
타임라인:
- 09:00 야간 배포 완료
- 09:17 개발팀 "SSH 안 된다" 슬랙 보고
- 09:18 인프라팀 대응 시작, iptables 확인
- 09:19 원인 확정 (SSH 규칙 없음), 복구 명령 실행
- 09:22 SSH 접속 정상 확인, 영구 저장 완료
근본 원인:
배포 스크립트에 iptables -F(전체 초기화) 포함.
재적용 스크립트에서 SSH(22) 허용 규칙 누락.
복구 조치:
iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT
iptables-save > /etc/iptables/rules.v4
재발 방지 계획:
1. 배포 스크립트에서 iptables -F 제거, 개별 규칙 조작 방식으로 변경
2. 배포 후 자동 검증 스크립트 추가 (SSH/HTTP/HTTPS 포트 확인)
3. /etc/iptables/rules.v4 버전 관리에 포함
4. 방화벽 규칙 변경은 변경 관리 프로세스 통해 사전 리뷰 필수화
프로덕션 서버 표준 방화벽 규칙 세트
#!/bin/bash
# 프로덕션 서버 방화벽 표준 설정 스크립트
# 1. policy를 일시적으로 ACCEPT (초기화 중 차단 방지)
## 정책을 ACCEPT로 바꾸지 마세요. 콘솔/OOB와 백업을 먼저 확보하고 ruleset을 검증합니다.
## iptables-restore --test 후 단일 ruleset으로 적용합니다.
## 예시는 운영 서버에 그대로 실행하지 않습니다.
# 2. 기존 규칙 초기화
# 운영 서버에서 -F/-X/-Z를 직접 실행하지 않습니다. 백업한 전체 ruleset을
# `iptables-restore --test`로 검증한 뒤 단일 트랜잭션으로 교체해야 합니다.
# 3. 루프백 허용
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# 4. 기존 연결 허용 (이것이 없으면 이미 열린 세션도 끊김)
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# 5. INVALID 패킷 차단
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
# 6. SSH — 사무실 IP에서만 허용 (변경 필요)
iptables -A INPUT -p tcp --dport 22 -s 203.0.113.0/24 -j ACCEPT
# 7. HTTP/HTTPS
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# 8. ICMP (ping) — 일부 모니터링에 필요
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s -j ACCEPT
# 9. 차단 로그 (rate limiting 적용)
iptables -A INPUT -m limit --limit 5/min --limit-burst 10 \
-j LOG --log-prefix "INPUT_DROP: " --log-level 4
# 10. policy DROP 적용 (마지막에)
iptables -P INPUT DROP
iptables -P FORWARD DROP
# 11. 영구 저장
iptables-save > /etc/iptables/rules.v4
# 12. 검증
echo "=== 적용된 규칙 ==="
iptables -L INPUT -n -v --line-numbers
echo "=== 포트 접근성 확인 ==="
ss -tlnp | grep -E '22|80|443'
DevOps 엔지니어 — 배포 자동화에서 방화벽 관리
Terraform이나 Ansible로 서버를 프로비저닝할 때 방화벽 규칙도 코드로 관리합니다. "수동으로 SSH해서 iptables 명령 치는 것"은 일회성 진단용이고, 실제 운영은 코드로 관리하는 것이 원칙입니다.
Ansible로 선언적 iptables 관리
# Ansible: community.general.iptables 모듈 사용 예시
- name: SSH 허용 (사무실 IP만)
community.general.iptables:
chain: INPUT
protocol: tcp
destination_port: "22"
source: "203.0.113.0/24"
jump: ACCEPT
state: present
- name: 변경사항 영구 저장
community.builtin.command: netfilter-persistent save
Terraform과 클라우드 방화벽 이중화
클라우드 환경에서는 AWS Security Group 또는 GCP Firewall Rules가 1차 방어선 역할을 합니다. iptables는 그 안에서 추가적인 세분화 제어에 사용합니다. 두 레이어를 이중화하면 클라우드 방화벽 설정 오류가 발생해도 iptables가 보호막이 됩니다.
# Terraform: AWS Security Group (1차 방어)
resource "aws_security_group_rule" "ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["203.0.113.0/24"]
security_group_id = aws_security_group.server.id
}
GitOps로 iptables 규칙 관리
iptables 규칙을 Git 저장소에서 관리하면 변경 이력 추적과 리뷰가 가능합니다.
# 규칙 파일을 Git으로 관리
git add /etc/iptables/rules.v4
git commit -m "feat: 모니터링 서버 IP(10.0.1.100) SSH 허용 추가"
# CI/CD 파이프라인에서 자동 검증
# 배포 후 포트 접근성 자동 확인
nc -z -w5 서버IP 22 && echo "SSH OK" || (echo "SSH FAIL"; exit 1)
nc -z -w5 서버IP 80 && echo "HTTP OK" || (echo "HTTP FAIL"; exit 1)
핵심 원칙: 방화벽 규칙은 코드로 관리하고, 변경은 PR 리뷰를 거치며, 배포 후 자동 검증으로 확인합니다.
이 모듈을 완전히 익혔다면 할 수 있어야 합니다
각 항목을 직접 해봤다면 체크하세요.
명령어·단축키 빠른 참조
이 모듈에서 다룬 iptables 명령을 실전 옵션·조합과 함께 모았습니다. policy DROP 환경에서는 규칙 순서(-I 삽입 위치)가 중요합니다.
| 명령어/옵션 | 용도 | 자주 쓰는 예 |
|---|---|---|
iptables -L <체인> -n -v --line-numbers | 규칙·카운터·줄번호 확인(진단 첫 명령) | iptables -L INPUT -n -v --line-numbers |
iptables -I INPUT 1 … | 맨 위에 규칙 삽입(policy DROP에선 필수) | iptables -I INPUT 1 -p tcp --dport 22 -j ACCEPT |
iptables -A INPUT … | 체인 맨 끝에 규칙 추가 | iptables -A INPUT -p tcp --dport 80 -j ACCEPT |
iptables -D INPUT <번호> | 규칙 삭제(줄번호 기준) | iptables -D INPUT 3 |
-s <CIDR> | 출발지 IP 제한(SSH 특정 대역만) | iptables -I INPUT 1 -p tcp --dport 22 -s 203.0.113.0/24 -j ACCEPT |
-m conntrack --ctstate ESTABLISHED,RELATED | 아웃바운드 응답 자동 허용(stateful) | iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT |
-j DROP | 조용히 폐기(외부 공격·스캔 차단) | iptables -I INPUT 1 -s 45.142.212.100 -j DROP |
-j REJECT --reject-with | 즉시 거부(내부·개발 디버깅) | iptables -A INPUT -p tcp --dport 22 -j REJECT --reject-with tcp-reset |
-j LOG --log-prefix | 차단 패킷 기록(-m limit로 폭주 방지) | iptables -A INPUT -m limit --limit 5/min -j LOG --log-prefix "DROP: " |
iptables -P <체인> DROP | 체인 기본 정책 지정 | iptables -P FORWARD DROP |
iptables-save > <파일> | 규칙 영구 저장 | iptables-save > /etc/iptables/rules.v4 |
iptables-restore < <파일> | 저장 규칙 일괄 복원 | iptables-restore < /etc/iptables/rules.v4 |
conntrack -L | 연결 추적 테이블 확인(포화 진단) | conntrack -L | wc -l |
iptables -F | 규칙 전체 삭제 — 운영/원격에서 직접 실행하지 말고 검증된 ruleset을 restore | (직접 실행 금지) |
관련 모듈로 더 깊이:
- NAT 변환과 포트 포워딩(Port Forwarding) 작동 원리 — 같은 iptables의 nat 테이블로 DNAT/SNAT를 다루는 다음 단계
- CentOS/Ubuntu 서버 방화벽(firewalld / ufw) 포트 열기 — iptables를 감싸는 firewalld/ufw 추상화 레이어 이해
- ss/lsof로 포트를 점유한 좀비 프로세스 찾아 강제 종료하기 — INPUT 체인이 허용해도 리스닝 프로세스가 없을 때를 가리는 법
다음 모듈에서는 NAT 테이블과 PREROUTING/POSTROUTING 체인을 다룹니다 — 포트 포워딩(DNAT)과 MASQUERADE(SNAT)로 사설 네트워크 트래픽을 외부로 내보내는 실전 패턴을 배웁니다. 오늘 배운 filter 테이블과 nat 테이블이 같은 훅 포인트에서 어떻게 우선순위를 나누어 동작하는지도 다룹니다.