악성 봇은 못 멈춘다. 대신 앱 앞에서 잘라버립시다 - nginx 단계에서 이상한 URL 정리하기

nginx blackhole.conf로 이상한 URL 정리하기

웹 애플리케이션을 인터넷에 노출하는 순간, 어떤 프레임워크를 쓰든 이상한 요청이 쏟아집니다.

  • 존재하지도 않는 /wp-admin/
  • PHP 한 줄도 안 쓰는데 날아오는 /index.php, /xmlrpc.php
  • .git, .env, /cgi-bin/ 같은 민감한 파일/디렉터리들
  • wso.php, shell.php, test.php

직접 만든 서비스가 아니라 전 세계 스캐너와 봇들이 “취약점이 있나?” 찔러보는 URL들이죠. 솔직히 말하면, 이걸 안 당하는 서버는 전 세계에 없다고 봐도 됩니다.

다행히 애플리케이션 레벨에서 방어체계가 잘 되어 있다면 대부분은 400/404로 잘 처리됩니다. 문제는 그 이후입니다.

  • 로그가 더러워집니다. 진짜 사용자 요청을 보기도 전에 이상한 요청들로 스크롤 반을 씁니다.
  • 아주 조금이지만 CPU를 씁니다. “안전하니까 괜찮다”라고 하기엔, 쓸데없는 연산을 하고 있다는 느낌이 찝찝합니다.
  • 마음이 피곤해집니다. 로그 볼 때마다 /wp-login.php가 수백 줄… 보기만 해도 피로감이 쌓입니다.

그래서 저는 이런 것들을 아예 애플리케이션 앞단의 nginx에서 잘라버립니다. 앱까지 도달도 못 하게, 호스트 레벨에서 “블랙홀”로 보내는 것이죠.

이 글에서는:

  • 왜 이런 “nginx 단에서의 1차 차단”이 유용한지
  • 어떻게 blackhole.conf 같은 파일로 공통 차단 규칙을 관리할 수 있는지
  • 실제로 쓸 수 있는 nginx 설정 샘플

을 정리해 보겠습니다.


1. 애플리케이션에서만 막으면 아쉬운 이유



우리가 흔히 하는 기본적인 접근은 이렇습니다.

  • 라우터/컨트롤러에 없는 URL → 404
  • 예외 처리 → 400/500
  • 로그 → APM / log collector로 수집

기능적으로는 문제 없습니다. 하지만 운영 관점에서는 찜찜한 포인트가 몇 가지 있습니다.

  1. 로그 노이즈
  • APM, 로깅 시스템에서 에러/404 비율이 실제 사용자보다 스캐너 비율 때문에 부풀려집니다.
  • “이게 진짜 사용자인지, 스캐너인지”를 매번 눈으로 구분해야 합니다.
  1. 계층이 너무 아래에서 막힌다
  • 요청이 앱까지 들어왔다는 것 자체가,
  • 프레임워크, 미들웨어, 라우팅 레이어를 모두 한 번 타고 들어왔다는 뜻입니다.
  • 한 번 더 생각해보면, “굳이 여기까지 들여보낼 필요가 있을까?”라는 의문이 듭니다.
  1. 미세하지만 누적되는 리소스 소모
  • 한두 번이면 상관 없지만,
  • 24시간 내내 들어오는 스캐너 트래픽이 누적되면 꽤 많은 요청 수를 차지합니다.

그래서 저는 이런 “봐도 도움 안 되는 요청들”을 위쪽 레이어에서 최대한 정리하고 들어가는 걸 선호합니다.


2. nginx에서 블랙홀 만들기: 444로 바로 끊어버리기

nginx에는 HTTP 표준에는 없는 자체 상태 코드가 하나 있습니다.

return 444;
  • 응답 헤더도, 바디도 보내지 않고
  • 그냥 TCP 연결만 조용히 끊어 버리는 코드입니다.
  • 클라이언트 입장에서는 “연결이 끊겼네?” 정도만 보입니다.

이걸 이용해서, “우리가 보기에도 100% 이상한 URL들”은 아예 응답도 하지 않고 연결을 끊어버릴 수 있습니다.

장점은 단순합니다.

  • 애플리케이션까지 도달하지 않습니다. (프레임워크 CPU 0)
  • access 로그도 옵션에 따라 아예 남기지 않을 수 있습니다.
  • “이상한 URL”은 nginx 한 겹에서 모두 걸러지니, 앱 쪽 로그가 매우 깨끗해집니다.

3. blackhole.conf 하나로 관리하는 패턴



규칙을 매번 서버 블록마다 적어 넣기보다는, 저는 보통 blackhole.conf라는 파일을 하나 두고, 공통 패턴을 여기 모아 둡니다.

예를 들어:

# /etc/nginx/blackhole.conf (경로는 자유롭게)

# === 1. 숨김/형상 관리 디렉터리 (.git, .env, IDE 등) ===
location ~* (^|/)\.(git|hg|svn|bzr|DS_Store|idea|vscode)(/|$)   { return 444; }
location ~* (^|/)\.env(\.|$)                                    { return 444; }
location ~* (^|/)\.(?:bash_history|ssh|aws|npm|yarn)(/|$)       { return 444; }

# === 2. 쓰지 않는 CMS 관리자/취약점 스캔 ===
location ^~ /administrator/                                    { return 444; }  # Joomla 등
location ^~ /phpmyadmin/                                      { return 444; }  # 안 쓰면 확실히 막기

# === 3. PHP/CGI/워드프레스 흔적 ===
# PHP를 전혀 쓰지 않는 스택(예: pure Node, Go, Python) 에서만 권장
location ~* \.(?:php\d*|phtml|phps|phar)(/|$)                  { return 444; }
location ^~ /cgi-bin/                                         { return 444; }
location ~* ^/(wp-admin|wp-includes|wp-content)(/|$)          { return 444; }

# === 4. 스캐너가 자주 긁어보는 파일/경로 ===
location ~* ^/(?:info|phpinfo|test|wso|shell|gecko|moon|root|manager|system_log)\.php(?:/|$)? {
    return 444;
}
location ~* ^/(?:autoload_classmap|composer\.(?:json|lock)|package\.json|yarn\.lock|vendor)(?:/|$) {
    return 444;
}
location ~* ^/(?:_profiler|xmrlpc|xmlrpc|phpversion)\.php(?:/|$)? {
    return 444;
}

# === 5. 백업/임시/덤프 파일 ===
location ~* \.(?:bak|backup|old|orig|save|swp|swo|tmp|sql(?:\.gz)?|tgz|zip|rar)$ {
    return 444;
}

# === 6. well-known: ACME만 허용하고 나머지는 컷 ===
location ^~ /.well-known/acme-challenge/ {
    root /var/www/letsencrypt;
}
location ^~ /.well-known/ {
    return 444;
}

# === 7. 메서드 가드(옵션): 거의 안 쓰는 메서드는 사전 차단 ===
# TRACE, CONNECT, WebDAV 계열 메서드를 쓰지 않는다면 미리 막아두기
if ($request_method !~ ^(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS)$) {
    return 405;
}

이렇게 만들어 두고, 각 서버 블록에서 한 줄만 추가합니다.

http {
    # ...

    server {
        listen 80;
        server_name example.com;

        include /etc/nginx/blackhole.conf;

        # 나머지 정상 라우팅 설정...
    }
}

이제 대부분의 “쓰지도 않는 경로에 대한 취약점 스캔”은 애플리케이션에 도달하기 전에 nginx에서 444로 정리됩니다.


4. 로그 소음까지 줄이고 싶다면 (선택)

여기까지 해도 충분히 상쾌하지만, 원한다면 이 블랙홀로 빠지는 요청들은 아예 access_log에도 안 남게 만들 수 있습니다.

예를 들어, 아래처럼 map을 활용할 수 있습니다.

http {
    # 블랙홀로 보낼 만한 패턴은 로그를 줄이고 싶다
    map $request_uri $drop_noise_log {
        default                            0;
        ~*^/(?:\.git|\.env)                1;
        ~*^/(wp-admin|wp-includes|cgi-bin) 1;
        ~*\.php(?:/|$)                     1;
    }

    server {
        listen 80;
        server_name example.com;

        include /etc/nginx/blackhole.conf;

        # drop_noise_log가 0일 때만 access 로그 기록
        access_log /var/log/nginx/access.log combined if=$drop_noise_log;
    }
}
  • $drop_noise_log가 1인 요청은 로그를 남기지 않습니다.
  • 대신 진짜 사용자 요청, 정상적인 라우트에 대한 로그만 깔끔하게 남습니다.

실운영에서는, 처음에는 잠깐 로그를 켠 상태로 패턴을 검증해 보고 “정상 유저가 여기 걸릴 일이 없다”는 확신이 들 때 if=$drop_noise_log로 바꿔 주는 걸 추천합니다.


5. 오탐(정상 요청을 막는 실수)을 피하기 위한 가이드

이런 류의 차단 규칙에서 가장 중요한 건 “지나치게 공격적으로 막지 않는 것”입니다. 몇 가지 기준을 잡아두면 좋습니다.

  1. 스택에 맞는 규칙만 켜기
  • PHP를 아예 쓰지 않는 프로젝트라면 .php 전체를 막는 게 편합니다.
  • 반대로 Laravel, WordPress를 실제로 쓰고 있다면 .php 차단 규칙은 당연히 빼야 합니다.
  • 마찬가지로 /cgi-bin을 실제로 쓰는 레거시 시스템이라면 해당 규칙 제외.
  1. 처음에는 404/403으로 시작해도 좋다
  • 바로 444로 끊어도 되지만,
  • 초반에는 return 403; 정도로 응답을 남겨두고,
  • “정상 사용자가 이 URL을 치는 일이 있는지” 며칠 모니터링해 본 뒤 444로 바꿔도 됩니다.
  1. 위험한 경로부터 보수적으로
  • .git, .env, 백업 파일, phpinfo.php 등은 “실수로라도 외부 노출되면 안 되는 것들”입니다.
  • 이런 것들은 과감하게 444로 막아도 운영에 영향을 줄 가능성이 거의 없습니다.
  1. 위치와 우선순위
  • nginx는 location 매칭 우선순위(정확, 접두사, 정규식 등)가 있습니다.
  • 일반적인 애플리케이션 라우팅보다 위에 두지 말고, 보통은 include blackhole.conf를 server 초반에 놓고, 뒤에 location / { ... } 같은 메인 핸들러를 두면 문제 없습니다.

6. 애플리케이션, WAF, nginx: 각자 잘하는 위치에서 막자

이 글에서 소개한 방식은 어디까지나 “1차적인 노이즈 제거”에 가깝습니다.

  • nginx 블랙홀:

  • “봐도 의미 없는, 존재해서는 안 되는 경로”를 초기에 잘라내는 역할

  • 애플리케이션 레벨 검증:

  • 도메인 로직, 권한, 입력값 검증 등 서비스 특화된 보안

  • 별도의 WAF/보안 솔루션:

  • 패턴 기반/시그니처 기반 공격, L7 DDoS, 정교한 봇 필터링

각 계층이 서로를 대체하기보다는, 서로를 보완하는 쪽으로 생각하면 좋습니다.

그중 nginx 블랙홀은:

  • 구현이 비교적 간단하고,
  • 효과는 의외로 체감이 크고,
  • “정신 건강에 좋다”는 부가 효과까지 있는,
  • 도입 비용 대비 효율이 매우 좋은 작은 트릭입니다.

마무리

인터넷에 노출된 서비스라면, “이상한 URL 스캔”을 피하는 건 사실상 불가능에 가깝습니다.

그렇다면 관점을 바꿔 볼 수 있습니다.

“어차피 올 거라면, 애플리케이션까지 들여보내지 말고 nginx 앞에서 조용히 정리해 버리자.”

blackhole.conf 하나로 공통 패턴을 관리하고, 444 응답으로 애플리케이션 앞단에서 불필요한 트래픽을 정리하면:

  • 앱 로그는 훨씬 더 깨끗해지고,
  • 모니터링/분석도 더 쉽고,
  • 서버 리소스도 조금이나마 여유가 생깁니다.

무엇보다, 새벽 2시에 로그를 열었을 때 끝없이 이어지는 /wp-login.php 행렬 대신 “진짜 사용자” 요청만 보게 되는 경험은 꽤 즐겁습니다.

운영 중인 nginx가 있다면, 이번 기회에 나만의 blackhole.conf를 하나 만들어 포함시켜 보세요. 며칠만 지나도 “이걸 왜 이제야 했지?”라는 생각이 들 겁니다.

image of nginx block malicious url