들어가며: 작은 차이가 큰 문제를 만든다

오늘 아파치 RewriteRule에서 매우 간단해 보이지만 실제로는 중요한 차이를 발견했다.

1
2
3
4
5
# 규칙 1
RewriteRule ^(.*)$ https://example.co.jp/? [R=302,L]

# 규칙 2
RewriteRule ^(.*)$ https://example.co.jp/ [R=302,L]

이 둘의 차이가 뭘까?

쿼리스트링 처리: 직관을 벗어나다

처음 봤을 때의 직관:

  • /? = 명시적으로 “아무것도 없다” → 쿼리스트링을 버릴 것 같다
  • / = 경로만 지정 → 원래 쿼리스트링이 유지될 것 같다

그런데 실제로는 반대다.

실제 동작

규칙 1 (/?):

요청: /page?foo=bar&baz=qux
결과: https://example.co.jp/

→ 쿼리스트링이 버려진다

규칙 2 (/):

요청: /page?foo=bar&baz=qux
결과: https://example.co.jp/?foo=bar&baz=qux

→ 쿼리스트링이 유지된다

왜 이런 이상한 일이 일어날까?

Apache의 mod_rewrite 문서를 읽으면:

“By default, the query string is passed through unchanged.”

즉, 기본적으로 쿼리스트링은 유지된다.

그래서:

  • ? = 쿼리스트링을 명시적으로 비우겠다 (버리기)
  • / = 아무 지정 안 함 = 기본값 = 쿼리스트링 유지

Apache 2.4의 전환: QSD의 등장

이 복잡함을 해결하기 위해 Apache 2.4부터는 새로운 플래그가 도입된다:

1
2
# Apache 2.4+: 쿼리스트링을 명시적으로 버리기
RewriteRule ^(.*)$ https://example.co.jp/ [R=302,L,QSD]

QSD = Query String Discard

이제 의도가 명확하다:

  • QSD = 쿼리스트링을 명시적으로 버린다
  • 플래그 없음 = 쿼리스트링을 기본대로 유지한다

Apache 버전별 권장 방식

Apache 2.2 이전

1
2
3
4
5
# 버리기
RewriteRule ^(.*)$ https://example.co.jp/? [R=302,L]

# 유지하기 (기본값이므로 명시 안 해도 됨)
RewriteRule ^(.*)$ https://example.co.jp/ [R=302,L]

Apache 2.4+

1
2
3
4
5
# 버리기 (명시적)
RewriteRule ^(.*)$ https://example.co.jp/ [R=302,L,QSD]

# 유지하기 (기본값이므로 명시 안 해도 됨)
RewriteRule ^(.*)$ https://example.co.jp/ [R=302,L]

더 나은 실천법

  1. Apache 2.4 이상이면 QSD를 쓰자

    • 의도가 명확함
    • 유지보수가 쉬움
    • 버전 호환성도 좋음
  2. QSA는 이제 선택사항

    • 쿼리스트링을 유지하고 싶을 때 명시적으로 쓸 수 있음
    • 하지만 기본값이므로 안 써도 됨
  3. 의도를 명확히 하자

    1
    2
    3
    4
    5
    6
    
    # 좋은 예
    RewriteRule ^(.*)$ https://example.co.jp/ [R=302,L,QSD]     # 명시적으로 버림
    RewriteRule ^(.*)$ https://example.co.jp/ [R=302,L,QSA]     # 명시적으로 유지
    
    # 피하기
    RewriteRule ^(.*)$ https://example.co.jp/? [R=302,L]        # ?는 헷갈림
    

교훈: 직관이 틀릴 수 있다

오늘 배운 가장 큰 교훈은 **“명시적으로 보이는 것이 실제 의도를 담지 못할 수 있다”**는 것.

웹서버 설정, 특히 URL 리라이트는:

  • 예상과 달리 작동할 수 있다
  • 버전마다 다를 수 있다
  • 문서를 꼼꼼히 읽어야 한다

다음번엔 불확실하면 검색하고, 결과를 확인해야겠다. 😊


참고 자료: