nftables는 2026년 현재 리눅스 커널 방화벽의 사실상 표준입니다. iptables·ip6tables·arptables·ebtables를 하나의 프레임워크로 묶고, 원자적 규칙 업데이트와 IPv4/IPv6 이중 스택을 단일 inet 테이블에서 처리하며, 세트와 맵을 통해 선형 O(n) 대신 상수 시간 O(1) 룩업을 제공하죠. RHEL 10에 와서는 iptables-nft 호환 계층까지 사라졌기 때문에, 이제 nftables 네이티브 문법을 익히지 않으면 프로덕션 방화벽을 유지할 방법이 없습니다. 솔직히 말하면, 저도 지난 프로젝트에서 iptables 룰 3천 줄을 한 번에 nftables로 옮기다가 한 서버를 잠깐 잃은 적이 있어요. 그래서 이 가이드는 그런 실수를 피할 수 있게 설계 원칙, 마이그레이션 절차, 실전 규칙 세트, 브루트포스 차단·rate limiting 같은 고급 패턴까지 순서대로 다룹니다.
nftables는 iptables의 후속 도구로, inet 패밀리 하나로 IPv4·IPv6를 동시에 필터링하고 규칙 세트를 원자적으로 교체합니다.
RHEL 10부터 iptables-nft와 ipset 패키지가 제거됐으니, RHEL 8/9 사용자는 지금 iptables-restore-translate로 마이그레이션해야 합니다.
기본 정책은 항상 drop, 상태 추적은 ct state established,related accept가 첫 번째 규칙이어야 합니다.
세트(set)와 맵(map)은 대규모 IP 블록리스트 처리 시 iptables 대비 수천 배 빠른 O(1) 조회를 제공합니다.
Fail2Ban 없이도 meter와 limit rate만으로 SSH 무차별 대입 공격을 커널 레벨에서 차단할 수 있습니다.
변경은 반드시 nft -c -f로 검증한 뒤 적용하고, /etc/nftables.conf에 저장해 재부팅 후에도 유지되도록 합니다.
2026년에도 iptables를 계속 쓰면 안 되는 이유
iptables는 20년 넘게 리눅스 방화벽의 대명사였지만, 2026년 시점에서는 그냥 레거시입니다. Red Hat이 RHEL 10 방화벽 문서에서 공식화한 것처럼, RHEL 10에서는 iptables-nft 호환 계층과 ipset 패키지가 모두 제거됐습니다. Debian 13(trixie)과 Ubuntu 24.04 LTS 이후 릴리스도 nftables를 기본 백엔드로 채택했고, /usr/sbin/iptables는 nftables 룰셋을 흉내 내는 shim에 불과하죠.
기술적으로도 iptables는 규칙이 늘어날수록 확장성이 급격히 나빠집니다. iptables는 각 패킷에 대해 규칙을 선형(O(n))으로 순회하기 때문에, 10,000개의 IP 블록리스트를 처리하면 최악의 경우 매 패킷마다 10,000번의 비교가 발생합니다. 반면 nftables 세트는 해시 기반 룩업이라 룰 개수와 무관하게 상수 시간에 매칭되고요. 게다가 iptables는 규칙 교체 시 원자성이 없어 재적용 도중에 패킷이 새는 순간이 존재하는데, nftables는 트랜잭션 단위로 룰셋 전체를 커밋하거나 롤백합니다. 성능·안전성·유지보수성 모두 nftables 쪽이 정답이에요.
nftables 아키텍처: 테이블, 체인, 훅, 우선순위
nftables를 이해하는 핵심은 테이블 → 체인 → 규칙의 3계층 구조와, 각 체인이 커널 네트워킹 스택의 어떤 훅(hook)에 어떤 우선순위(priority)로 붙는지를 정확히 파악하는 것입니다. 테이블은 네임스페이스 역할을 하고 반드시 하나의 패밀리(family)에 속하죠. 실전에서 90% 정도는 inet 패밀리를 선택하면 됩니다. inet은 IPv4와 IPv6 패킷을 동시에 처리해 룰셋을 이중으로 유지할 필요가 없거든요.
체인은 두 종류입니다. 기본 체인(base chain)은 type·hook·priority·policy를 갖고 패킷 진입점 역할을 하며, 일반 체인(regular chain)은 jump·goto의 대상이 되어 규칙을 논리적으로 그룹화합니다. 필터링 훅은 순서대로 ingress → prerouting → input | forward → output → postrouting이 있고, 각 훅에 여러 체인이 붙을 때는 priority 숫자가 낮은 체인이 먼저 실행됩니다. 관례상 filter는 0, mangle은 -150, nat prerouting은 -100을 사용해요.
# inet 패밀리로 IPv4·IPv6 통합 테이블 생성
sudo nft add table inet filter
# input 체인: filter 훅에 policy drop
sudo nft add chain inet filter input \
'{ type filter hook input priority filter; policy drop; }'
# forward 체인: 포워딩 트래픽 기본 차단
sudo nft add chain inet filter forward \
'{ type filter hook forward priority filter; policy drop; }'
# output 체인: 나가는 트래픽은 기본 허용
sudo nft add chain inet filter output \
'{ type filter hook output priority filter; policy accept; }'
여기서 policy drop은 최소 권한 원칙의 구현입니다. 어떤 규칙에도 매칭되지 않은 패킷은 무조건 거부되므로, 이후 추가되는 규칙은 명시적으로 허용해야 할 것만 정의하면 됩니다. 이 뼈대 위에 실제 서비스 규칙을 얹어 나가는 방식이죠.
설치와 기본 방화벽 골격 구축
대부분의 최신 배포판은 nftables 패키지가 기본 설치되어 있지만, 최소 이미지에서는 별도로 설치해야 합니다. 설치 후에는 systemd 유닛을 활성화해 부팅 시 자동으로 룰셋이 로드되도록 합니다. systemd 서비스 자체의 격리 강화는 systemd 서비스 보안 강화 가이드를 함께 참고하세요. (방화벽 하나만 잠그고 서비스는 그대로 두면 절반짜리 방어입니다.)
# Debian/Ubuntu
sudo apt install -y nftables
# RHEL/Fedora/Rocky/Alma
sudo dnf install -y nftables
# Arch
sudo pacman -S nftables
# 서비스 활성화
sudo systemctl enable --now nftables
# 현재 룰셋 확인
sudo nft list ruleset
운영 서버에서는 nft 명령을 한 줄씩 입력하는 대신 /etc/nftables.conf에 선언적으로 룰셋을 작성해 원자적으로 재적용하는 방식을 권장합니다. 아래는 웹 서버 한 대에 즉시 사용 가능한 최소 하드닝 프로필이에요.
#!/usr/sbin/nft -f
# /etc/nftables.conf
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
# 1) 루프백은 무조건 허용
iif lo accept
# 2) 이미 수립된/관련 연결은 통과 (성능 핵심)
ct state established,related accept
# 3) invalid 상태는 즉시 차단
ct state invalid drop
# 4) ICMP/ICMPv6 필수 타입만 허용
ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded, parameter-problem } accept
ip6 nexthdr icmpv6 icmpv6 type { echo-request, destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept
# 5) SSH (분당 5회 제한으로 브루트포스 완화)
tcp dport 22 ct state new limit rate 5/minute accept
# 6) HTTP/HTTPS 공개
tcp dport { 80, 443 } accept
# 7) 나머지는 로그 후 drop (샘플링으로 로그 홍수 방지)
limit rate 10/minute log prefix "nft-drop: " level info
counter drop
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}
파일을 저장한 뒤엔 반드시 먼저 문법 검증부터 실행합니다. nft -c -f /etc/nftables.conf는 룰셋을 실제로 적용하지 않고 파싱만 수행하죠. 원격 SSH 세션에서 잘못된 룰을 적용해 자기 자신을 잠가버리는 사고를 방지하는 핵심 습관입니다. (제가 처음 nftables를 배울 때 이 단계를 건너뛰다가 딱 한 번 크게 데었습니다.)
iptables에서 nftables로 마이그레이션하는 방법
기존 iptables 룰셋을 nftables로 옮기는 가장 안전한 방법은 iptables-save로 덤프한 뒤 iptables-restore-translate로 자동 변환하는 것입니다. 이 유틸리티는 iptables 확장 대부분을 nftables 문법으로 변환하지만, 일부 미지원 확장은 # 주석으로 남기므로 결과물은 반드시 검토해야 합니다.
# 1) 기존 규칙 백업
sudo iptables-save > /root/iptables-v4.dump
sudo ip6tables-save > /root/iptables-v6.dump
# 2) nftables 문법으로 변환
sudo iptables-restore-translate -f /root/iptables-v4.dump > /etc/nftables/from-iptables.nft
sudo ip6tables-restore-translate -f /root/iptables-v6.dump > /etc/nftables/from-ip6tables.nft
# 3) 변환되지 않은 확장 검색 (수동 처리 필요)
grep -n '^#' /etc/nftables/from-iptables.nft
# 4) 문법 검증 후 원자적 적용
sudo nft -c -f /etc/nftables/from-iptables.nft
sudo nft -f /etc/nftables/from-iptables.nft
실전에서 자주 걸리는 함정 두 가지가 있습니다. 첫째, -m recent나 -m string 같은 iptables 확장은 nftables에서 meter·@set·ct 표현식으로 대체해야 해서 자동 변환이 안 됩니다. 둘째, ipset 기반 룰은 nftables 네이티브 세트(다음 섹션 참고)로 다시 작성하는 편이 훨씬 성능이 좋고요. 변환된 룰셋을 그대로 방치하지 말고, inet 패밀리로 통합하고 세트·맵을 활용해 최적화하는 것이 마이그레이션의 진짜 가치입니다.
상태 기반 필터링과 연결 추적
상태 기반(stateful) 필터링은 nftables 성능과 보안의 핵심입니다. 커널의 conntrack 모듈이 모든 연결을 추적하니까, 이미 허용된 연결의 응답 패킷을 매번 전체 룰셋과 비교할 필요가 없어지죠. 관례는 ct state established,related accept를 input 체인의 첫 번째 규칙으로 두는 것입니다. 이 한 줄이 정상 트래픽의 99%를 즉시 통과시켜, 나머지 규칙은 실제로는 새 연결에만 적용되도록 만들어요.
연결 상태는 다섯 가지입니다. new는 방화벽이 처음 보는 패킷, established는 양방향 통신이 확립된 연결, related는 기존 연결에 부속된 패킷(FTP 데이터 채널, ICMP 오류 메시지 등), invalid는 상태 머신과 맞지 않는 패킷, untracked는 notrack 규칙으로 추적 대상에서 제외된 패킷입니다. invalid는 스캐닝이나 스푸핑 시도인 경우가 많으니 즉시 drop하고, related는 반드시 established와 함께 허용해야 정상 프로토콜이 동작합니다.
# 상태 기반 필터링 표준 5줄
ct state established,related accept
ct state invalid drop
# 새 SSH 연결만 카운트해 로그 남기기
tcp dport 22 ct state new counter log prefix "ssh-new: " accept
# 특정 서비스는 SYN 플러딩 방어 (TCP flags 확인)
tcp flags & (fin|syn|rst|ack) == syn tcp dport 443 \
meter http_syn { ip saddr limit rate 50/second } accept
세트와 맵으로 대규모 룰셋 최적화
nftables가 iptables·ipset을 뛰어넘는 결정적인 이유는 세트(set)와 맵(map)입니다. 세트는 IP·포트·MAC 등의 컬렉션을 해시 자료구조로 저장해 상수 시간 룩업을 제공하고, 맵은 키를 값에 매핑해 규칙 개수를 늘리지 않고도 세밀한 정책을 표현하죠. 예를 들어 10만 개의 위협 IP 블록리스트를 처리한다고 해봅시다. iptables는 10만 개의 규칙이 필요하지만, nftables는 세트 하나와 규칙 하나면 끝납니다. 처음 봤을 때 저도 조금 허탈했어요.
table inet filter {
# 관리자용 신뢰 IP 세트
set admin_v4 {
type ipv4_addr
flags interval
elements = { 10.0.0.0/8, 192.168.0.0/16, 203.0.113.42 }
}
# 위협 IP 동적 블록리스트 (fail2ban 대체 가능)
set threat_v4 {
type ipv4_addr
flags dynamic, timeout
timeout 24h
size 100000
}
# 포트 → 서비스 이름 맵 (로깅용)
map port_service {
type inet_service : string
elements = { 22 : "ssh", 80 : "http", 443 : "https", 3306 : "mysql" }
}
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif lo accept
# 위협 IP는 즉시 차단
ip saddr @threat_v4 counter drop
# 관리자 IP는 SSH·MySQL 자유 접근
ip saddr @admin_v4 tcp dport { 22, 3306 } accept
# 공개 서비스
tcp dport { 80, 443 } accept
}
}
flags dynamic, timeout이 붙은 세트는 규칙 액션에서 동적으로 원소를 추가할 수 있고, TTL이 지나면 자동으로 만료됩니다. 이 조합이 바로 fail2ban 없이 커널 내부에서 브루트포스 IP를 자동 격리하는 기반이 되죠. 자세한 활용은 다음 섹션에서 다룹니다. 참고로 이 룰셋을 실시간으로 관찰하고 싶다면, eBPF 기반 리눅스 런타임 보안 가이드에서 소개한 Falco·Tetragon과 결합하면 굉장히 강력해집니다.
SSH 무차별 대입 공격을 nftables만으로 막는 법
Fail2Ban은 여전히 유용하지만, 로그를 파싱해 iptables에 룰을 추가하는 방식이라 지연이 크고 로그 회전과 경쟁 조건에 취약합니다. nftables는 meter와 동적 세트를 결합해 커널 레벨에서 실시간으로 무차별 대입 공격을 감지하고 차단할 수 있어요. SSH 자체의 하드닝, 특히 포스트 양자 암호화 설정은 OpenSSH 10.x 포스트 양자 암호화 가이드를 함께 적용하면 다층 방어가 완성됩니다.
table inet filter {
set ssh_blackhole {
type ipv4_addr
flags dynamic, timeout
timeout 1h
size 65536
}
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
ct state invalid drop
iif lo accept
# 이미 격리된 IP는 조용히 drop
ip saddr @ssh_blackhole drop
# 새 SSH 연결이 분당 5회를 초과하면 1시간 격리
tcp dport 22 ct state new \
add @ssh_blackhole { ip saddr limit rate over 5/minute } \
log prefix "ssh-brute: " drop
# 정상 SSH 접근
tcp dport 22 ct state new accept
tcp dport { 80, 443 } accept
}
}
동작 원리는 이렇습니다. add @ssh_blackhole { ip saddr limit rate over 5/minute }는 소스 IP별로 분당 5회 이상 새 SSH 연결이 감지되면 그 IP를 ssh_blackhole 세트에 즉시 추가하고, 이후 1시간 동안 모든 트래픽을 차단합니다. 임계값을 넘지 않은 정상 사용자는 rate over 조건이 false이므로 세트에 추가되지 않고 다음 규칙에서 통과되죠. 로그는 journalctl -k -f -g nft-drop 같은 명령으로 실시간 확인할 수 있습니다. 저는 이 방식으로 스크립트 키디 트래픽을 커널 밖으로 나가지도 않고 흡수한 뒤부터, Fail2Ban 프로세스 자체를 서버에서 지웠어요.
규칙 영속화, 로깅, 감사
지금까지 만든 룰셋을 재부팅 후에도 유지하려면 /etc/nftables.conf에 저장하고 nftables.service가 이 파일을 로드하도록 설정하면 됩니다. 배포판마다 systemd 유닛 이름이 조금씩 다르므로 확인 후 적용하세요.
# 현재 활성 룰셋을 파일로 덤프 (헤더 포함)
sudo sh -c 'echo "#!/usr/sbin/nft -f" > /etc/nftables.conf'
sudo sh -c 'echo "flush ruleset" >> /etc/nftables.conf'
sudo nft list ruleset >> /etc/nftables.conf
# 문법 검증
sudo nft -c -f /etc/nftables.conf
# 부팅 시 자동 로드
sudo systemctl enable --now nftables
# 감사 로그 실시간 모니터링
sudo journalctl -u nftables -f
sudo dmesg -w | grep nft
로깅은 두 가지 관점에서 설계해야 합니다. 첫째, 차단된 트래픽 자체를 파악하기 위한 프리픽스 로그는 limit rate로 반드시 억제해야 로그 홍수와 디스크 소진을 막을 수 있어요. 둘째, 규칙 자체의 변경 이력을 추적하려면 auditd로 /etc/nftables.conf와 /etc/nftables/ 디렉터리를 감시하고, 변경 시 Wazuh 같은 HIDS 플랫폼이나 SIEM으로 알림이 발생하도록 파이프라인을 구성합니다. 방화벽 룰셋 자체가 컴플라이언스 감사의 1차 증거이니만큼, Git 저장소에 nftables.conf를 커밋해 변경 이력을 남기는 관행을 권장합니다. (개인적으로 이 습관 하나가 감사 대응 시간을 정말 크게 줄여줬어요.)
자주 묻는 질문
nftables와 firewalld를 함께 사용할 수 있나요?
기술적으로는 firewalld가 nftables를 백엔드로 사용하지만, 직접 작성한 /etc/nftables.conf와 firewalld가 동시에 활성화되면 룰셋 원자성이 깨져 예측 불가능한 동작이 발생합니다. 둘 중 하나만 활성화하고 나머지는 systemctl disable --now로 비활성화하세요.
nftables 규칙을 재부팅 후에도 유지하려면 어떻게 하나요?
현재 룰셋을 nft list ruleset > /etc/nftables.conf로 저장하고 systemctl enable --now nftables를 실행하세요. 파일 최상단에 #!/usr/sbin/nft -f와 flush ruleset을 반드시 넣어야 부팅 시 이전 상태가 남지 않고 깨끗하게 로드됩니다.
nftables에서 특정 IP를 영구 차단하려면 어떤 명령을 쓰나요?
정적 세트에 원소를 추가하는 방식이 관리하기 좋습니다. nft add element inet filter blocklist_v4 { 198.51.100.7 }처럼 세트에 IP를 추가하면 관련 규칙에 자동으로 반영됩니다. 재부팅 후에도 유지하려면 /etc/nftables.conf의 세트 정의에 해당 IP를 명시하세요.
nftables가 iptables보다 실제로 얼마나 빠른가요?
규칙 수가 적으면 체감 차이는 미미하지만, 수천에서 수만 개의 IP 매칭 규칙에서는 극적입니다. iptables는 O(n) 선형 순회이므로 규칙 수에 비례해 지연이 증가하는 반면, nftables 세트는 O(1) 해시 룩업이라 규칙 수와 무관하게 상수 시간에 매칭됩니다. 대규모 블록리스트 시나리오에서 10~100배 이상의 처리량 차이가 보고됩니다.
SSH 무차별 대입 공격을 nftables만으로 막을 수 있나요, Fail2Ban이 여전히 필요한가요?
단순한 IP 기반 브루트포스라면 nftables의 meter와 동적 timeout 세트만으로 충분하고, 오히려 로그 파싱 오버헤드가 없어서 더 빠릅니다. 다만 여러 서비스에 걸친 복합 패턴 감지나 로그 상관 분석이 필요하다면 Fail2Ban이나 CrowdSec 같은 도구를 nftables 앞단에 두는 하이브리드 구성이 유리해요.