CHHB stroy

.htaccess로 특정 URL 차단하는 방법 — 서버 털리기 전에 읽어야 할 글 본문

기타

.htaccess로 특정 URL 차단하는 방법 — 서버 털리기 전에 읽어야 할 글

CHHB 2026. 5. 18. 15:56

서버 운영하다 보면 액세스 로그를 한번쯤 열어보게 된다. 그리고 놀란다. 내가 만든 적도 없는 경로로 요청이 미친 듯이 들어오고 있거든. /wp-login.php, /admin, /phpmyadmin, /.env 같은 것들. WordPress 안 쓰는데 wp-login으로 로그인 시도를 하고 있고, .env 파일을 긁어가려는 봇이 하루에도 수백 번씩 찔러본다.

처음 봤을 때 좀 소름 돋았다. "이게 매일 이러고 있었어?" 싶어서.

이런 요청들을 그냥 놔두면 서버 리소스도 잡아먹고, 혹시라도 실제로 취약점이 있으면 뚫릴 수도 있다. .htaccess로 이런 요청들을 차단하는 방법을 정리해봤다.


.htaccess가 뭔데

Apache 웹서버에서 디렉토리 단위로 설정을 제어하는 파일이다. 서버 설정 파일(httpd.conf)을 직접 건드리지 않고도 URL 리다이렉트, 접근 제어, 인증 같은 걸 할 수 있다.

파일명이 .으로 시작해서 숨김 파일이다. FTP나 파일 매니저에서 안 보이면 숨김 파일 표시 옵션을 켜야 한다.

⚠️ 주의: Nginx를 쓰고 있다면 .htaccess는 아무 효과 없다. Nginx는 이 파일을 무시한다. Nginx에서는 서버 설정 파일(nginx.conf 또는 사이트별 설정)에서 직접 처리해야 한다.


기본 전제: mod_rewrite 활성화 확인

URL 차단에 RewriteRule을 쓰려면 Apache의 mod_rewrite 모듈이 활성화돼 있어야 한다. 대부분의 웹호스팅에서는 기본으로 켜져 있는데, 혹시 안 되면 확인해보자.

# 모듈 확인 (서버 접근 가능한 경우)
apache2ctl -M | grep rewrite

# 결과에 rewrite_module이 있으면 OK

.htaccess 파일의 시작 부분에 이렇게 써주면 안전하다:

# mod_rewrite가 있을 때만 실행
<IfModule mod_rewrite.c>
    RewriteEngine On
    # 여기에 규칙 작성
</IfModule>

특정 URL 경로 차단하기

1. 정확한 경로 차단

가장 기본적인 방식. 특정 경로로 들어오는 요청을 403(Forbidden)으로 막는다.

<IfModule mod_rewrite.c>
    RewriteEngine On

    # wp-login.php 차단
    RewriteRule ^wp-login\.php$ - [F,L]

    # wp-admin 디렉토리 전체 차단
    RewriteRule ^wp-admin - [F,L]

    # phpMyAdmin 차단
    RewriteRule ^phpmyadmin - [F,L]
</IfModule>

[F,L]의 의미:

  • F = Forbidden (403 에러 반환)
  • L = Last (이 규칙에 매치되면 이후 규칙은 검사 안 함)

2. 여러 경로를 한 번에 차단

하나씩 쓰기 귀찮으면 |(OR)로 묶을 수 있다.

<IfModule mod_rewrite.c>
    RewriteEngine On

    # WordPress 관련 경로 일괄 차단
    RewriteRule ^(wp-login|wp-admin|wp-config|wp-includes|xmlrpc) - [F,L]

    # 흔한 공격 경로 일괄 차단
    RewriteRule ^(phpmyadmin|pma|myadmin|mysql|adminer) - [F,L]
</IfModule>

이게 훨씬 깔끔하다. 나는 이 방식을 선호한다.

3. 특정 파일 확장자 차단

설정 파일이나 백업 파일이 외부에서 접근되면 큰일난다. .env 파일에 DB 비밀번호가 다 들어있는데, 이게 웹에서 접근 가능하면... 생각만 해도 아찔하다.

<IfModule mod_rewrite.c>
    RewriteEngine On

    # 민감한 파일 차단
    RewriteRule \.env$ - [F,L]
    RewriteRule \.git - [F,L]
    RewriteRule \.sql$ - [F,L]
    RewriteRule \.bak$ - [F,L]
    RewriteRule \.log$ - [F,L]
    RewriteRule \.ini$ - [F,L]
    RewriteRule \.yml$ - [F,L]
    RewriteRule \.yaml$ - [F,L]
    RewriteRule \.toml$ - [F,L]
    RewriteRule \.lock$ - [F,L]
    RewriteRule \.conf$ - [F,L]
</IfModule>

또는 한 줄로:

RewriteRule \.(env|git|sql|bak|log|ini|yml|yaml|toml|lock|conf)$ - [F,L]

💡 : .git 디렉토리가 웹에서 접근 가능하면 소스코드 전체가 유출될 수 있다. 실제로 이걸로 뚫리는 사이트가 꽤 있다. /.git/HEAD로 접속해봐서 뭔가 나오면 바로 차단하자.

4. 쿼리스트링(Query String) 기반 차단

URL 뒤에 붙는 파라미터로 공격하는 경우도 많다. SQL 인젝션 시도 같은 거.

<IfModule mod_rewrite.c>
    RewriteEngine On

    # 쿼리스트링에 의심스러운 패턴 차단
    RewriteCond %{QUERY_STRING} (union|select|insert|drop|delete|update|concat|char\() [NC]
    RewriteRule .* - [F,L]

    # base64 인코딩된 공격 시도 차단
    RewriteCond %{QUERY_STRING} base64_encode [NC]
    RewriteRule .* - [F,L]

    # <script> 태그 삽입 시도 차단
    RewriteCond %{QUERY_STRING} (<script|%3Cscript) [NC]
    RewriteRule .* - [F,L]
</IfModule>

[NC]는 대소문자 무시(No Case)라는 뜻이다. 공격자가 UNION, Union, union 이런 식으로 바꿔가면서 시도하니까 꼭 넣어줘야 한다.

⚠️ 주의: 이 규칙이 너무 공격적이면 정상적인 요청도 차단될 수 있다. 예를 들어 게시판에서 SQL 관련 글을 쓸 때 "SELECT" 같은 단어가 쿼리스트링에 포함될 수 있다. 사이트 특성에 맞게 조절해야 한다.


특정 IP 차단하기

로그를 보면 특정 IP에서 집중적으로 공격하는 경우가 있다. 이런 건 IP 자체를 막아버리는 게 효율적이다.

Apache 2.4 이상

# 특정 IP 차단
<RequireAll>
    Require all granted
    Require not ip 192.168.1.100
    Require not ip 10.0.0.0/8
    Require not ip 203.0.113.50
</RequireAll>

Apache 2.2 (레거시)

# 옛날 방식 — 아직 이걸 쓰는 서버도 많다
Order Allow,Deny
Allow from all
Deny from 192.168.1.100
Deny from 203.0.113.0/24

특정 IP만 허용하고 나머지 차단

관리자 페이지 같은 데는 아예 특정 IP만 접근 가능하게 하는 게 안전하다.

# /admin 경로는 내 IP만 접근 가능
<If "%{REQUEST_URI} =~ m#^/admin#">
    Require ip 123.456.789.0
</If>

또는 Files/Directory 디렉티브로:

# 특정 파일 보호
<Files "admin.php">
    Require ip 123.456.789.0
</Files>

User-Agent 기반 차단

악성 봇은 User-Agent를 보면 대충 알 수 있다. 물론 위조가 쉬워서 완벽한 방법은 아니지만, 뻔한 봇은 걸러진다.

<IfModule mod_rewrite.c>
    RewriteEngine On

    # 악성 봇 / 스크래퍼 차단
    RewriteCond %{HTTP_USER_AGENT} (SemrushBot|AhrefsBot|DotBot|MJ12bot) [NC]
    RewriteRule .* - [F,L]

    # 빈 User-Agent 차단 (정상적인 브라우저는 UA가 있다)
    RewriteCond %{HTTP_USER_AGENT} ^$
    RewriteRule .* - [F,L]
</IfModule>

SEO 도구 봇(Semrush, Ahrefs)을 차단할지는 사이트 성격에 따라 다르다. 경쟁사 분석 도구라서 막고 싶을 수도 있고, 그냥 두고 싶을 수도 있다. 사이트에 부하를 많이 주는 봇이면 차단하는 게 낫다.


특정 HTTP 메서드 차단

웹사이트가 GET이랑 POST만 쓰는데 TRACE, DELETE, PUT 같은 요청이 들어오면 공격일 가능성이 높다.

<IfModule mod_rewrite.c>
    RewriteEngine On

    # GET, POST, HEAD만 허용
    RewriteCond %{REQUEST_METHOD} !^(GET|POST|HEAD)$ [NC]
    RewriteRule .* - [F,L]
</IfModule>

REST API를 운영하고 있다면 PUT, PATCH, DELETE도 허용해야 하니까 상황에 맞게.


실전: 내가 실제로 쓰는 .htaccess

위의 내용을 종합해서 내가 실제 서버에서 쓰고 있는 설정이다. 그대로 복붙해도 대부분의 사이트에서 잘 동작한다.

# ============================================
# 보안 설정
# ============================================

# mod_rewrite 활성화
<IfModule mod_rewrite.c>
    RewriteEngine On

    # ----------------------------------------
    # 1. 민감한 파일 접근 차단
    # ----------------------------------------
    RewriteRule \.(env|git|sql|bak|log|ini|yml|yaml|toml|lock|conf|swp)$ - [F,L]
    RewriteRule (composer\.(json|lock)|package\.json|package-lock\.json)$ - [F,L]
    RewriteRule (Makefile|Dockerfile|docker-compose|Vagrantfile) - [F,L]

    # .git 디렉토리 전체 차단
    RewriteRule ^\.git - [F,L]

    # .well-known은 허용 (SSL 인증서 갱신 등에 필요)
    RewriteRule ^\.(?!well-known) - [F,L]

    # ----------------------------------------
    # 2. WordPress 공격 차단 (WP 안 쓰는 경우)
    # ----------------------------------------
    RewriteRule ^(wp-login|wp-admin|wp-config|wp-content|wp-includes|xmlrpc)\.?.*$ - [F,L]

    # ----------------------------------------
    # 3. 흔한 공격 경로 차단
    # ----------------------------------------
    RewriteRule ^(phpmyadmin|pma|myadmin|adminer|mysql|dbadmin) - [F,L]
    RewriteRule ^(cgi-bin|scripts|shell|cmd) - [F,L]
    RewriteRule ^(eval|base64|exec)\b - [F,L]

    # ----------------------------------------
    # 4. 쿼리스트링 공격 차단
    # ----------------------------------------
    # SQL 인젝션 시도
    RewriteCond %{QUERY_STRING} (union\s+select|concat\s*\(|char\s*\(|0x[0-9a-f]+) [NC]
    RewriteRule .* - [F,L]

    # XSS 시도
    RewriteCond %{QUERY_STRING} (<script|%3Cscript|javascript:|vbscript:) [NC]
    RewriteRule .* - [F,L]

    # 원격 파일 포함 시도
    RewriteCond %{QUERY_STRING} (https?://|ftp://) [NC]
    RewriteRule .* - [F,L]

    # ----------------------------------------
    # 5. 허용하지 않는 HTTP 메서드 차단
    # ----------------------------------------
    RewriteCond %{REQUEST_METHOD} !^(GET|POST|HEAD)$ [NC]
    RewriteRule .* - [F,L]

    # ----------------------------------------
    # 6. 빈 User-Agent 차단
    # ----------------------------------------
    RewriteCond %{HTTP_USER_AGENT} ^$
    RewriteRule .* - [F,L]
</IfModule>

# ============================================
# 디렉토리 리스팅 비활성화
# ============================================
Options -Indexes

# ============================================
# 서버 정보 노출 방지
# ============================================
ServerSignature Off

# ============================================
# 보안 헤더
# ============================================
<IfModule mod_headers.c>
    # 클릭재킹 방지
    Header always set X-Frame-Options "SAMEORIGIN"

    # XSS 필터 활성화
    Header always set X-XSS-Protection "1; mode=block"

    # MIME 타입 스니핑 방지
    Header always set X-Content-Type-Options "nosniff"

    # Referrer 정보 제한
    Header always set Referrer-Policy "strict-origin-when-cross-origin"

    # 서버 헤더에서 PHP 버전 숨기기
    Header unset X-Powered-By
</IfModule>

주요 포인트 설명:

  • .well-known 디렉토리는 예외 처리했다. Let's Encrypt SSL 인증서 갱신할 때 이 경로를 쓰거든. 이걸 막아버리면 인증서 갱신이 안 돼서 HTTPS가 끊긴다. 한 번 겪으면 안 잊는다.
  • Options -Indexes는 디렉토리 목록 노출 방지. 이거 안 하면 images/ 같은 폴더에 접속했을 때 파일 목록이 쭉 보인다.
  • 보안 헤더는 .htaccess에서도 설정 가능하다. 클릭재킹, XSS, MIME 스니핑 같은 기본적인 공격을 막아준다.

차단 대신 리다이렉트하기

403 에러를 보여주는 대신 홈페이지로 보내버릴 수도 있다.

# 403 대신 홈으로 리다이렉트
RewriteRule ^wp-login\.php$ / [R=301,L]

# 특정 페이지로 리다이렉트
RewriteRule ^wp-admin /not-found [R=404,L]

근데 나는 그냥 403으로 끊는 걸 선호한다. 리다이렉트하면 봇이 "어, 응답이 왔네" 하고 계속 시도하는 경우가 있거든. 403이면 좀 더 빨리 포기하는 느낌이다 (물론 집요한 봇은 뭘 해도 온다).


차단이 제대로 되는지 확인하는 방법

설정 적용 후에 반드시 테스트하자. 잘못 쓰면 본인 사이트가 안 열린다. 나도 정규식 하나 잘못 써서 사이트 전체가 403 뜬 적 있다. 식은땀 장난 아니었다.

curl로 확인

# 차단된 경로 테스트
curl -I https://mysite.com/wp-login.php
# HTTP/1.1 403 Forbidden ← 이렇게 나오면 성공

# 정상 경로 테스트
curl -I https://mysite.com/
# HTTP/1.1 200 OK ← 이건 정상이어야 함

# .env 파일 접근 테스트
curl -I https://mysite.com/.env
# HTTP/1.1 403 Forbidden

# 빈 User-Agent로 테스트
curl -I -A "" https://mysite.com/
# HTTP/1.1 403 Forbidden

브라우저에서 확인

그냥 브라우저 주소창에 차단한 URL을 직접 쳐보면 된다. 403 에러 페이지가 뜨면 정상.

문법 오류 확인

# Apache 설정 문법 체크 (서버 접근 가능한 경우)
apachectl configtest
# Syntax OK ← 이게 나와야 함

.htaccess 수정할 때 주의사항

1. 반드시 백업

# 수정 전에 무조건 백업
cp .htaccess .htaccess.backup

잘못 수정하면 사이트가 안 열리니까. 백업 있으면 바로 복원할 수 있다.

2. 규칙 순서가 중요하다

.htaccess는 위에서 아래로 실행된다. [L] 플래그가 붙은 규칙에 매치되면 그 아래 규칙은 검사하지 않는다.

# 이 순서가 맞다
RewriteRule ^api/ - [L]           # API 경로는 통과
RewriteRule ^wp-login - [F,L]     # wp-login은 차단

# 이 순서는 위험하다
RewriteRule .* - [F,L]            # 모든 요청 차단 (아래 규칙 도달 불가)
RewriteRule ^api/ - [L]           # 이 규칙은 영원히 실행 안 됨

3. 정규식 실수

# ❌ 틀린 예 — 마침표를 이스케이프 안 함
RewriteRule ^wp-login.php$ - [F,L]
# "wp-loginXphp" 같은 것도 매치됨 (.은 정규식에서 '아무 문자' 의미)

# ✅ 맞는 예
RewriteRule ^wp-login\.php$ - [F,L]

정규식에서 .은 아무 문자를 의미한다. 진짜 마침표를 매치하려면 \.으로 이스케이프해야 한다. 사소한 것 같지만 보안 규칙에서 이런 실수가 있으면 의도와 다르게 동작한다.

4. 캐시 주의

브라우저가 301 리다이렉트를 캐시하는 경우가 있다. 테스트할 때 시크릿 모드(프라이빗 브라우징)를 쓰거나 curl로 확인하는 게 정확하다.


.htaccess의 성능 이슈

하나 알아둘 게 있다. .htaccess요청이 올 때마다 매번 파일을 읽는다. 규칙이 많아지면 그만큼 처리 시간이 늘어난다. Apache 공식 문서에서도 가능하면 .htaccess 대신 서버 설정 파일에 직접 쓰라고 권장한다.

근데 현실적으로 공유 호스팅에서는 서버 설정 파일을 못 건드리니까 .htaccess를 쓸 수밖에 없다. VPS나 전용 서버를 쓰고 있다면 httpd.confsites-available 설정 파일에 넣는 게 성능상 낫다.

# httpd.conf에서 .htaccess 비활성화 (성능 향상)
<Directory /var/www/html>
    AllowOverride None
</Directory>

AllowOverride None으로 하면 Apache가 .htaccess 파일을 아예 안 찾는다. 대신 모든 설정을 서버 설정 파일에서 해야 한다.


이것만으로는 부족한 것들

.htaccess만으로 모든 보안을 해결할 수는 없다. 추가로 고려할 것들:

  • fail2ban: 반복적으로 접근 시도하는 IP를 자동으로 방화벽에서 차단한다. .htaccess보다 더 근본적인 차단.
  • WAF(Web Application Firewall): Cloudflare 같은 서비스를 쓰면 서버에 도달하기도 전에 악성 요청을 걸러준다. 무료 플랜으로도 꽤 많이 막아준다.
  • ModSecurity: Apache 모듈로 설치하는 WAF. OWASP 룰셋을 적용하면 SQL 인젝션, XSS 같은 공격을 체계적으로 막을 수 있다.
  • 서버 업데이트: PHP, Apache, OS 보안 패치를 꾸준히 하는 게 사실 제일 중요하다. 아무리 .htaccess를 잘 써도 서버 자체에 취약점이 있으면 의미 없다.

마무리

.htaccess 설정은 한 번 해놓으면 계속 적용되니까 초기에 잘 세팅해두는 게 좋다. 특히 WordPress 안 쓰는데 wp-login 공격이 들어온다거나, .env 파일이 노출될 수 있는 상황이면 지금 당장 확인해보길 추천한다.

핵심 정리:

  • .env, .git 같은 민감한 파일은 반드시 차단하자
  • 안 쓰는 CMS(WordPress 등) 관련 경로도 차단하면 불필요한 요청을 줄일 수 있다
  • 쿼리스트링 기반 SQL 인젝션, XSS 시도도 막을 수 있다
  • 수정 전에 무조건 백업. 정규식 한 글자 실수로 사이트가 죽을 수 있다
  • 설정 후 curl로 반드시 테스트하자
  • .htaccess만으로는 한계가 있다. fail2ban, Cloudflare, ModSecurity도 같이 고려하자

궁금한 점이나 본인이 쓰는 차단 규칙이 있으면 댓글로 공유해주세요! 🙌