← 모든 글

fail2ban + ufw 만으로 충분한가

fail2ban과 ufw만으로 결제·정산 서버를 지킬 수 있을까. 두 도구의 실제 가시권과 맹점을 운영 경험으로 짚고, 소규모 팀이 즉시 적용할 수 있는 보완 레이어를 단계별로 정리했다.

왜 결제·정산 서버에서 이 질문이 더 무겁게 느껴지는가

ufw allow 443, apt install fail2ban, 로그 잠깐 확인, 터미널 종료. 이 루틴은 빠르고 익숙하다. 문제는 그 다음에 오는 확신—"이제 됐지"—이 어디까지 유효한가다. 일반적인 웹 서비스 서버라면 이 확신이 70% 정도는 맞다. 그러나 결제 연동과 정산 자동화를 직접 운영하는 팀에게 이 숫자는 현저히 낮아진다.

HEDVION 은 외부 PG사 API 연동, 야간 정산 배치 잡, 실시간 webhook 수신을 단일 또는 소수의 서버 위에서 운영한다. 이 구조에서 서버가 "다운"되는 것보다 "조용히 뚫리는 것"이 훨씬 위험하다. 정산 잡이 돌아가는 서버에 지속적인 비정상 트래픽이 유입된다면, 그것은 단순한 노이즈가 아니라 잠재적 사고의 전조다. ufw 는 443을 열어야 하고, fail2ban 은 웹 레이어를 기본적으로 보지 않는다. 이 조합이 왜 시작점은 될 수 있어도 충분한 끝점은 아닌지를 운영 경험으로 설명한다.

ufw 가 실제로 하는 일과 하지 않는 일

ufw 는 iptables 위에 얹은 편의 래퍼다. 포트 단위 허용·차단은 잘 한다. 그러나 허용된 포트 안에서 발생하는 L7 트래픽은 ufw 의 가시권 밖이다. 443 포트를 통해 들어오는 슬로우 POST, 비정상 페이로드, 로그인 엔드포인트에 대한 저속 반복 요청—ufw 입장에서는 이 모든 것이 "합법적인 트래픽"이다.

우리가 운영하는 webhook 수신 서버를 예로 들면, 외부 PG사의 정산 이벤트 요청과 스캐너가 보내는 프로브 요청이 ufw 레이어에서는 동일하게 취급된다. 포트 443, 프로토콜 TCP—여기까지는 같다. IP 화이트리스트로 보완할 수 있지만, PG사의 발신 IP가 다수이거나 변동될 경우 관리 부담이 상당하다. 결론적으로 ufw 는 "불필요한 포트를 닫는 도구"로만 이해하는 것이 정확하다. 애플리케이션 레이어의 방어를 ufw 에 기대하면 반드시 빈틈이 생긴다.

fail2ban 의 실제 한계: 수치로 보기

우리 서버 중 하나의 /var/log/auth.log 를 2주간 분석한 결과, 평일 기준 하루 8001,200건의 SSH 접속 시도가 들어왔다. fail2ban 기본 설정(maxretry=5, findtime=600s, bantime=3600s)은 단일 IP 가 10분 안에 5회 이상 실패하면 1시간 차단한다. 이 설정으로 하루 평균 4060개 IP 가 차단되었고, 대부분은 3~5회 실패 직후였다. 단순 브루트포스 봇에는 효과적이다.

그러나 같은 기간 로그에서 다른 패턴도 발견했다. 서로 다른 IP 2030개가 시간당 12회씩 분산 시도하는 흐름이었다. 각 IP 는 임계값(maxretry=5)을 절대 넘지 않으므로 fail2ban 은 아무것도 차단하지 않는다. maxretry 를 1~2로 낮추면 탐지는 가능하지만, 공유 NAT 환경의 팀원이 SSH 에 접근할 경우 정상 사용자까지 차단되는 False Positive 가 급증한다. 이 트레이드오프는 설정값 하나로 해결되지 않는다. 웹 레이어도 마찬가지다. fail2ban 의 기본 jail 은 sshd 에 집중되어 있다. nginx 나 애플리케이션 로그를 감시하려면 jail 을 직접 작성해야 하고, 작성하지 않으면 웹 엔드포인트에 대한 반복 요청은 fail2ban 이 전혀 개입하지 않는다.

우리 팀의 시나리오: 정산 자동화 서버에서 겪은 일

HEDVION 의 정산 파이프라인은 야간 배치와 실시간 webhook 처리 두 축으로 돌아간다. 야간 배치는 외부 PG사에서 일별 거래 데이터를 내려받아 집계 후 내부 DB에 쓰고, webhook 서버는 결제 이벤트를 즉시 수신해 정산 상태를 갱신한다. 두 서버 모두 443 포트가 공개 인터넷에 열려 있고, 초기에는 fail2ban + ufw 만으로 운영했다.

어느 날 webhook 수신 서버의 CPU 가 특정 시간대에 간헐적으로 치솟기 시작했다. nginx 로그를 열었더니, 정산 webhook 경로(/api/v1/settlement/event)에 외부에서 반복 POST 요청이 유입되고 있었다. fail2ban 은 아무것도 차단하지 않았다—웹 로그 jail 이 없었기 때문이다. ufw 는 443 포트가 정상 운영 중이니 그대로 통과시켰다. 실제 공격인지 크롤러 오작동인지는 확인하지 못했지만, 그 시간대 야간 정산 잡이 지연되었고 이는 명확한 운영 영향이었다. 이후 조치는 세 단계였다: nginx limit_req_zone 으로 해당 경로에 분당 30회 제한 적용, PG사 제공 서명 검증을 페이로드 레벨에서 추가, Cloudflare 프록시로 원본 서버 IP 노출 차단. 이 세 가지 적용 후, Cloudflare WAF 에서 비정상 요청을 차단하기 시작했고 nginx 에는 더 이상 도달하지 않았다.

두 도구를 넘어서: 우리가 실제로 추가한 레이어

SSH 키 인증 강제 가 가장 먼저다. PasswordAuthentication no 한 줄이 브루트포스 위협의 90% 이상을 무력화한다. 이 설정을 하고 나면 fail2ban 의 SSH jail 은 사실상 보험 역할로 내려간다. 팀에서 이 설정 없이 fail2ban 만 믿는 것은 안전벨트 없이 에어백에만 의존하는 것과 같다.

nginx 레이트 리미팅 은 두 번째다. 인증·결제 엔드포인트에 limit_req_zone $binary_remote_addr zone=payment:10m rate=30r/m 을 적용하고 burst=10 nodelay 로 순간 트래픽을 수용한다. 429 응답이 발생하기 시작하면 nginx 로그에서 바로 포착되고, 여기에 fail2ban nginx jail 을 연결하면 연속 429 IP 를 자동 차단하는 파이프라인이 완성된다. 두 도구를 연결하면 각각의 단점이 보완된다. Cloudflare 프록시 는 소규모 팀에게 비용 대비 효과가 가장 높은 레이어다. 무료 플랜만으로도 원본 IP 보호, 기본 DDoS 방어, 알려진 악성 IP 차단이 제공된다. 우리 기준 Cloudflare 적용 후 nginx 에 도달하는 비정상 트래픽이 약 70% 감소했다. 단, Cloudflare 가 앞에 있으면 fail2ban 이 보는 IP 는 항상 Cloudflare IP 가 된다. nginx 에서 set_real_ip_fromreal_ip_header CF-Connecting-IP 설정을 빠뜨리면 fail2ban 이 Cloudflare IP 자체를 차단하는 치명적인 실수가 발생한다—반드시 확인해야 한다.

지금 바로 써먹을 수 있는 체크리스트

이론보다 실행이다. 우리 팀이 새 서버를 셋업할 때 거치는 순서 그대로다.

1단계 — SSH 강화 (5분)

  • sshd_config 에서 PasswordAuthentication no, PubkeyAuthentication yes 확인
  • 기본 22번 포트를 비표준 포트로 변경 (ufw 와 sshd_config 동시 수정)
  • AllowUsers 또는 AllowGroups 로 SSH 허용 계정 명시적 제한

2단계 — fail2ban 웹 jail 추가 (30분)

  • nginx 400·403·429 반복 발생 IP 차단 jail 작성
  • 애플리케이션 로그(로그인 실패 등) 파싱 커스텀 jail 추가
  • bantime 을 기본 1시간에서 24시간 이상으로 상향—재시도 비용을 높인다

3단계 — nginx 레이트 리미팅 (20분)

  • 인증·결제·민감 엔드포인트에 limit_req_zone 적용
  • API 서버라면 전체 경로에 기본 리미팅, 민감 경로에는 별도로 더 타이트하게

4단계 — Cloudflare 연동 시 필수 확인 (10분)

  • nginx 에서 set_real_ip_from + real_ip_header CF-Connecting-IP 설정
  • fail2ban 이 실제 클라이언트 IP 를 인식하는지 로그에서 직접 확인

5단계 — 모니터링 루프 확립

  • fail2ban-client status 주기적 확인 스크립트 또는 알림 연결
  • nginx 5xx·429 응답 빈도를 로그 집계 또는 Grafana 로 추적
  • 주 1회 iptables -L -n --line-numbers | grep DROP 으로 차단 현황 확인

fail2ban + ufw 는 시작점이다. 끝점이 되어서는 안 된다. 특히 결제와 정산이 엮인 서버라면, 각 도구의 가시권이 어디서 끝나는지를 먼저 그려 놓고 그 빈틈을 의도적으로 채워야 한다. 도구를 신뢰하기 전에 그 도구가 보지 못하는 것을 먼저 목록으로 만드는 것—그것이 우리 팀이 배운 가장 실용적인 보안 습관이다.

* 위 링크는 인프런 affiliate 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.

📚 추천 강의
한 입 크기로 잘라먹는 바이브코딩 (with Claude Code)
Claude Code로 바이브코딩, 개발자라면 꼭 들어야 할 필수 강의
강의 보러가기 →

* 위 추천 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.