블로그로 돌아가기
2025년 10월 27일Sergei Solod6 분 읽기

내 정적 Next.js 사이트에는 하루 수천 건의 취약점 스캔이 들어온다. Nginx는 거의 흔들리지 않는다

로그에는 WordPress, PHP 백도어, .env, .git/config를 찾는 probe가 계속 쌓인다. 하지만 정적 Next.js + Nginx 구성에서는 대부분 저비용 miss와 404로 끝났고, 관찰 중 CPU 부하는 변하지 않았다. 이 경험이 정적 사이트 보안에 대해 말해 주는 것과 말해 주지 못하는 것을 구분해 본다.

보안Next.jsNginxDevOps정적 호스팅Attack Surface

매일 내 서버 로그에는 수천 건의 자동 취약점 스캔이 기록된다. 요청만 보면 꽤 위협적으로 보인다.

  • /wp-admin/wp.php
  • /111.php
  • /code.php
  • /.git/config
  • /i.php
  • /admin
  • /api/.env
  • /kal.php

내가 놀란 것은 숫자 자체가 아니라 서버의 반응이었다. 이 트래픽을 지켜보는 동안 CPU 부하에는 눈에 띄는 변화가 없었다.

이 관찰은 사실이지만, 처음 내렸던 설명은 너무 단정적이었다. 정적 배포는 마법의 방패가 아니고 404도 공짜가 아니다. 내 구조가 실제로 잘하는 일은 더 좁고 실용적이다. 우연히 들어오는 많은 probe가 저렴하게 끝나는 이유는 public request path에서 도달할 수 있는 server-side application logic이 거의 없기 때문이다.

이것은 probe이지, 침해가 일어났다는 증거가 아니다

/wp-admin/wp.php/111.php 같은 경로를 보면 모든 요청을 “exploit”이라고 부르고 싶어진다. 하지만 기술적으로는 정확하지 않다. 내가 주로 보는 것은 automated probing이다. bot이 흔한 파일명과 endpoint를 대입해 보고 무엇이 응답하는지 확인한다.

일부 경로는 WordPress나 PHP를 명확하게 겨냥한다. /.git/config/api/.env는 실수로 노출된 설정이나 secret을 찾는 유형이다. /admin은 더 일반적이며 정상 route일 수도 있다. 수상한 요청은 scanner가 무엇을 찾는지는 알려 주지만, 취약점이 실제로 존재한다는 뜻은 아니다.

내 경우 핵심은 아키텍처다. 공개 사이트는 Next.js로 미리 렌더링한 정적 HTML이며 Nginx가 직접 제공한다.

내 stack에서 이 트래픽의 비용이 작은 이유

여기서 말하는 페이지의 request path에는 매 요청마다 실행되는 PHP runtime이나 database가 없다. Nginx는 이미 존재하는 파일을 제공한다. scanner가 /111.php를 요청했는데 파일이 없으면 PHP application이 시작되지 않고, WordPress code도 실행되지 않으며, database query도 발생하지 않는다. 그냥 miss가 되고 404로 끝난다.

이 차이가 요청 비용 구조를 바꾼다. 동적 application에서는 하나의 request가 routing, framework, authentication, rendering, database, 외부 service까지 통과할 수 있다. 내 정적 경로는 moving parts가 훨씬 적다. 물론 Nginx도 connection을 받고, HTTP를 처리하고, resource를 찾고, log를 쓰고, response를 보내야 한다. 다만 application-level work가 작다.

사이트에는 aggressive caching도 사용한다. 일반적인 정적 콘텐츠 제공에는 도움이 된다. 하지만 임의의 404가 모두 cache 덕분이라고 보지는 않는다. 처음 보는 잘못된 URI라면 404를 반환하기 전에 일반 Nginx processing과 filesystem lookup이 일어날 수 있다.

중요한 수정: 정적이라고 해서 “기본적으로 안전”한 것은 아니다

지금이라면 이 부분을 가장 조심스럽게 쓸 것이다. 정적 아키텍처는 public request path에서 많은 server-side component를 제거하지만, 보안 작업 자체를 없애지는 않는다.

  • 노출된 파일은 정적 서버에서도 그대로 노출된다. .git, .env, backup, secret이 public directory에 들어가 있고 설정이 막지 않는다면 Nginx는 HTML처럼 효율적으로 그 파일을 제공할 수 있다.
  • Nginx, TLS, 운영체제, 네트워크는 여전히 attack surface다. 업데이트와 올바른 설정이 필요하다.
  • client-side code에도 취약점이 생길 수 있다. 정적 HTML이 JavaScript, 브라우저의 auth logic, third-party script를 자동으로 안전하게 만들지는 않는다.
  • resource exhaustion도 가능하다. connection, bandwidth, TLS handshake, worker capacity, log I/O, disk space는 유한하다. 충분한 트래픽은 정적 사이트에도 문제를 만들 수 있다.

따라서 정확한 결론은 “이 요청들은 아무 의미가 없다”가 아니다. 이 아키텍처에서는 많은 application-specific probe가 실행할 대상을 찾지 못한다가 더 맞다.

“하루 수천 건”이라는 숫자는 실제 부하보다 더 무섭게 보일 수 있다

로그에서는 실패한 probe 하나하나가 줄로 보이기 때문에 Internet background noise가 굉장히 심해 보인다. 하지만 하루 총량만으로 운영상 영향은 알 수 없다. 24시간에 걸쳐 분산된 수천 요청과 짧은 burst에 몰린 같은 수의 요청은 완전히 다르다.

CPU 역시 하나의 signal일 뿐이다. 내 경우 CPU가 그대로였다는 것은 관찰 당시 이 트래픽이 눈에 띄는 compute pressure를 만들지 않았다는 유용한 증거다. 다른 비용까지 0이었다는 증거는 아니다. 제대로 평가하려면 request rate, bandwidth, active connections, Nginx worker 사용량, response latency, log volume, disk usage도 함께 봐야 한다.

이 noise를 보며 배운 것

내가 얻은 가장 큰 교훈은 아키텍처 자체가 scanner signature마다 별도 rule을 만들지 않아도 자동 공격의 여러 범주를 무의미하게 만들 수 있다는 점이다. bot이 하루 종일 WordPress와 PHP backdoor를 찾아도, 공개 request path에 둘 다 없다면 그 probe가 활용할 것이 거의 없다.

다만 이제는 “내 stack에는 관련이 적다”와 “무해한 traffic”을 같은 말로 쓰지 않는다. 현재 내 기준은 다음과 같다.

  • public request path에서 실제로 도달 가능한 runtime과 service를 파악한다.
  • secret, repository metadata, backup, build artifact를 web root 밖에 두거나 명시적으로 차단한다.
  • 다른 targeting이나 impact 증거가 없다면 반복 404 probe를 일반적인 Internet background noise로 본다.
  • CPU만 보고 abusive traffic의 비용이 없다고 판단하지 않는다.
  • application이 정적이어도 Nginx, OS, TLS library, dependency를 계속 patch한다.

이 경험이 증명하지 못하는 것

나는 controlled benchmark를 실행하지 않았고 DDoS를 재현하지도 않았다. 매일 수천 건의 scanner request가 들어오는 동안 production log와 server load를 관찰했을 뿐이다. 내 setup에 대한 practical conclusion에는 충분하지만 “정적 사이트가 항상 이긴다”는 보편적 규칙을 증명하지는 않는다.

더 정확하게 확인할 수 있는 사실은 이것이다. Nginx가 제공하는 내 Next.js static export에서는 관찰한 scanner traffic 대부분이 PHP runtime이나 database-backed application까지 가지 않고 저비용 file miss와 404로 끝났다. 관찰 중 CPU 부하는 변하지 않았다.

내가 중요하게 보는 resilience는 바로 이 부분이다. moving parts가 적다고 시스템이 무적이 되지는 않는다. 대신 random Internet noise가 실제 application work로 바뀔 수 있는 지점을 줄여 준다.