.htaccess RewriteRule 순서 오류 | 관리자 페이지 예외 처리

.htaccess 규칙은 위에서 아래로 순서대로 읽힙니다. 이 말을 알고는 있었지만, 실제로는 규칙 순서를 대충 섞어 넣었다가 게시판 주소와 관리자 페이지가 번갈아 고장 났습니다.

이 글은 .htaccess 문법을 처음부터 설명하는 글이 아닙니다. 짧은 URL을 만들면서 RewriteRule 순서를 잘못 배치했고, 끝 슬래시 제거 규칙까지 겹치면서 관리자 페이지까지 깨졌던 경험을 바탕으로 어떤 규칙을 먼저 두고 어떤 경로를 예외 처리해야 하는지 정리한 글입니다.

.htaccess에서 실제 폴더와 파일을 제외하고 구체적인 RewriteRule을 먼저 배치하는 예시

짧은 URL 작업 중 생긴 문제

원래 주소는 이런 형태였습니다.

list.php?id=site&menu=856
page.php?id=site&menu=1250
page.php?id=site

이 주소를 아래처럼 짧게 바꾸고 싶었습니다.

/site/board/856
/site/1250
/site

겉으로 보면 단순합니다. 게시판은 /board/를 붙이고, 일반 페이지는 숫자만 붙이면 될 것 같았습니다. 여기에 SEO 중복 URL 정리를 위해 끝 슬래시 제거와 쿼리스트링 정리까지 같이 넣었습니다.

문제는 이때부터였습니다. 규칙이 하나씩 늘어나자 어떤 주소는 게시판으로 가고, 어떤 주소는 일반 페이지로 가고, 관리자 페이지는 CSS가 깨져서 열렸습니다. 원인은 대부분 코드 내용보다 규칙 순서였습니다.

일반 규칙이 구체적인 주소를 먼저 처리

처음에는 이런 식으로 작성했습니다.

# 잘못된 순서
RewriteRule ^([A-Za-z0-9_-]+)/([0-9]+)$ page.php?id=$1&menu=$2 [L,QSA]
RewriteRule ^([A-Za-z0-9_-]+)/board/([0-9]+)$ list.php?id=$1&menu=$2 [L,QSA]

/site/856은 일반 페이지 규칙에 맞습니다. /site/board/856은 두 번째 규칙에 맞습니다. 그런데 실제 사이트에서는 게시판 번호와 페이지 번호가 섞여 있었고, /site/856과 /site/board/856이 비슷한 내용을 보여주는 문제가 생겼습니다.

검색엔진 입장에서는 같은 내용이 두 주소로 열리는 중복 URL처럼 보일 수 있습니다.

이럴 때는 규칙을 넓은 것부터 두면 안 됩니다. 더 구체적인 주소를 먼저 처리해야 합니다.

# 구체적인 규칙 먼저
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([A-Za-z0-9_-]+)/board/([0-9]+)/search$ list.php?id=$1&menu=$2&mode=search [L,QSA]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([A-Za-z0-9_-]+)/board/([0-9]+)$ list.php?id=$1&menu=$2 [L,QSA]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([A-Za-z0-9_-]+)/([0-9]+)$ page.php?id=$1&menu=$2 [L,QSA]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([A-Za-z0-9_-]+)$ page.php?id=$1 [L,QSA]

배치 기준은 간단합니다.

  • 긴 주소 패턴을 먼저 배치
  • /board/처럼 고정 문자열이 있는 규칙을 먼저 배치
  • 숫자만 받는 넓은 규칙은 뒤로 배치
  • 실제 파일과 폴더는 리라이트 대상에서 제외

이 순서만 지켜도 예상하지 못한 충돌을 많이 줄일 수 있습니다.

끝 슬래시 제거 후 관리자 페이지 오류

짧은 URL 정리를 하다 보면 /site/와 /site처럼 끝 슬래시만 다른 주소가 생깁니다. 중복 URL을 줄이려고 끝 슬래시를 제거하는 규칙을 넣었습니다.

# 위험한 버전
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [R=301,L]

처음에는 잘 되는 것처럼 보였습니다. /site/가 /site로 이동했기 때문입니다. 그런데 관리자 페이지를 열자 화면이 깨졌습니다.

이유는 /admin/이 실제로 존재하는 디렉토리였기 때문입니다. 디렉토리 주소에서 슬래시가 사라지면 상대경로로 불러오는 CSS, JS, 이미지 경로가 꼬일 수 있습니다.

그래서 실제 파일과 디렉토리는 건드리지 않도록 조건을 추가했습니다.

# 안전한 버전
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [R=301,L]

!-d는 실제 디렉토리가 아닐 때만, !-f는 실제 파일이 아닐 때만 규칙을 적용하라는 뜻입니다. 짧은 두 줄이지만 관리자 페이지, 업로드 폴더, 이미지 폴더를 보호하는 역할을 합니다.

관리자와 업로드 폴더는 먼저 예외 처리

관리자 페이지나 업로드 폴더처럼 실제로 존재하는 경로는 리라이트 규칙보다 먼저 보호하는 편이 안전합니다.

# 관리자와 업로드 경로는 리라이트 제외
RewriteRule ^admin(/.*)?$ - [L]
RewriteRule ^upload(/.*)?$ - [L]
RewriteRule ^images(/.*)?$ - [L]

이 규칙은 해당 경로가 들어오면 더 아래의 짧은 URL 규칙으로 넘기지 않고 그대로 멈추게 합니다.

사이트 구조에 따라 data, files, assets, skin, css, js 같은 폴더도 예외 목록에 넣어야 할 수 있습니다. 오래된 PHP 사이트는 상대경로로 파일을 불러오는 경우가 많아서, 실제 폴더를 리라이트 대상으로 잡으면 화면이 쉽게 깨집니다.

쿼리스트링 정리 규칙과 함께 쓸 때 주의점

짧은 URL 작업과 쿼리스트링 제거 작업을 같이 하면 충돌이 더 쉽게 납니다.

예를 들어 아래처럼 무조건 쿼리스트링을 제거하면 검색이나 페이징 값이 같이 사라질 수 있습니다.

# 위험한 예
RewriteCond %{QUERY_STRING} !^$
RewriteRule ^(.*)$ /$1? [R=301,L]

검색어, 페이지 번호, 필터 값은 쿼리스트링이지만 실제 기능에 필요한 값입니다. 그래서 무조건 제거하지 말고 필요한 파라미터는 예외로 둬야 합니다.

# 검색과 페이징 파라미터는 유지
RewriteCond %{QUERY_STRING} !^$
RewriteCond %{QUERY_STRING} !(^|&)q=
RewriteCond %{QUERY_STRING} !(^|&)keyword=
RewriteCond %{QUERY_STRING} !(^|&)page=
RewriteCond %{QUERY_STRING} !(^|&)start=
RewriteRule ^(.*)$ /$1? [R=301,L]

리라이트 규칙은 URL 모양을 바꾸는 작업이고, 쿼리스트링 정리는 파라미터를 지우는 작업입니다. 둘을 한 번에 넣으면 어느 규칙이 먼저 동작했는지 헷갈리기 쉽습니다. 가능하면 하나씩 적용하고, 적용할 때마다 네트워크 탭에서 이동 흐름을 확인하는 편이 안전합니다.

어떤 규칙이 실행됐는지 확인하는 방법

규칙이 어디에서 걸리는지 모를 때는 서버 로그가 가장 좋습니다.

LogLevel alert rewrite:trace3

다만 일반 호스팅에서는 Apache 설정을 직접 바꾸기 어려운 경우가 많습니다. 그럴 때는 PHP 파일 맨 위에 임시 응답 헤더를 넣어 확인할 수 있습니다.

<?php
header('X-Debug-Rule: page.php');

게시판 파일에는 이렇게 넣습니다.

<?php
header('X-Debug-Rule: list.php');

그다음 개발자 도구의 네트워크 탭에서 응답 헤더를 확인합니다.

/site/board/856  →  X-Debug-Rule: list.php
/site/1250       →  X-Debug-Rule: page.php
/admin/          →  리라이트 대상 아님

이렇게 보면 주소가 어느 파일로 연결되는지 바로 확인할 수 있습니다. 문제가 해결되면 디버그 헤더는 반드시 제거해야 합니다.

배포 전 확인할 주소 목록

.htaccess는 한 줄만 바꿔도 사이트 전체 흐름이 달라질 수 있습니다. 그래서 배포 전에는 대표 주소를 직접 열어봐야 합니다.

curl -I https://example.com/site/board/856
curl -I https://example.com/site/856
curl -I https://example.com/site/
curl -I https://example.com/site
curl -I https://example.com/admin/
curl -I https://example.com/upload/

브라우저에서도 같이 확인했습니다.

  • 게시판 상세 주소가 원하는 파일로 연결되는지 확인
  • 일반 페이지 주소가 게시판으로 잘못 가지 않는지 확인
  • /admin/ 로그인 화면 CSS가 깨지지 않는지 확인
  • /upload/, /images/ 파일 접근이 막히지 않는지 확인
  • 끝 슬래시 제거가 실제 디렉토리에는 적용되지 않는지 확인
  • 리다이렉트가 여러 번 반복되지 않는지 확인

curl -I로 상태 코드를 보고, 브라우저 네트워크 탭으로 실제 이동 경로를 보면 어느 줄에서 문제가 생기는지 훨씬 빨리 찾을 수 있습니다.

.htaccess 수정 체크리스트

규칙 한 줄보다 적용 순서가 더 중요

.htaccess 문제는 문법이 틀려서만 생기지 않습니다. 문법은 맞는데 순서가 틀려서 생기는 문제가 더 많았습니다. 특히 짧은 URL, 끝 슬래시 제거, 쿼리스트링 정리를 한 파일에 같이 넣으면 작은 순서 차이로 전혀 다른 결과가 나옵니다.

제가 얻은 기준은 단순합니다. 구체적인 규칙을 먼저 두고, 실제 파일과 폴더는 건드리지 않는 것입니다. 그리고 관리자 페이지, 업로드 폴더, 검색, 페이징처럼 사용자가 직접 쓰는 기능은 SEO 정리보다 먼저 보호해야 합니다.

주소가 깔끔해지는 것만 보면 성공처럼 보일 수 있습니다. 하지만 관리자 페이지가 깨지거나 검색 파라미터가 사라지면 실제 운영에서는 실패입니다. .htaccess를 고칠 때는 한 번에 많이 바꾸지 말고, 한 줄씩 적용하면서 주소와 기능을 같이 확인하는 편이 가장 안전했습니다.

댓글 남기기