본문 바로가기
삵
Operating System

fail2ban으로 SSH 무차별 대입 자동 차단하기

나나크나로·2026년 9월 28일·조회 1

공개 IP를 붙인 서버라면 sshd 로그에 낯선 IP의 로그인 실패가 하루 종일 쌓인다. 저도 새 서버를 올리고 며칠 지나 journalctl -u sshd를 열어보면 중국, 러시아, 이름 모를 대역에서 root, admin, oracle 계정으로 초당 몇 건씩 로그인을 시도하는 기록이 늘 보인다. 키 인증만 열어두면 뚫리진 않지만 로그가 지저분해지고 리소스도 갉아먹는다. 예전에 iptables 규칙을 손으로 넣던 글을 쓴 적이 있는데, 요즘은 fail2ban에 맡기는 쪽이 훨씬 편하다.

결론부터 말하자면, Rocky/AlmaLinux 9에서는 EPEL의 fail2ban 패키지를 설치하고 fail2ban-firewalld 연동을 켠 뒤 /etc/fail2ban/jail.local에 sshd jail만 활성화하면 된다. fail2ban이 systemd 저널의 sshd 로그를 읽어 실패 횟수를 세고, 임계치를 넘긴 IP를 firewalld의 rich rule로 자동 차단한다. 아래에서 설치, 설정, 확인, 오탐 방지와 해제를 순서대로 다룬다.

1. fail2ban은 무엇을 하는가

fail2ban은 로그 파일이나 systemd 저널을 감시하다가 특정 패턴(예: 로그인 실패)이 정해진 시간 안에 정해진 횟수를 넘으면 방화벽에 차단 규칙을 넣는 데몬이다. 핵심 단위는 jail(감옥)이다. jail 하나는 "어떤 로그를(filter) 어떤 백엔드로 읽어서, 몇 번 실패하면, 어떤 방화벽 동작(action)으로 얼마나 차단할지"를 묶은 설정이다.

자주 나오는 값 세 가지만 먼저 짚는다. maxretry는 차단까지 허용하는 실패 횟수, findtime은 그 횟수를 세는 시간 창, bantime은 차단 유지 시간이다. 즉 findtime 안에 maxretry번 실패하면 bantime만큼 막는다.

2. 설치와 firewalld 연동

fail2ban은 기본 저장소에 없고 EPEL에 있다. EPEL을 먼저 붙인다.

$ sudo dnf install -y epel-release
$ sudo dnf install -y fail2ban fail2ban-firewalld

fail2ban-firewalld를 함께 설치하면 /etc/fail2ban/jail.d/00-firewalld.conf가 깔린다. 이 파일이 차단 동작(banaction)을 iptables가 아니라 firewalld의 rich rule로 바꾼다. 내용은 이렇다.

$ cat /etc/fail2ban/jail.d/00-firewalld.conf
[DEFAULT]
banaction = firewallcmd-rich-rules[actiontype=<multiport>]
banaction_allports = firewallcmd-rich-rules[actiontype=<allports>]

Rocky/AlmaLinux 9는 firewalld가 기본 방화벽이므로 이 연동을 쓰는 편이 맞다. firewalld가 켜져 있는지만 확인한다.

$ systemctl is-active firewalld
active

여기서 한 번 걸리는 지점이 있다. firewalld가 꺼져 있으면 fail2ban이 차단 규칙을 넣을 대상이 없어 jail 기동 시 에러가 난다. firewalld를 안 쓰는 서버라면 00-firewalld.conf를 지우고 banaction을 iptables 계열로 되돌려야 한다. 이 글은 firewalld를 쓰는 기본 구성을 전제로 한다.

3. jail.local에 sshd jail 설정

/etc/fail2ban/jail.conf는 패키지가 관리하는 원본이라 직접 고치지 않는다. 업데이트 때 덮어써진다. 대신 /etc/fail2ban/jail.local을 새로 만들어 필요한 값만 올린다. jail.local이 jail.conf보다 우선한다.

$ sudo vi /etc/fail2ban/jail.local
[DEFAULT]
# 차단에서 항상 제외할 IP (사무실 고정 IP, 모니터링 서버 등)
ignoreip = 127.0.0.1/8 ::1 203.0.113.10
bantime  = 1h
findtime = 10m
maxretry = 5

[sshd]
enabled  = true
backend  = systemd
port     = ssh
maxretry = 4
bantime  = 1h

Rocky/AlmaLinux 9의 sshd 로그는 /var/log/secure가 아니라 systemd 저널로 간다. 그래서 backend = systemd로 지정해 fail2ban이 저널을 직접 읽게 했다. 이 백엔드를 쓰면 logpath를 따로 줄 필요가 없다.

port = ssh는 /etc/services의 22번을 가리킨다. SSH 포트를 바꿨다면 여기에 실제 번호를 적는다(예: port = 2022). 포트를 안 맞추면 차단 규칙이 엉뚱한 포트에 걸려 무력해질 수 있다.

4. 서비스 기동과 상태 확인

설정을 저장했으면 서비스를 켜고 부팅 시 자동 시작으로 등록한다.

$ sudo systemctl enable --now fail2ban
$ systemctl status fail2ban --no-pager

기동됐으면 fail2ban-client로 어떤 jail이 올라왔는지 본다.

$ sudo fail2ban-client status
Status
|- Number of jail:      1
`- Jail list:   sshd

sshd jail 하나를 콕 집어 보면 감시 중인 실패 건수와 현재 차단된 IP 목록이 나온다.

$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     37
|  `- Journal matches:  _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned: 1
   |- Total banned:     5
   `- Banned IP list:   198.51.100.23

차단이 실제로 firewalld에 반영됐는지는 rich rule 목록으로 교차 확인한다. fail2ban이 넣은 규칙이 보인다.

$ sudo firewall-cmd --list-rich-rules
rule family="ipv4" source address="198.51.100.23" port port="22" protocol="tcp" reject

차단 이벤트를 사후에 확인하려면 fail2ban 로그를 본다. 어떤 IP를 언제 Ban했는지 남는다.

$ sudo grep 'Ban' /var/log/fail2ban.log | tail
2026-09-28 11:20:41,003 fail2ban.actions [1123]: NOTICE [sshd] Ban 198.51.100.23

5. 정상 IP 오탐 방지

실무에서 가장 조심할 부분이다. 사무실이나 VPN에서 비밀번호를 몇 번 잘못 치면 나 자신이 차단돼 SSH가 끊긴다. 콘솔 접근이 없는 클라우드 인스턴스라면 곤란해진다.

가장 확실한 예방은 ignoreip에 신뢰하는 대역을 등록하는 것이다. 위 설정의 203.0.113.10 자리에 회사 고정 IP나 점프 서버 대역을 넣는다. 공백으로 여러 개를 나열하고 CIDR 표기도 된다.

ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24 198.18.5.6

ignoreip에 들어간 주소는 실패를 아무리 쌓아도 차단되지 않는다. 단, 값을 바꾼 뒤에는 반드시 재적용한다.

$ sudo fail2ban-client reload sshd

차단 정책을 너무 공격적으로 잡는 것도 오탐을 늘린다. maxretry를 1~2로 두면 오타 한두 번에 바로 막힌다. 사람이 쓰는 서버라면 4~5 정도가 무난하다. 자동화 계정만 붙는 서버라면 더 낮춰도 된다.

6. 잘못 막힌 IP 해제하기

이미 차단된 주소는 unban으로 즉시 푼다. jail을 지정하지 않으면 모든 jail에서 해당 IP를 해제한다.

$ sudo fail2ban-client unban 198.51.100.23
$ sudo fail2ban-client set sshd unbanip 198.51.100.23   # sshd jail만 지정해 해제

해제 뒤에는 status sshd의 Banned IP list와 firewall-cmd --list-rich-rules에서 해당 규칙이 사라졌는지 확인한다. 두 곳 모두에서 빠져야 정상이다.

차단된 IP가 하나도 없는 초기 상태로 되돌리려면 jail을 통째로 비운다.

$ sudo fail2ban-client unban --all

7. 재범 IP를 더 오래 막기(선택)

같은 IP가 차단 해제 직후 또 들어오는 경우가 흔하다. 이럴 때는 반복 차단마다 bantime을 점점 늘리는 점진적 차단을 켠다. jail.local의 [DEFAULT]에 넣는다.

[DEFAULT]
bantime.increment = true
bantime.factor    = 2
bantime.maxtime   = 1w

bantime.increment = true면 재범할수록 차단 시간이 factor 배씩 늘어나고 maxtime에서 상한이 걸린다. 처음 1시간이던 차단이 반복되면 하루, 일주일까지 길어진다. 상습 스캐너를 걸러내는 데 효과가 있다.

설정 후에는 전체 재적용을 한다.

$ sudo fail2ban-client reload

마무리

정리하면 순서는 EPEL과 fail2ban 설치, firewalld 연동 확인, jail.local에 sshd jail 활성화, 상태 점검, 그리고 ignoreip로 오탐을 막는 것이다. 키 인증과 PermitRootLogin no 같은 sshd 기본 방어를 갖춘 위에 fail2ban을 얹으면 로그를 어지럽히는 무차별 대입 시도를 사람 손 안 대고 걸러낸다. 설정을 바꿀 때마다 reload를 잊지 않고, 원격 서버에서는 ignoreip에 자기 대역을 먼저 넣어두는 습관만 지키면 된다.

자주 묻는 질문

Rocky/AlmaLinux 9에서 fail2ban이 /var/log/secure를 못 읽는다.

9 계열은 sshd 로그가 systemd 저널로 가서 /var/log/secure에 안 쌓이는 경우가 많다. jail의 backend를 systemd로 지정하면 fail2ban이 저널을 직접 읽는다. jail.local의 [sshd]에 backend = systemd를 넣고 fail2ban-client reload sshd로 재적용한다.

fail2ban을 켰는데 차단이 전혀 안 된다.

먼저 fail2ban-client status sshd로 Journal matches와 Total failed가 잡히는지 본다. failed가 0이면 백엔드나 필터가 로그를 못 읽는 것이고, failed는 쌓이는데 banned가 0이면 maxretry가 너무 높거나 대상 IP가 ignoreip에 들어 있는 경우다. firewalld가 active인지도 확인한다.

내 IP가 차단됐다. 어떻게 푸나.

다른 경로로 접속해 sudo fail2ban-client unban 해당IP를 실행하면 즉시 풀린다. 재발을 막으려면 jail.local의 ignoreip에 그 IP나 대역을 등록하고 fail2ban-client reload sshd로 재적용한다. ignoreip에 들어간 주소는 이후 차단되지 않는다.

banaction을 firewalld로 쓰려면 뭘 설치하나.

fail2ban-firewalld 패키지를 설치하면 /etc/fail2ban/jail.d/00-firewalld.conf가 깔리면서 banaction이 firewallcmd-rich-rules로 바뀐다. 차단된 IP는 firewall-cmd --list-rich-rules에 reject 규칙으로 나타난다.

SSH 포트를 22가 아닌 다른 번호로 바꿨다.

jail.local의 [sshd] 섹션에서 port에 실제 번호를 적는다(예: port = 2022). 기본값 port = ssh는 22번을 가리키므로, 포트를 바꿨는데 이 값을 그대로 두면 차단 규칙이 엉뚱한 포트에 걸려 효과가 없을 수 있다.

관련 글

댓글 0

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.