13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] 로그 분석

  • [Linux] journald 로그 관리: systemd 실전 트러블슈팅 5가지

    [Linux] journald 로그 관리: systemd 실전 트러블슈팅 5가지

    [Linux] journald 로그 관리: systemd 실전 트러블슈팅 5가지

    서버 장애는 꼭 바쁜 시간에 터지더라고요. 특히 journald 로그 관리가 제대로 안 되어 있으면, 분명 에러는 났는데 로그가 어디 갔는지부터 찾게 됩니다. 저도 홈랩에서 Rocky Linux, Ubuntu 계열 서버를 굴리면서 처음엔 /var/log만 뒤지다가 시간을 꽤 날렸습니다. 근데 systemd 기반 배포판에서는 journalctl 하나만 제대로 익혀도 장애 대응 속도가 확 달라지거든요. 이번 글에서는 제가 실제로 자주 쓰는 Linux 트러블슈팅 방식 기준으로, 운영 중 바로 써먹을 수 있는 로그 분석 팁 5가지를 정리해보겠습니다.

    journald 로그 관리 개요와 systemd 로그 수집 구조 이미지

    systemd와 journald가 서비스 로그를 수집하고 조회하는 전체 흐름을 보여주는 개요 이미지입니다.

    1. journald가 중요한 이유: 파일 로그만 보던 습관에서 벗어나야 합니다

    쉽게 말해 journald는 systemd 환경에서 로그를 모아두는 중앙 저장소 역할을 합니다. 전통적인 텍스트 로그 파일처럼 보이는 방식과는 조금 다르죠. 서비스별 로그, 부팅 세션별 로그, 우선순위(priority), 커널 메시지까지 한 번에 엮어서 볼 수 있다는 게 핵심입니다.

    처음엔 이게 뭔가 싶었는데, 실제로 써보니까 특정 서비스가 언제 죽었는지, 재시작되기 직전에 어떤 에러가 있었는지 추적하기가 훨씬 편했습니다. 특히 컨테이너 호스트나 작은 홈서버처럼 서비스 수가 늘어나는 환경에서는 로그가 여기저기 흩어져 있으면 진짜 피곤하거든요.

    • journalctl: journald 로그 조회 도구
    • persistent logging(영구 저장): 재부팅 후에도 로그 유지
    • priority(우선순위): error, warning 같은 중요도 기준 필터링
    • boot scope(부팅 범위): 특정 부팅 시점 로그만 추적

    2. 기본 개념 먼저: journalctl은 어떻게 봐야 덜 헷갈릴까

    저도 처음엔 journalctl 치고 로그가 너무 많이 나와서 바로 닫았습니다. 그래서 저는 아래 순서로 익히는 걸 추천합니다.

    1. 전체 로그보다 서비스 단위로 보기
    2. 시간 범위를 자르기
    3. 우선순위를 걸어서 에러만 보기
    4. 부팅 단위로 사고 시점을 좁히기

    가장 먼저 익혀둘 명령은 이 정도입니다.

    # 전체 로그 최근 부분 보기
    journalctl -e
    
    # 특정 서비스 로그 보기
    journalctl -u nginx.service
    
    # 최근 부팅 기준 로그 보기
    journalctl -b
    
    # 에러 레벨만 보기
    journalctl -p err
    
    # 실시간 추적
    journalctl -f

    여기서 중요한 포인트! -u, -b, -p, --since만 익혀도 현장에서 체감이 큽니다. 쓸데없이 로그 바다에 빠지지 않게 해주거든요.

    3. 실전 트러블슈팅 1: 로그가 너무 많아서 원인을 못 찾을 때

    이건 정말 흔합니다. 로그는 있는데 너무 많아요. 특히 장시간 운영한 서버에서 전체 로그를 그냥 열면 필요한 줄 찾다가 집중력이 먼저 떨어집니다. 제가 직접 해보니 이럴 땐 시간 범위 + 서비스명 + 우선순위를 같이 거는 게 제일 효율적이었습니다.

    3-1. 시간 범위로 자르기

    journalctl --since "2026-07-15 09:00:00" --until "2026-07-15 10:00:00"
    
    journalctl --since "30 min ago"
    
    journalctl --since today

    장애가 9시 20분쯤 났다는 제보가 있으면, 굳이 하루치 전체를 볼 필요가 없죠. 이 범위를 먼저 줄이는 것만으로도 노이즈가 꽤 사라집니다.

    3-2. 서비스 단위로 좁히기

    journalctl -u docker.service --since "30 min ago"
    journalctl -u ssh.service -e
    journalctl -u NetworkManager.service -b

    예를 들어 SSH 접속 장애면 ssh.service, 컨테이너 이슈면 docker.service부터 보시면 됩니다. 서비스 이름이 헷갈릴 때는 아래처럼 확인해두면 편합니다.

    systemctl list-units --type=service
    systemctl status nginx.service

    3-3. 에러 우선순위만 보기

    journalctl -p warning
    journalctl -p err
    journalctl -p crit..alert

    저는 급할 때 journalctl -p err -b부터 봅니다. 큰 줄기부터 잡고, 그다음 상세 로그로 내려가는 방식이 훨씬 빠르더라고요.

    journald 로그 관리에서 시간 범위와 서비스 필터를 적용하는 로그 분석 이미지

    시간 범위, 서비스, 우선순위 필터를 조합해 로그를 줄여나가는 과정을 보여주는 이미지입니다.

    4. 실전 트러블슈팅 2: 재부팅 후 로그가 사라지는 문제

    처음 홈랩을 꾸렸을 때 이걸로 꽤 삽질했습니다. 분명 어젯밤에 장애가 있었는데, 아침에 다시 보니 로그가 안 남아 있는 거예요. 이유는 간단했습니다. persistent logging(영구 저장)이 꺼져 있었던 겁니다.

    4-1. 현재 저장소 상태 확인

    journalctl --disk-usage
    ls -ld /var/log/journal

    /var/log/journal 디렉터리가 없으면 메모리 기반 저장만 되는 경우가 있습니다. 배포판 설정에 따라 동작이 다를 수 있어서, 실제 상태를 먼저 확인하는 게 안전합니다.

    4-2. 영구 저장 활성화

    sudo mkdir -p /var/log/journal
    sudo systemd-tmpfiles --create --prefix /var/log/journal
    sudo systemctl restart systemd-journald

    환경에 따라 /etc/systemd/journald.conf에서 저장 정책을 명시하는 게 더 깔끔할 때도 있습니다.

    sudo editor /etc/systemd/journald.conf
    [Journal]
    Storage=persistent
    SystemMaxUse=1G
    RuntimeMaxUse=200M
    MaxRetentionSec=1month

    설정 후에는 데몬을 다시 읽혀줍니다.

    sudo systemctl restart systemd-journald
    journalctl -b -1

    -b -1이 보이면 이전 부팅 로그까지 남고 있다는 뜻입니다. 드디어 됐다! 싶은 순간이 여기서 나오죠.

    5. 실전 트러블슈팅 3: 디스크가 차오르는 원인이 journald일 때

    로그는 남겨야 하지만, 무한정 쌓아두면 결국 디스크를 압박합니다. 특히 작은 SSD나 VM 디스크에서는 이게 꽤 치명적이거든요. journald 로그 관리에서 운영자가 가장 먼저 챙겨야 할 부분 중 하나가 용량 정책입니다.

    5-1. 현재 사용량 확인

    journalctl --disk-usage

    5-2. 즉시 정리하기

    # 7일보다 오래된 로그 삭제
    sudo journalctl --vacuum-time=7d
    
    # 총 사용량을 500M 이하로 줄이기
    sudo journalctl --vacuum-size=500M
    
    # 파일 개수 기준 정리
    sudo journalctl --vacuum-files=10

    운영 중 급하게 용량을 비워야 할 때 정말 유용합니다. 다만 무작정 지우기 전에 꼭 필요한 장애 분석이 끝났는지는 확인하셔야 합니다.

    5-3. 아예 정책으로 제한하기

    [Journal]
    SystemMaxUse=1G
    SystemKeepFree=500M
    SystemMaxFileSize=100M
    MaxRetentionSec=1month

    저는 홈랩에서는 과하게 크게 잡지 않습니다. 분석 가능한 기간은 유지하되, 디스크 여유 공간도 남겨야 하니까요. 여기서 중요한 건 숫자 자체보다 정책을 명시해두는 습관입니다.

    옵션 의미 언제 유용한가
    SystemMaxUse journal 전체 최대 사용량 디스크 점유 상한을 명확히 두고 싶을 때
    SystemKeepFree 남겨둘 최소 여유 공간 서비스 디스크 고갈을 예방할 때
    MaxRetentionSec 로그 보관 기간 기간 기준으로 운영 정책을 맞출 때
    –vacuum-size 즉시 용량 기준 정리 긴급 대응 시
    journald 로그 관리의 디스크 사용량 점검과 정리 전후 비교 이미지

    journald 로그 용량을 확인하고 정리하기 전후 차이를 보여주는 시각 자료입니다.

    6. 실전 트러블슈팅 4: 서비스는 죽었는데 원인이 안 보일 때

    이럴 때는 서비스 상태만 보지 말고, 이전 부팅 기록과 실시간 추적을 같이 봐야 합니다. 특히 재시작이 반복되는 서비스는 순간적으로 죽었다 살아나서 놓치기 쉽습니다.

    6-1. 특정 부팅 세션 기준으로 보기

    journalctl --list-boots
    journalctl -b -1
    journalctl -b -2 -u nginx.service

    배포 후 재부팅이 있었는지, 장애가 이전 부팅부터 이어진 건지 확인할 때 굉장히 유용합니다. 저도 커널 업데이트 이후 네트워크 서비스가 꼬인 적이 있었는데, 부팅별로 나눠보니 원인이 바로 보이더라고요.

    6-2. 서비스 실시간 추적

    journalctl -u myapp.service -f

    애플리케이션 재시작 테스트를 하면서 한쪽 터미널에서는 이 명령을 띄워두면 편합니다. 장애 재현과 동시에 로그를 보게 되니까, 놓치는 줄이 확 줄어듭니다.

    6-3. 상세 상태 확인과 묶어서 보기

    systemctl status myapp.service
    journalctl -u myapp.service -n 100 -o short-iso

    systemctl status는 현재 상태 요약, journalctl은 상세 로그 본문이라고 생각하시면 이해가 쉽습니다.

    7. 실전 트러블슈팅 5: 로그 형식이 불편하거나 파이프라인 연동이 필요할 때

    운영하다 보면 사람 눈으로만 보는 로그보다, 다른 도구로 넘기기 좋은 형식이 필요할 때가 있습니다. 예를 들어 자동화 스크립트, 간단한 grep 처리, 또는 JSON 수집 파이프라인 같은 경우죠.

    7-1. 출력 형식 바꾸기

    journalctl -u nginx.service -o short-iso
    journalctl -u nginx.service -o json
    journalctl -u nginx.service -o cat

    개인적으로 사람 눈으로 볼 땐 short-iso를 자주 씁니다. 타임스탬프가 읽기 편해서요. 반면 후처리나 수집 연계가 필요하면 json 출력이 꽤 쓸만합니다.

    7-2. 최근 로그 일부만 빠르게 확인

    journalctl -u nginx.service -n 50
    journalctl -k -n 100

    -k는 커널 로그를 보는 옵션입니다. 디스크 I/O나 네트워크 드라이버 문제처럼 시스템 하단 이슈를 의심할 때 먼저 보는 편입니다.

    7-3. grep과 함께 써서 빠르게 패턴 찾기

    journalctl -u docker.service --since "1 hour ago" | grep -i error
    journalctl -b | grep -Ei "timeout|failed|denied"

    엄밀히 말하면 structured log(구조화 로그)의 장점을 다 살리는 방식은 아니지만, 현장에선 이렇게 빠르게 감 잡는 경우도 많습니다. 급할 때는 일단 보이는 게 중요하거든요.

    journald 로그 관리에서 서비스 로그 실시간 추적과 구조화 출력 예시 이미지

    서비스 로그를 실시간으로 추적하고 다양한 출력 형식으로 확인하는 예시 이미지입니다.

    8. 운영 중 자주 겪는 주의사항: 제가 삽질했던 포인트들

    여기부터는 진짜 실무형 메모입니다. 문서만 보면 안 보이는데, 실제로는 아래 포인트에서 많이 막히더라고요.

    • ⚠️ 권한 문제: 일반 사용자로는 일부 시스템 로그가 제한될 수 있습니다. 안 보이면 sudo로 다시 확인해보세요.
    • ⚠️ 로그가 없다고 장애가 없는 건 아닙니다: 애플리케이션이 stdout/stderr로 안 남기면 journald에도 부족하게 남을 수 있습니다.
    • ⚠️ 영구 저장 설정 후 재시작 필요: 설정 파일만 바꾸고 journald 재시작을 안 해서 헷갈리는 경우가 많습니다.
    • ⚠️ 너무 aggressive한 vacuum: 로그를 급하게 지웠다가, 나중에 RCA(Root Cause Analysis, 근본 원인 분석) 못 하는 상황이 생깁니다.
    • ⚠️ 시간 동기화 확인: NTP가 어긋나면 로그 해석이 꼬입니다. 사건 순서가 뒤집혀 보이기도 하거든요.

    여기서 중요한 포인트! journald 로그 관리는 단순히 용량 정리만 의미하지 않습니다. 남겨야 할 로그를 남기고, 필요한 순간에 바로 찾을 수 있게 만드는 운영 습관에 가깝습니다.

    FAQ: 많이 받는 질문

    Q. rsyslog가 있으면 journald는 안 써도 되나요?
    아닙니다. 둘은 배타적이라기보다 함께 쓰는 경우가 많습니다. journald로 빠르게 조회하고, 필요하면 다른 로그 시스템으로 전달하는 식이죠.

    Q. journalctl이 너무 느릴 때는요?
    시간 범위, 서비스명, 부팅 범위를 먼저 줄여보세요. 전체 검색부터 하면 당연히 체감이 무겁습니다.

    Q. Linux 트러블슈팅에서 가장 먼저 볼 명령 하나만 고르라면?
    저는 journalctl -p err -b를 자주 씁니다. 현재 부팅의 에러를 먼저 보고 큰 줄기를 잡기 좋거든요.

    9. 검증과 마무리: 제대로 설정됐는지 이렇게 확인하면 됩니다

    설정은 했는데 정말 잘 적용됐는지 확인하는 단계가 중요합니다. 아래 순서대로 보면 웬만한 건 검증됩니다.

    1. journalctl --disk-usage로 저장량 확인
    2. journalctl --list-boots로 이전 부팅 로그 유지 여부 확인
    3. journalctl -u 서비스명 -f로 실시간 수집 확인
    4. journalctl -p err -b로 에러 필터 확인
    5. systemctl restart systemd-journald 이후 동작 재확인
    journalctl --disk-usage
    journalctl --list-boots
    journalctl -b -1
    journalctl -u ssh.service -n 20 -o short-iso
    journalctl -p err -b

    이 다섯 줄만 점검해도 현재 서버의 journald 상태를 꽤 명확하게 파악할 수 있습니다. 저도 처음엔 파일 로그만 찾다가 돌아왔는데, 이제는 장애가 나면 거의 반사적으로 journalctl부터 엽니다. 이거 진짜 편하더라고요.

    정리하자면, 이번 글의 핵심은 이겁니다. journald 로그 관리는 로그를 보는 기술이 아니라, 장애 순간에 증거를 잃지 않는 운영 방식입니다. persistent 설정으로 로그를 남기고, 시간/서비스/우선순위 필터로 빨리 좁히고, vacuum 정책으로 디스크를 보호하면 기본기는 꽤 단단해집니다.

    다음 글에서는 systemd 서비스 유닛 파일 튜닝이나, rsyslog와의 연계, 중앙 로그 수집 구조도 이어서 다뤄볼 예정입니다. 이전 글에서 다뤘던 리눅스 서비스 장애 대응 흐름과 연결해서 보시면 더 이해가 잘 되실 거예요. 혹시 지금 운영 중인 서버에서 로그가 자꾸 증발하거나, 디스크가 로그 때문에 차는 경험 있으신가요? 그럴 때 오늘 소개한 5가지를 하나씩 적용해보시면 방향이 꽤 빨리 잡히실 겁니다.

    journald 설정과 검증 포인트를 한눈에 정리한 요약 인포그래픽 이미지입니다.

  • [Linux] 리눅스 시스템 장애, 프로세스 비정상 종료 원인 분석 및 해결 사례 연구

    [Linux] 리눅스 시스템 장애, 프로세스 비정상 종료 원인 분석 및 해결 사례 연구

    [Linux] 리눅스 시스템 장애, 프로세스 비정상 종료 원인 분석 및 해결 사례 연구

    운영 중인 서버에서 갑자기 애플리케이션이 내려가고, 재시작은 되는데 원인이 안 보일 때가 있죠. 저도 홈랩과 실무에서 이런 일을 여러 번 겪었습니다. 특히 리눅스 프로세스 비정상 종료는 겉으로 보기엔 단순한 장애처럼 보여도, 실제로는 메모리 부족, 시그널(signal, 운영체제가 프로세스에 보내는 제어 신호), 파일 디스크립터(file descriptor, 열린 파일 핸들), 권한 문제, 디스크 I/O 지연처럼 여러 원인이 겹쳐 있는 경우가 많거든요. 이번 글에서는 제가 직접 겪었던 실제 시스템 장애 사례를 바탕으로, 로그 분석과 원인 분석을 어떻게 진행했는지, 그리고 어떤 순서로 복구했는지 차근차근 정리해보겠습니다.

    리눅스 프로세스 비정상 종료 분석 흐름을 보여주는 서버 운영 다이어그램

    장애 발생부터 로그 수집, 원인 분석, 복구까지의 전체 흐름을 한눈에 보는 개요 이미지입니다.

    1. 왜 리눅스 프로세스 비정상 종료가 까다로운가

    처음 장애를 보면 보통 이렇게 생각합니다. “프로세스가 죽었네? 다시 올리면 되겠지.” 근데 여기서 끝내면 같은 문제가 또 터지더라고요. 쉽게 말해 리눅스 프로세스 비정상 종료는 결과일 뿐이고, 진짜 봐야 하는 건 그 직전의 시스템 상태입니다.

    • OOM Killer(Out Of Memory Killer, 메모리 부족 시 커널이 프로세스를 강제 종료하는 기능)가 개입했는지
    • SIGKILL, SIGSEGV, SIGABRT 같은 종료 신호가 있었는지
    • systemd가 재시작 정책으로 계속 덮어쓰고 있지는 않은지
    • 애플리케이션 로그와 커널 로그의 시간이 정확히 맞는지
    • 디스크 공간이나 inode(아이노드, 파일 메타데이터 구조)가 바닥난 건 아닌지

    여기서 중요한 포인트! 애플리케이션만 보면 절반만 보는 겁니다. 커널 로그, 서비스 매니저, 리소스 한계값까지 같이 봐야 퍼즐이 맞습니다.

    2. 개념부터 정리: 프로세스는 왜 비정상 종료될까요?

    저도 처음엔 헷갈렸는데, 원인을 큰 범주로 나누면 훨씬 수월합니다.

    2-1. 대표적인 종료 원인

    원인 설명 대표 징후
    메모리 부족 커널이 OOM Killer를 실행 dmesg에 kill process 기록
    세그멘테이션 폴트 잘못된 메모리 접근 Segmentation fault, core dumped
    강제 종료 시그널 운영자, 스크립트, 오케스트레이터가 종료 exit code 137, signal 9
    리소스 제한 초과 ulimit, nofile, nproc 초과 Too many open files
    스토리지 문제 디스크 full, inode 고갈, I/O 지연 write 실패, journal 오류
    권한/환경 문제 파일 권한, 환경 변수, 라이브러리 누락 permission denied, missing library

    2-2. 종료 코드(exit code)도 힌트입니다

    운영하다 보면 종료 코드 하나가 실마리가 되기도 합니다. 예를 들어 137은 보통 SIGKILL과 연결해서 보게 되고, 139는 Segmentation fault 가능성을 먼저 의심하게 되죠. 물론 이 숫자만 보고 단정하면 안 됩니다. 저는 항상 “종료 코드 확인 → journald 확인 → 커널 로그 확인” 순서로 갔습니다.

    3. 사례 연구: 새벽에 반복된 시스템 장애, 처음엔 앱 버그인 줄 알았습니다

    이번 사례 연구는 제가 홈랩에서 돌리던 API 서비스에서 실제로 겪은 패턴을 바탕으로 재구성한 내용입니다. 구조는 단순했습니다. Nginx(엔진엑스, 웹 서버) 뒤에 Python 기반 API가 있었고, systemd로 서비스 관리 중이었죠. 증상은 이랬습니다.

    1. 특정 시간대에 응답 지연이 먼저 발생했습니다.
    2. 이후 워커 프로세스가 하나씩 사라졌습니다.
    3. systemd가 자동 재시작했지만 잠시 뒤 다시 종료됐습니다.
    4. 모니터링에서는 CPU보다 메모리 사용량이 비정상적으로 치솟았습니다.

    처음엔 코드 메모리 누수(memory leak, 메모리를 반환하지 못하는 현상)인 줄 알았거든요. 근데 로그를 차근차근 맞춰보니, 원인이 하나가 아니었습니다. 삽질 좀 했습니다 ㅎㅎ

    4. 실전 분석 1단계: 로그 분석으로 타임라인부터 맞췄습니다

    장애 분석에서 제가 제일 먼저 하는 건 “시간축 맞추기”입니다. 여러 로그를 뒤섞어 보면 정신없는데, 같은 시각 기준으로 정렬하면 보이는 게 많아집니다.

    date
    uptime
    systemctl status myapi.service
    journalctl -u myapi.service --since "2026-07-01 00:00:00" --until "2026-07-01 03:00:00"
    journalctl -k --since "2026-07-01 00:00:00" --until "2026-07-01 03:00:00"
    

    여기서 제가 확인한 포인트는 세 가지였습니다.

    • 서비스 종료 시각과 커널 로그 시각이 맞는지
    • 재시작 직전의 에러 메시지가 있는지
    • 같은 시간대에 다른 시스템 이벤트가 있었는지

    실제로 써보니까 journalctl -u만 보면 부족한 경우가 많더라고요. journalctl -k로 커널 메시지를 같이 봐야 합니다.

    journalctl -u myapi.service -n 50
    journalctl -k -n 100 | egrep -i "killed process|oom|segfault|out of memory"
    dmesg -T | egrep -i "killed process|oom|segfault"
    
    리눅스 프로세스 비정상 종료 원인 분석을 위한 로그 분석 장면

    서비스 로그와 커널 로그를 나란히 비교하면서 장애 시점을 맞춰보는 분석 화면을 표현한 이미지입니다.

    5. 실전 분석 2단계: OOM, 파일 디스크립터, 디스크 상태를 함께 봤습니다

    로그를 보니 일단 OOM 메시지가 보였습니다. 그런데 거기서 끝이 아니더라고요. 왜 메모리가 찼는지, 다른 자원도 같이 무너졌는지를 확인해야 재발을 막을 수 있거든요.

    5-1. 메모리 상태 확인

    free -h
    vmstat 1 5
    cat /proc/meminfo | egrep "MemAvailable|SwapTotal|SwapFree"
    ps aux --sort=-%mem | head -20
    

    여기서 특정 워커 프로세스가 메모리를 비정상적으로 많이 먹는 걸 확인했습니다. 근데 또 하나, 스왑(swap, 메모리 부족 시 디스크를 임시 메모리처럼 쓰는 공간)이 사실상 여유가 없더라고요. 메모리 압박이 생기면 커널이 버티지 못하고 강제 종료로 넘어가더라고요.

    5-2. 파일 디스크립터와 프로세스 제한 확인

    ulimit -n
    cat /proc/$(pgrep -f myapi | head -1)/limits
    lsof -p $(pgrep -f myapi | head -1) | wc -l
    ss -tanp | head -20
    

    혹시 이런 경험 있으신가요? 메모리만 보고 있었는데 실제론 소켓(socket, 네트워크 연결 끝점)이 쌓여서 Too many open files가 먼저 터지는 경우요. 저도 예전에 이걸 놓쳐서 원인 분석을 반나절 더 했었습니다.

    5-3. 디스크와 inode 상태 확인

    df -h
    df -i
    iostat -xz 1 3
    

    로그 적재 서버에서는 디스크 공간보다 inode 고갈이 더 자주 문제였습니다. 파일이 너무 잘게 쪼개져 쌓이면 공간이 남아도 쓰기를 못 하거든요. 이 경우 프로세스가 로그 기록 실패 후 연쇄적으로 비정상 동작하는 경우도 있습니다.

    6. 원인 분석 결과: 단일 원인이 아니라 복합 장애였습니다

    분석 결과를 정리하면 이랬습니다.

    1. 배치 작업이 시작되면서 API 워커 메모리 사용량이 급증했습니다.
    2. 동시에 외부 요청이 몰리며 연결 수가 늘어났습니다.
    3. 애플리케이션의 로그 파일 회전(log rotation, 로그 파일 순환 관리)이 늦어져 쓰기 부하가 커졌습니다.
    4. 결국 커널이 OOM Killer를 실행해 워커 프로세스를 종료했습니다.

    즉, 표면적으로는 리눅스 프로세스 비정상 종료였지만, 실제 뿌리는 메모리 압박 + 연결 누적 + 로그 처리 지연의 조합이었습니다. 처음엔 앱 버그 하나만 의심했는데, 시스템 전반을 봐야 답이 나오더라고요.

    6-1. 제가 적용한 systemd 보완 설정

    [Unit]
    Description=My API Service
    After=network.target
    
    [Service]
    User=www-data
    Group=www-data
    WorkingDirectory=/srv/myapi
    ExecStart=/srv/myapi/venv/bin/gunicorn app:app --workers 2 --bind 0.0.0.0:8000
    Restart=on-failure
    RestartSec=5
    LimitNOFILE=65535
    TimeoutStopSec=30
    KillSignal=SIGTERM
    
    [Install]
    WantedBy=multi-user.target
    

    KillSignal과 LimitNOFILE 설정은 생각보다 중요합니다. 종료 시그널을 정리해두면 강제 종료 전에 정리 작업을 할 여지가 생기고, 파일 디스크립터 한계를 현실적으로 맞춰두면 예기치 않은 장애를 줄일 수 있습니다.

    리눅스 프로세스 비정상 종료 대응을 위한 systemd 설정 구성 이미지

    서비스 재시작 정책, 파일 디스크립터 제한, 종료 시그널 처리 지점을 정리한 구성 이미지입니다.

    7. ⚠️ 트러블슈팅: 실제로 자주 놓치는 포인트

    여기부터는 제가 많이 당했던 부분들입니다. 진짜 사소해 보여도 장애 때는 이런 게 치명적이더라고요.

    • 애플리케이션 로그만 보고 커널 로그를 안 보는 실수
      OOM이나 segfault는 앱 로그에 안 남는 경우가 많습니다.
    • 재시작 성공을 복구 완료로 착각하는 실수
      systemd가 살려놨을 뿐, 원인은 그대로일 수 있습니다.
    • exit code를 안 보는 실수
      137, 139 같은 숫자가 꽤 큰 힌트가 됩니다.
    • 모니터링 해상도가 너무 낮은 실수
      1분 단위 그래프만 보면 급격한 스파이크를 놓치기도 합니다.

    저는 이 문제 이후로 장애 체크리스트를 아예 만들어뒀습니다.

    #!/usr/bin/env bash
    set -eu
    
    echo "== service status =="
    systemctl status myapi.service --no-pager || true
    
    echo "== recent service logs =="
    journalctl -u myapi.service -n 50 --no-pager || true
    
    echo "== kernel errors =="
    journalctl -k -n 100 --no-pager | egrep -i "oom|killed process|segfault|error" || true
    
    echo "== memory =="
    free -h || true
    
    echo "== disk =="
    df -h || true
    df -i || true
    

    이런 식으로 기본 수집 스크립트를 만들어두면, 새벽 장애 때 멘탈이 덜 흔들립니다. 드디어 됐다! 싶었던 순간이 이 자동화 만든 뒤였어요.

    8. 검증과 결과: 재현, 완화, 모니터링까지 묶어야 끝입니다

    문제를 고친 뒤에는 반드시 검증해야 합니다. 저는 아래 순서로 확인했습니다.

    1. 동일 부하 조건에서 메모리 사용량이 다시 치솟는지 확인
    2. 서비스 재시작 없이 일정 시간 안정적으로 유지되는지 확인
    3. 로그 회전과 디스크 사용량이 정상 범위인지 확인
    4. 알람 임계치가 현실적으로 설정됐는지 확인
    systemctl daemon-reload
    systemctl restart myapi.service
    systemctl is-active myapi.service
    watch -n 2 'ps aux --sort=-%mem | head -10'
    

    결과적으로 워커가 반복 종료되던 현상은 멈췄고, 피크 시간대에도 응답이 훨씬 안정적으로 유지됐습니다. 무엇보다 좋았던 건, 다음번 비슷한 시스템 장애가 와도 어디부터 볼지 기준이 생겼다는 점입니다.

    리눅스 프로세스 비정상 종료 해결 후 안정화 결과 시각화

    장애 조치 후 메모리 사용량이 안정되고 프로세스 재시작이 줄어든 결과를 보여주는 검증 이미지입니다.

    9. 정리와 다음 단계: 리눅스 프로세스 비정상 종료 대응 체크리스트

    이번 경험에서 다시 느낀 건 하나입니다. 리눅스 프로세스 비정상 종료는 프로세스 하나의 문제가 아니라, 시스템 전체의 신호를 읽는 문제라는 점이죠. 저도 처음엔 “왜 죽었지?”만 붙잡고 있었는데, 지금은 “죽기 전에 시스템이 어떤 상태였지?”를 먼저 봅니다.

    • 1순위: 종료 시각과 로그 타임라인 정렬
    • 2순위: 커널 로그에서 OOM, segfault, I/O 오류 확인
    • 3순위: 메모리, 파일 디스크립터, 디스크 상태 점검
    • 4순위: 재시작 정책과 서비스 제한값 재검토
    • 5순위: 재현 테스트와 모니터링 임계치 보완

    마지막으로, 장애 원인을 찾았더라도 문서화는 꼭 해두세요. 다음 장애 때 팀 전체 속도가 완전히 달라집니다. 다음 글에서는 coredump(core dump, 비정상 종료 시 메모리 상태를 남긴 파일) 분석과 gdb 기반 세그폴트 추적 방법도 다뤄볼 예정입니다. 이전 글에서 다룬 systemd 서비스 운영 팁과 함께 보면 더 흐름이 잘 잡히실 거예요.

    자주 묻는 질문

    • Q. 프로세스가 죽었는데 앱 로그가 비어 있습니다.
      A. 커널 로그와 journalctl -k를 먼저 보세요. OOM이나 segfault는 앱 로그에 안 남을 수 있습니다.
    • Q. 재시작되면 괜찮은 것 아닌가요?
      A. 아닙니다. 자동 재시작은 증상 완화일 뿐이고, 원인 분석이 안 되면 반복됩니다.
    • Q. 가장 먼저 볼 명령어는 뭔가요?
      A. 보통 systemctl status, journalctl -u, journalctl -k, dmesg -T 조합이면 출발점으로 충분합니다.
    리눅스 프로세스 비정상 종료 대응 체크리스트 인포그래픽

    리눅스 프로세스 비정상 종료 대응 절차를 빠르게 복습할 수 있도록 정리한 요약 인포그래픽입니다.

  • [보안] 중소기업 Wazuh 도입 1년 회고: SIEM 구축 성공과 실패 사례

    [보안] 중소기업 Wazuh 도입 1년 회고: SIEM 구축 성공과 실패 사례

    [보안] 중소기업 Wazuh 도입 1년 회고: SIEM 구축 성공과 실패 사례

    중소기업에서 Wazuh 도입을 고민하시는 분들은 대개 비슷한 지점에서 막히시더라고요. 보안 모니터링은 해야겠는데 상용 솔루션은 부담스럽고, 그렇다고 로그를 그냥 쌓아만 두자니 사고가 터졌을 때 아무것도 못 보는 상황이 생깁니다. 저도 홈랩과 실무 환경에서 비슷한 흐름을 여러 번 겪었습니다. 특히 SIEM 구축은 제품 하나 설치한다고 끝나는 일이 아니라, 로그 수집 범위, 경보 기준, 운영 인력의 습관까지 같이 바뀌어야 하거든요. 이번 글은 제가 1년 정도 오픈소스 SIEM 계열을 실제로 굴려보면서 느낀 점, 그리고 그중에서도 Wazuh를 중심으로 어떤 부분은 잘 됐고 어떤 부분은 삽질이었는지 정리한 회고입니다.

    처음엔 저도 “이거 설치만 하면 보안 관제가 되는 건가?” 싶었는데요, 실제로 써보니까 그런 기대는 빨리 버리는 게 맞았습니다. 대신 기준만 잘 잡으면 중소기업에서도 꽤 현실적인 보안 모니터링 체계를 만들 수 있더라고요. 혹시 지금 사내 서버 로그가 각자 제자리에서만 돌고 있고, 장애나 이상 징후를 사람 감으로만 보고 계신가요? 그렇다면 이 글이 꽤 도움이 되실 겁니다.

    Wazuh 도입 기반 중소기업 보안 모니터링 아키텍처 다이어그램

    중소기업 환경에서 Wazuh manager, agents, 로그 수집 대상 서버, 대시보드 흐름을 한눈에 보여주는 개요 이미지입니다.

    1. 왜 중소기업에서 Wazuh 도입을 검토하게 되는가

    현실적으로 중소기업은 보안팀이 따로 없거나, 있어도 인프라 담당자가 같이 보는 경우가 많습니다. 저도 비슷했거든요. 그러다 보니 가장 먼저 필요한 건 거창한 위협 인텔리전스보다 “무슨 일이 일어났는지 한곳에서 보는 능력”이었습니다.

    • 서버마다 로그인 실패 로그가 흩어져 있음
    • Windows 이벤트와 Linux syslog가 따로 놀음
    • 파일 무결성 체크(FIM, File Integrity Monitoring)가 안 됨
    • 에이전트(agent, 수집기) 배포 후 운영 기준이 없음
    • 알림은 오는데 무엇이 중요한지 구분이 안 됨

    이런 상황에서 Wazuh는 비교적 잘 알려진 오픈소스 기반 보안 플랫폼이고, 호스트 단위 가시성 확보에 강점이 있습니다. 특히 Linux와 Windows를 같이 보는 환경에서 시작점으로는 꽤 괜찮았습니다. 다만 여기서 중요한 포인트가 하나 있습니다. Wazuh 도입 자체가 목표가 되면 실패합니다. 목표는 도구 설치가 아니라, 운영 가능한 탐지 체계를 만드는 겁니다.

    2. SIEM 구축을 쉽게 말하면 무엇인가

    SIEM(Security Information and Event Management, 보안 정보 및 이벤트 관리)을 쉽게 말하면, 여러 시스템에서 나오는 로그와 이벤트를 한곳에 모아서 “이상 징후를 더 빨리 찾고, 나중에 추적도 할 수 있게 만드는 체계”입니다. 단순 로그 저장소하고는 조금 다릅니다.

    예를 들어 보겠습니다. 어떤 사용자가 새벽 시간대에 VPN에 접속하고, 직후 Linux 서버에서 sudo 사용이 늘어나고, 그다음 애플리케이션 서버에서 특정 설정 파일이 바뀌었다고 해보죠. 개별 로그만 보면 각각 별일 아닐 수 있습니다. 그런데 SIEM 구축 관점에서는 이 흐름을 묶어서 볼 수 있어야 합니다. 그게 핵심입니다.

    구분 로그 저장 SIEM 구축
    목적 나중에 확인 탐지와 대응
    데이터 처리 수집 위주 상관분석과 룰 기반 경보
    운영 난이도 상대적으로 낮음 튜닝이 필수
    성과 측정 저장 여부 실제 탐지율과 오탐 감소

    Wazuh 활용도 결국 여기로 연결됩니다. 에이전트를 깔고 대시보드만 띄우는 단계에서 멈추면 그냥 보기 좋은 로그 화면 하나 생긴 수준이에요. 반대로 자산 분류, 룰 튜닝, 알림 우선순위까지 잡아가면 중소기업에서도 쓸 만한 보안 모니터링 체계가 됩니다.

    3. 제가 잡았던 Wazuh 도입 목표와 범위

    처음부터 모든 자산을 넣지는 않았습니다. 이건 진짜 중요합니다. 욕심내서 한 번에 다 넣으면 대시보드가 아니라 경보 쓰레기통이 되거든요. 제가 직접 해보니 1단계 목표는 아주 단순해야 했습니다.

    1. 인터넷 노출 가능성이 있는 서버부터 우선 수집
    2. 관리 계정 로그인, 권한 상승, 주요 파일 변경을 우선 탐지
    3. 운영팀이 실제로 대응 가능한 수준까지만 알림 설정
    4. 2주 단위로 오탐(false positive, 정상인데 경보 발생) 정리

    범위는 Linux 서버 몇 대, Windows 서버 몇 대, 그리고 일부 웹 서비스 로그 정도로 시작했습니다. 네트워크 장비까지 한 번에 얹고 싶었는데, 예전 경험상 그렇게 하면 룰 정리도 안 되고 책임 범위만 넓어지더라고요. 결국 성공 포인트는 “작게 시작해서 운영에 녹이는 것”이었습니다.

    4. 실전 구현: 중소기업 환경에서 Wazuh 활용 시작하기

    여기서는 복잡한 HA(High Availability, 고가용성) 구성보다, 현실적으로 시작 가능한 단일 매니저 중심 구조를 기준으로 설명드리겠습니다. 운영 환경에서는 백업, 디스크 용량, 인덱스 보존 기간부터 먼저 계산하셔야 합니다.

    4-1. 에이전트 연결 전 체크리스트

    • 시간 동기화: NTP(Network Time Protocol) 확인
    • 수집 대상 서버 자산 목록 정리
    • 로그 보존 기간과 디스크 사용량 가늠
    • 누가 경보를 보고, 누가 처리할지 역할 정의

    4-2. Linux 에이전트 등록 예시

    배포판에 따라 설치 방식은 조금 다르지만, 기본 흐름은 비슷합니다. 실무에서는 사내 패키지 저장소나 자동화 도구를 같이 쓰시는 편이 좋습니다.

    # manager address example
    sudo /var/ossec/bin/agent-auth -m 10.0.0.10
    
    # agent service start example
    sudo systemctl enable wazuh-agent
    sudo systemctl start wazuh-agent
    
    # status check
    sudo systemctl status wazuh-agent

    처음엔 에이전트가 붙기만 하면 끝인 줄 알았는데, 실제로는 여기서부터 시작입니다. 붙은 뒤에 어떤 로그를 더 읽을지, 파일 무결성 감시 대상을 어디까지 둘지 정해야 하거든요.

    4-3. Linux 주요 로그 수집 예시

    <localfile>
      <log_format>syslog</log_format>
      <location>/var/log/auth.log</location>
    </localfile>
    
    <localfile>
      <log_format>syslog</log_format>
      <location>/var/log/syslog</location>
    </localfile>

    Ubuntu 계열이면 <code>/var/log/auth.log가 중요했고, RHEL 계열은 경로나 로그 체계가 다를 수 있어서 환경별 확인이 필요했습니다. 이런 기본 로그만 잘 들어와도 계정 사용 패턴이 꽤 보입니다.

    Wazuh 도입 시 에이전트 등록과 로그 수집 흐름 이미지

    에이전트 설치, 매니저 등록, 인증, 로그 전달, 대시보드 반영 흐름을 단계별로 보여주는 구성 이미지입니다.

    4-4. 파일 무결성 감시 설정 예시

    FIM(File Integrity Monitoring, 파일 무결성 감시)은 생각보다 체감 효과가 컸습니다. 웹 서버 설정 파일이나 중요 스크립트가 바뀌었을 때 바로 보이니까요. 다만 범위를 넓히면 바로 시끄러워집니다.

    <syscheck>
      <directories check_all="yes" realtime="yes">/etc</directories>
      <directories check_all="yes" realtime="yes">/usr/local/bin</directories>
      <ignore>/etc/mtab</ignore>
    </syscheck>

    제가 처음엔 /var 쪽까지 넓게 걸었다가 로그 회전과 임시 파일 변경 때문에 경보가 너무 많이 쏟아졌습니다. 삽질 좀 했습니다 ㅎㅎ 그래서 지금은 보안적으로 의미 있는 경로만 좁게 잡는 쪽을 권합니다.

    4-5. 경보 레벨 운영 기준 정리

    Wazuh 활용에서 정말 중요한 건 경보의 품질입니다. 경보가 너무 많으면 결국 아무도 안 봅니다. 저는 대략 이렇게 나눠서 운영했습니다.

    레벨 의미 운영 방식
    낮음 정보성 이벤트 대시보드 확인 위주
    중간 반복 확인 필요 일일 검토
    높음 권한 상승, 정책 위반 가능성 즉시 확인
    치명 침해 의심 흐름 담당자 즉시 호출

    5. 성공 사례: 작게 시작했더니 운영이 굴러갔습니다

    성공했던 부분은 의외로 화려한 탐지가 아니었습니다. 기본기였어요. 예를 들면 다음과 같습니다.

    • 관리 계정 로그인 실패 반복 확인이 쉬워짐
    • 주요 설정 파일 변경 이력 가시성 확보
    • Windows와 Linux 이벤트를 같은 관점에서 보기 시작
    • 보안팀이 없어도 운영팀이 최소한의 이상 징후를 확인 가능

    특히 계정 관련 이벤트는 체감이 컸습니다. 예전에는 “누가 언제 어디서 실패했는지”를 서버별로 따로 봤는데, 이제는 한 화면에서 흐름이 이어지니까 조사 시간이 줄더라고요. 드디어 됐다 싶은 순간이 이런 데서 나왔습니다. 엄청 첨단 기능이 아니라도, 찾아야 할 로그를 덜 헤매는 것만으로 운영 품질이 달라졌습니다.

    또 하나 좋았던 건 내부 커뮤니케이션입니다. 장애냐, 보안 이슈냐 애매한 상황에서 로그 근거를 바로 보여줄 수 있으니 논의가 빨라졌습니다. 인프라 팀과 개발팀 사이에서도 “느낌상 그런 것 같다”가 아니라 이벤트 기준으로 이야기하게 되더라고요.

    6. 실패 사례: Wazuh 도입 후 오히려 피곤해졌던 순간들

    반대로 실패도 분명히 있었습니다. 솔직히 말씀드리면, 도입 초반 2개월은 시스템이 아니라 사람이 먼저 지칩니다. 이유는 대부분 비슷합니다.

    6-1. 오탐이 많아서 아무도 안 보기 시작함

    가장 흔한 실패입니다. 룰을 많이 켠다고 좋은 게 아니더라고요. 시스템 업데이트, 배치 작업, 운영자의 정상 작업이 전부 경보로 올라오면 며칠 안 가서 무감각해집니다. 이 상태가 제일 위험합니다. 진짜 중요한 이벤트가 섞여도 묻히거든요.

    6-2. 자산 분류 없이 에이전트만 늘림

    중요 서버와 덜 중요한 서버가 같은 우선순위로 들어오면 분석 리소스가 분산됩니다. 제가 처음엔 “일단 많이 붙이자” 쪽이었는데 완전히 잘못된 접근이었습니다. 자산 등급이 먼저고, 수집은 그다음입니다.

    6-3. 저장 정책을 가볍게 봄

    로그는 쌓이는 속도가 생각보다 빠릅니다. 특히 웹 접근 로그나 상세 시스템 이벤트까지 같이 보면 보존 정책이 금방 문제 됩니다. 디스크만의 문제가 아니라 검색 성능도 같이 내려갑니다. 이 부분은 SIEM 구축에서 꽤 현실적인 비용 포인트입니다.

    Wazuh 활용에서 경보 튜닝 전후를 보여주는 보안 모니터링 대시보드

    오탐이 많던 초기 상태와 룰 튜닝 이후 상태를 시각적으로 비교해 주는 대시보드 개념 이미지입니다.

    7. ⚠️ 실제 겪은 트러블슈팅과 해결 방법

    여기서는 제가 실제로 많이 부딪힌 문제 위주로 적어보겠습니다. 비슷한 상황이 정말 자주 나옵니다.

    7-1. 에이전트는 붙었는데 기대한 로그가 안 들어오는 문제

    원인은 대체로 세 가지였습니다. 로그 파일 경로를 잘못 잡았거나, 권한 문제이거나, 애플리케이션 로그 포맷이 기대와 다른 경우입니다. 특히 커스텀 애플리케이션은 syslog 형식이 아니라서 별도 파싱 전략이 필요할 때가 있습니다.

    # recent agent logs example
    sudo tail -n 50 /var/ossec/logs/ossec.log
    
    # auth log check example
    sudo tail -n 50 /var/log/auth.log

    이럴 때 저는 무조건 에이전트 로그부터 봤습니다. 대시보드에서만 찾으려고 하면 시간 더 걸립니다.

    7-2. 파일 무결성 감시가 너무 시끄러운 문제

    해결은 단순했습니다. 감시 경로를 줄이고, 변동이 잦은 경로는 과감히 제외했습니다. 모든 변경을 다 잡겠다는 욕심보다 중요 변경만 놓치지 않는 것이 낫습니다.

    7-3. 경보는 오는데 누가 볼지 정해지지 않은 문제

    이건 기술 이슈 같지만 사실 운영 이슈입니다. Slack이든 메일이든 알림 채널만 만들면 끝날 줄 알았는데 아니었습니다. 근무시간 내 확인 담당, 야간 긴급 기준, 주간 리포트 담당을 정해야 실제 운영이 됩니다. 도구보다 프로세스가 먼저라는 말을 괜히 하는 게 아니더라고요.

    7-4. 업데이트 이후 룰 동작이 달라졌다고 느껴질 때

    버전 업그레이드는 항상 검증 환경에서 먼저 보는 게 맞습니다. 저는 홈랩에서 비슷한 구성을 따로 돌려보면서 룰 변화, 에이전트 연결, 주요 대시보드 동작을 먼저 확인했습니다. 중소기업 환경일수록 운영 서버가 곧 테스트 서버가 되는 경우가 있는데, 보안 시스템은 그렇게 다루면 피곤해집니다.

    8. 검증과 결과: 1년 운영 후 무엇이 남았나

    정량 수치를 과하게 들이밀고 싶진 않습니다. 환경마다 다르거든요. 대신 운영 체감 기준으로는 확실한 변화가 있었습니다.

    1. 이상 로그인이나 권한 상승 흔적을 찾는 시간이 줄었습니다.
    2. 주요 서버의 변경 이력을 더 빨리 추적하게 됐습니다.
    3. 인프라 운영과 보안 모니터링 사이의 경계가 조금 정리됐습니다.
    4. 무엇보다 “문제 발생 후 로그를 찾는 문화”에서 “평소에 보는 문화”로 조금 이동했습니다.

    완벽하진 않았습니다. 여전히 룰 튜닝은 계속해야 하고, 애플리케이션 레벨 가시성은 부족한 부분이 있습니다. 하지만 Wazuh 도입으로 최소한의 기준선은 만들 수 있었습니다. 이게 꽤 큽니다. 아무것도 없는 상태와, 조금이라도 지속 가능한 보안 모니터링 체계가 있는 상태는 정말 다르거든요.

    Wazuh 도입 1년 후 보안 이벤트 가시성과 대응 흐름 결과 이미지

    운영 1년 후 주요 이벤트 분류, 우선순위, 대응 흐름이 정리된 상태를 보여주는 결과 시각화 이미지입니다.

    9. 마무리: 중소기업에서 오픈소스 SIEM을 성공시키는 기준

    정리해보면, Wazuh 도입은 “무료니까 일단 깔아보자”로 접근하면 금방 지칩니다. 대신 아래 기준으로 접근하면 훨씬 낫습니다.

    • 작게 시작할 것
    • 중요 자산부터 붙일 것
    • 오탐을 줄이는 운영 시간을 반드시 확보할 것
    • 경보를 누가 볼지 먼저 정할 것
    • 보안 모니터링 목표를 기술보다 운영 기준으로 정의할 것

    제가 직접 해보니 오픈소스 SIEM의 핵심은 기능 수보다 지속 가능성이었습니다. 멋진 룰셋보다, 팀이 실제로 매주 볼 수 있는 대시보드와 알림이 더 중요하더라고요. 혹시 지금 Wazuh 활용을 검토 중이시라면, 처음부터 거대한 청사진보다 “이번 달에 무엇을 탐지할 것인가”부터 적어보시면 좋겠습니다.

    다음 글에서는 Wazuh 룰 튜닝과 알림 우선순위 설계를 좀 더 실무적으로 다뤄볼 예정입니다. 이전 글에서 다뤘던 로그 보존 전략이나 syslog 수집 구조와도 연결되는 주제라, 같이 보시면 흐름이 더 잘 잡히실 겁니다.

    중소기업 Wazuh 도입 성공과 실패 포인트 요약 인포그래픽

    중소기업 관점에서 Wazuh 도입 성공 기준, 실패 패턴, 다음 단계 체크리스트를 한 장으로 정리한 요약 이미지입니다.

    자주 묻는 질문

    Wazuh는 중소기업 SIEM 구축 시작점으로 괜찮은가요?

    네, 시작점으로는 괜찮았습니다. 다만 설치보다 운영 기준 정의가 더 중요합니다.

    보안 전담 인력이 없어도 운영 가능한가요?

    가능은 합니다. 대신 자산 범위를 좁게 시작하고, 경보 우선순위를 명확히 나눠야 합니다.

    오픈소스 SIEM이면 비용이 아예 없나요?

    라이선스 비용이 없을 수는 있어도, 저장소 비용과 운영 시간 비용은 분명히 듭니다. 이걸 과소평가하면 실패 확률이 높습니다.

  • [보안] Fail2ban 오탐 줄이기: 로그 분석과 정규식 최적화 전략

    [보안] Fail2ban 오탐 줄이기: 로그 분석과 정규식 최적화 전략

    [보안] Fail2ban 오탐 줄이기: 로그 분석과 정규식 최적화 전략

    Fail2ban 오탐 때문에 정상 사용자가 차단되는 상황, 한 번쯤 겪어보셨을 겁니다. 저도 홈랩이랑 외부에 노출된 몇몇 서비스에서 비슷한 Fail2ban 오탐 문제를 꽤 겪었습니다. 처음엔 “차단이 잘 되면 좋은 거 아닌가?” 싶었는데, 실제 운영에서는 이야기가 좀 다르더라고요. 특히 SSH, Nginx, 메일 서비스처럼 로그 형식이 조금만 달라져도 Fail2ban 정규식이 과하게 반응해서 멀쩡한 요청까지 공격으로 오인하는 경우가 생깁니다. 이번 글에서는 제가 실제로 정리해 둔 방식대로, 로그 분석부터 정규식(regex, 정규 표현식) 튜닝까지 단계별로 풀어보겠습니다.

    핵심은 단순합니다. 차단 수치를 무작정 올리거나 <code>maxretry만 만지는 게 아니라, 먼저 로그를 읽고 패턴을 이해한 다음, 그에 맞는 필터를 만드는 겁니다. 이 흐름만 잡히면 침입 방지 시스템을 좀 더 안정적으로 운영할 수 있습니다.

    Fail2ban 오탐 분석과 로그 처리 흐름을 보여주는 아키텍처 이미지

    Fail2ban 오탐을 줄이기 위해 로그 수집, 패턴 분석, 정규식 튜닝, 차단 검증까지 이어지는 전체 흐름을 한눈에 보여주는 이미지입니다.

    왜 Fail2ban 오탐이 생길까요?

    쉽게 말해 Fail2ban은 로그를 보고 판단합니다. 네트워크 패킷 자체를 해석하는 게 아니라, 서비스가 남긴 텍스트 로그를 기준으로 차단하거든요. 그래서 서비스 설정이 바뀌거나 프록시(Proxy, 중계 서버)가 앞단에 추가되면, 기존 필터가 예상하지 못한 문자열이 로그에 섞이게 됩니다.

    예를 들어 이런 경우가 대표적입니다.

    • 리버스 프록시(Reverse Proxy, 역방향 프록시) 뒤에 있어서 실제 클라이언트 IP 대신 프록시 IP가 보이는 경우
    • 애플리케이션 로그 포맷이 커스텀되어 기본 필터와 맞지 않는 경우
    • 에러 로그와 경고 로그가 비슷한 문장 구조를 가져서 실패 이벤트로 잘못 매칭되는 경우
    • 한글 메시지 또는 추가 모듈 로그가 섞여 정규식이 과하게 넓게 잡히는 경우

    저도 예전에 Nginx 뒤에 붙은 인증 서비스에서 Fail2ban 오탐이 계속 나서 삽질 좀 했습니다. 원인은 단순했어요. 기존 필터가 authentication failure 같은 문자열만 보고 잡고 있었는데, 실제로는 봇 공격이 아니라 브라우저 재시도 요청 일부까지 걸리고 있었거든요.

    Fail2ban 동작 원리와 정규식 최적화 포인트

    Fail2ban은 크게 세 가지를 봅니다. 필터(filter), 저널 또는 로그 소스(log source), 그리고 차단 정책(jail)입니다. 여기서 Fail2ban 오탐을 줄이는 핵심은 필터입니다.

    구성 요소 역할 오탐과의 관계
    Filter 로그에서 실패 패턴 추출 정규식이 넓으면 정상 로그도 공격으로 인식
    Jail 재시도 횟수, 차단 시간, 대상 서비스 설정 필터가 잘못되면 정책이 정상 사용자에게 적용
    Action iptables, nftables 등으로 차단 수행 실수한 필터가 실제 차단으로 이어짐

    여기서 중요한 포인트! 정규식은 많이 잡는 게 좋은 게 아닙니다. 정확하게 잡는 게 중요합니다.

    제가 보통 보는 기준은 이렇습니다.

    1. 실패 이벤트를 명확히 나타내는 고정 문자열이 있는가
    2. IP 주소 위치가 일정한가
    3. 정상 요청과 실패 요청을 구분하는 문장이 분리되는가
    4. 시간대별, 서비스별 로그 변형이 있는가

    이 네 가지만 점검해도 Fail2ban 오탐 확률이 꽤 줄어듭니다.

    로그 분석 먼저: Fail2ban 오탐 줄이기의 출발점

    정규식을 바로 고치기 전에 반드시 로그를 먼저 봐야 합니다. 이걸 건너뛰면 거의 감으로 튜닝하게 되는데, 그러면 나중에 더 크게 꼬입니다. 실제로 써보니까 로그 20줄 제대로 보는 게 설정 20번 바꾸는 것보다 훨씬 빠르더라고요.

    1. 최근 차단 이벤트 확인

    sudo fail2ban-client status
    sudo fail2ban-client status sshd

    이 명령으로 활성화된 jail과 현재 차단된 IP를 먼저 확인합니다. 어떤 jail이 유독 많이 반응하는지부터 보는 거죠.

    2. 원본 로그에서 실패 패턴 확인

    sudo tail -n 200 /var/log/auth.log
    sudo journalctl -u ssh --since "1 hour ago"
    sudo grep -i "fail\|invalid\|error" /var/log/auth.log | tail -n 50

    시스템마다 로그 경로는 다를 수 있습니다. Debian/Ubuntu 계열은 auth.log, systemd 환경은 journalctl 기반으로 보는 경우가 많습니다.

    3. 정상 로그와 실패 로그를 같이 비교

    이 단계가 진짜 중요합니다. Fail2ban 오탐은 보통 실패 로그만 보면 안 보입니다. 정상 사용자 접속, 키 교환, 세션 종료 같은 문장까지 같이 봐야 차이가 보이거든요.

    sudo grep -E "Failed password|Accepted password|Invalid user|Connection closed" /var/log/auth.log | tail -n 100

    제가 자주 하는 방식은 실패 케이스와 정상 케이스를 각각 10~20줄씩 복사해서 옆에 두고 비교하는 겁니다. 어떤 단어가 실패에서만 나오는지 찾는 거죠.

    Fail2ban 오탐을 줄이기 위한 정상 로그와 실패 로그 비교 이미지

    정상 로그인 로그와 실패 로그인 로그를 나란히 비교하면서 정규식에 포함해야 할 문자열과 제외해야 할 문자열을 구분하는 과정을 표현한 이미지입니다.

    실전 구현: Fail2ban 필터와 정규식 다듬기

    이제 본격적으로 Fail2ban 정규식을 손보겠습니다. 여기서는 SSH 계열 로그를 예시로 들지만, 원리는 Nginx나 메일 서비스에도 그대로 적용됩니다.

    1. 기존 필터 확인

    sudo ls /etc/fail2ban/filter.d/
    sudo sed -n '1,200p' /etc/fail2ban/filter.d/sshd.conf

    기존 필터를 무작정 덮어쓰는 건 추천하지 않습니다. 기본 파일은 참고만 하고, 별도 커스텀 필터를 만드는 편이 유지보수에 좋습니다.

    2. 커스텀 필터 작성

    # /etc/fail2ban/filter.d/sshd-local.conf
    [Definition]
    failregex = ^%(__prefix_line)sFailed password for (?:invalid user )?.* from <HOST> port \d+ ssh2$
                ^%(__prefix_line)sInvalid user .* from <HOST>$
    ignoreregex = ^%(__prefix_line)sConnection closed by authenticating user .*$
                  ^%(__prefix_line)sAccepted publickey for .*$

    여기서 의도는 명확합니다.

    • failregex는 실패로 확정할 수 있는 로그만 좁게 잡습니다
    • ignoreregex는 헷갈릴 수 있는 정상 또는 무해한 로그를 제외합니다
    • <HOST>는 Fail2ban이 IP를 추출할 때 사용하는 플레이스홀더입니다

    처음엔 이게 뭔가 싶었는데, 실제로는 ignoreregex를 잘 쓰는 순간 Fail2ban 오탐이 확 줄더라고요. 많은 분들이 failregex만 만지는데, 사실 둘을 같이 봐야 합니다.

    3. jail 설정 분리

    # /etc/fail2ban/jail.local
    [sshd-local]
    enabled = true
    filter = sshd-local
    port = ssh
    logpath = /var/log/auth.log
    maxretry = 5
    findtime = 10m
    bantime = 30m

    maxretry, findtime, bantime은 정책값입니다. Fail2ban 오탐이 자주 난다고 무조건 maxretry를 크게 올리면 실제 공격 대응이 느슨해질 수 있습니다. 그래서 저는 먼저 필터를 좁히고, 그 다음 정책을 조정하는 순서를 지킵니다.

    4. 정규식 테스트

    sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd-local.conf

    이 명령은 거의 필수입니다. 실제 로그 파일에 대해 내가 만든 정규식이 몇 줄을 매칭하는지 보여주거든요. 여기서 정상 로그가 잡히면 배포 전에 바로 알 수 있습니다. 드디어 됐다! 싶은 순간도 보통 여기서 옵니다.

    Fail2ban 정규식 테스트와 로그 분석 검증 화면 이미지

    fail2ban-regex 명령으로 필터가 어떤 로그를 매칭하는지 검증하고, 예상치 못한 정상 로그가 걸리는 부분을 찾아내는 과정을 시각화한 이미지입니다.

    ⚠️ 제가 실제로 겪었던 Fail2ban 오탐 패턴과 해결법

    운영하다 보면 생각보다 단순한 이유로 오탐이 납니다. 아래는 제가 자주 본 패턴입니다.

    프록시 뒤에서 IP가 꼬이는 경우

    Nginx 같은 리버스 프록시 뒤에 인증 서비스가 있으면, 애플리케이션 로그에 원본 IP 대신 프록시 IP가 찍히는 경우가 있습니다. 그러면 한 사용자의 실패가 아니라 프록시 전체가 문제처럼 보일 수 있죠. 이럴 땐 애플리케이션 로그 포맷에서 실제 클라이언트 IP가 어디에 기록되는지 먼저 확인해야 합니다.

    로그 문장이 비슷해서 정상 이벤트가 같이 잡히는 경우

    예를 들어 Connection closed 같은 문장은 실패 직후에도 보이고 정상 종료에도 보일 수 있습니다. 이런 문장을 넓게 잡아버리면 Fail2ban 오탐이 생깁니다. 그래서 저는 실패 판단용 문자열은 가급적 Failed, Invalid user처럼 의미가 분명한 것만 씁니다.

    한 줄 정규식으로 모든 걸 해결하려는 경우

    이거 정말 많이 봅니다. 정규식을 너무 똑똑하게 만들려고 하면 나중에 본인도 못 읽게 됩니다. 차라리 여러 줄의 failregex로 케이스를 나누는 게 유지보수에 훨씬 좋습니다.

    테스트 없이 서비스 재시작하는 경우

    저도 초반에 이 실수 했었습니다. 필터 바꾸고 바로 재시작했는데, 다음 날 정상 사용자 한 명이 접속이 안 된다고 연락이 오더라고요. 그 뒤로는 무조건 fail2ban-regex 테스트부터 합니다.

    검증 절차: 설정 후 무엇을 확인해야 할까

    설정을 넣었다고 끝이 아닙니다. Fail2ban 오탐은 운영 중에 드러나는 경우가 많아서, 적용 후 검증 루틴이 필요합니다.

    1. 필터 문법 테스트
    2. 실제 로그에 대한 매칭 수 확인
    3. 서비스 재시작 또는 설정 리로드
    4. 최근 1시간 로그에서 정상 이벤트가 차단 대상에 포함되는지 확인
    5. 차단된 IP 목록과 대응 로그를 대조
    sudo fail2ban-client reload
    sudo fail2ban-client status sshd-local
    sudo fail2ban-client get sshd-local banip

    가능하면 테스트 계정으로 정상 로그인과 실패 로그인을 각각 발생시켜 보는 것도 좋습니다. 홈랩 환경에서는 이런 검증을 해보기 좋아서, 저도 새 필터 만들면 꼭 직접 재현해 봅니다.

    검증 체크리스트

    • 정상 로그인 이벤트가 failregex에 잡히지 않는가
    • 실패 이벤트는 빠짐없이 잡히는가
    • 차단된 IP가 실제 공격 주체와 일치하는가
    • 로그 포맷 변경 시 필터가 깨질 가능성은 없는가

    결과 정리: Fail2ban 오탐 줄이기 전후 비교

    점검 항목 오탐 줄이기 전 오탐 줄인 후
    필터 범위 에러 비슷한 문자열을 넓게 포함 실패로 확정 가능한 문장만 포함
    정상 사용자 영향 재시도나 세션 종료 로그까지 차단 가능 정상 이벤트는 ignoreregex로 제외
    운영 안정성 차단 이유 추적이 어려움 왜 차단됐는지 로그와 정규식이 명확함
    유지보수 한 줄짜리 복잡한 정규식에 의존 케이스별로 읽기 쉬운 필터 분리

    결국 중요한 건 “많이 막는 것”보다 “정확히 막는 것”입니다. 서버 보안은 강하게만 간다고 좋은 게 아니더라고요. 특히 운영 환경에서는 정상 사용자의 경험도 같이 지켜야 하니까요.

    Fail2ban 오탐 감소 후 차단 결과를 검증하는 대시보드 이미지

    Fail2ban 오탐이 줄어든 뒤 차단 로그와 정상 접속 로그가 명확하게 구분되고, 관리자 입장에서 추적이 쉬워진 상태를 보여주는 결과 이미지입니다.

    자주 묻는 질문: Fail2ban 오탐 대응 FAQ

    Q1. maxretry만 올리면 해결되지 않나요?

    일시적으로는 덜 민감해질 수 있습니다. 근데 근본 해결은 아닙니다. 정규식이 잘못되면 정상 로그를 계속 실패로 판단하니까요.

    Q2. ignoreregex는 꼭 써야 하나요?

    반드시 필요한 건 아니지만, 실제 운영에서는 꽤 유용합니다. 특히 비슷한 형식의 정상 로그가 많은 서비스에서는 Fail2ban 오탐 방지에 효과가 큽니다.

    Q3. 로그 분석은 어느 정도까지 해야 하나요?

    최소한 정상 이벤트와 실패 이벤트를 각각 여러 줄씩 비교해 보시는 걸 권합니다. 한두 줄 보고 만들면 예외 케이스를 놓치기 쉽습니다.

    Q4. 기본 필터를 수정해도 되나요?

    가능은 하지만 권장하지 않습니다. 배포판 업데이트나 패키지 변경 시 추적이 어려워질 수 있어서, 별도 로컬 필터 파일로 분리하는 편이 낫습니다.

    마무리: 로그를 읽는 습관이 Fail2ban 운영을 살립니다

    이번 글에서는 Fail2ban 오탐을 줄이기 위해 로그를 어떻게 읽고, Fail2ban 정규식을 어떤 기준으로 다듬어야 하는지 정리해봤습니다. 저도 처음엔 단순히 차단 시간이랑 재시도 횟수만 조정했었는데, 결국 답은 로그 안에 있더라고요. 실제로 해보니까 정규식을 똑똑하게 짜는 것보다, 서비스 로그가 어떤 의미를 가지는지 먼저 이해하는 게 훨씬 중요했습니다.

    혹시 지금도 정상 사용자가 자꾸 차단돼서 골치 아프시다면, 오늘 바로 fail2ban-regex부터 돌려보세요. 거기서 의외로 실마리가 바로 나옵니다. 다음 글에서는 Nginx 액세스 로그 기준으로 봇 요청과 인증 실패를 분리해서 침입 방지 시스템 정책을 나누는 방법도 다뤄볼 예정입니다. 이전 글에서 방화벽 정책과 로그 로테이션(log rotation, 로그 순환) 정리해두셨다면 같이 보시면 흐름이 더 잘 잡히실 겁니다.

    로그 분석, 정규식 최소화, ignoreregex 활용, 테스트 검증이라는 네 가지 핵심 원칙을 한 장으로 정리한 요약 이미지입니다.