도메인을 바꿨거나, http로 운영하던 사이트를 https로 넘겼거나, 글 주소 구조를 한 번 갈아엎었다고 해봅시다. 문제는 그다음입니다. 검색 결과에는 아직 옛날 주소가 그대로 떠 있고, 누군가는 3년 전 북마크로 들어옵니다. 그 사람이 보게 되는 건 404 페이지입니다. 방문자 한 명 잃는 걸로 끝나지 않고, 구글이 그 주소를 계속 긁다가 “이 페이지 없어졌네” 하고 색인에서 빼버립니다. 그동안 쌓은 검색 순위가 같이 날아갑니다.
이걸 막는 게 리다이렉트입니다. “옛날 주소로 오면 새 주소로 보내라”는 지시를 서버에 심어두는 거죠. 아파치 웹서버를 쓴다면 그 지시를 적는 파일이 .htaccess입니다. 서버 전체 설정 파일을 못 건드리는 공용 웹호스팅에서도 이 파일 하나만 있으면 리다이렉트를 걸 수 있어서, 카페24·가비아 같은 국내 호스팅을 쓰는 분들이 가장 먼저 찾게 되는 파일이기도 합니다.
한눈에 · 내 서버가 아파치 계열인지 확인 →
.htaccess백업 → Redirect 301로 단순 이전 → RewriteRule로 https·www 통일 →curl -I로 301과 Location 확인
리다이렉트가 필요해지는 순간
.htaccess는 이름 그대로 하이퍼텍스트 액세스 설정 파일입니다. 아파치가 요청을 처리하기 직전에 해당 디렉터리의 .htaccess를 읽고, 거기 적힌 규칙으로 서버 전역 설정을 그 디렉터리 범위에서만 덮어씁니다. 그래서 루트 권한이 없어도, 아파치를 재시작하지 않아도 규칙이 바로 적용됩니다.
리다이렉트를 안 걸고 그냥 두면 세 가지가 동시에 벌어집니다. 서버 로그에 404가 쌓이고, 검색엔진은 옛 URL의 평가를 새 URL로 넘겨주지 않고 그냥 버리고, 사용자 북마크와 외부 사이트의 링크가 전부 깨집니다. 특히 두 번째가 아픕니다. 301을 걸면 옛 주소의 평가가 새 주소로 이어지지만, 404로 방치하면 처음부터 다시 시작해야 합니다.
.htaccess는 어디에 두는 파일인가
웹 문서 루트에 둡니다. 워드프레스라면 wp-config.php가 있는 그 폴더입니다. 경로는 환경마다 다른데 흔한 곳은 이렇습니다.
/var/www/html/.htaccess
/home/계정명/public_html/.htaccess
/usr/local/apache/htdocs/.htaccess
하위 폴더에도 따로 둘 수 있습니다. 이 경우 하위 폴더의 .htaccess가 상위 것을 덮어씁니다. “루트에 규칙을 넣었는데 특정 폴더에서만 안 먹는다”면 그 폴더에 다른 .htaccess가 이미 있는 경우가 대부분입니다.
Apache / LiteSpeed / Nginx 중 내 서버는 어디인가
이건 먼저 확인해야 합니다. .htaccess는 아파치 전용 문법이고, Nginx는 이 파일을 아예 읽지 않습니다. 올려도 아무 반응이 없습니다. 에러도 안 납니다. 그냥 무시됩니다. LiteSpeed와 OpenLiteSpeed는 아파치 문법과 호환되니 그대로 쓰면 됩니다.
curl -I https://example.com | grep -i server
# Server: Apache/2.4.52 (Ubuntu)
# Server: LiteSpeed
# Server: nginx/1.18.0
Server 헤더를 감춰둔 서버도 많습니다. SSH로 들어갈 수 있으면 apache2 -v 또는 nginx -v를 쳐보는 게 확실합니다.
손대기 전에 확인할 것 세 가지
아파치 계열인 걸 확인했다면, 이제 규칙이 실제로 먹히는 환경인지 봐야 합니다. 문법을 아무리 정확하게 써도 아래 세 가지 중 하나가 어긋나면 아무 일도 일어나지 않습니다.
첫째는 AllowOverride 설정입니다. 아파치 설정 파일에서 이 값이 None으로 되어 있으면 아파치는 .htaccess 파일 자체를 읽지 않습니다. 규칙을 아무리 잘 써도 무시됩니다. 최소한 AllowOverride FileInfo 또는 AllowOverride All이어야 리다이렉트 지시어가 동작합니다.
<Directory /var/www/html>
AllowOverride All
</Directory>
직접 서버를 운영한다면 /etc/apache2/apache2.conf나 /etc/httpd/conf/httpd.conf에서 고치고 아파치를 재시작하면 됩니다. 공용 호스팅이라면 이 파일에 접근할 수 없으니 호스팅사에 “.htaccess의 AllowOverride가 All로 되어 있는지” 문의해야 합니다.
mod_rewrite 켜져 있는지 확인하기
두 번째는 mod_rewrite 모듈입니다. Redirect는 mod_alias로 동작하지만, 조건부 리다이렉트에 쓰는 RewriteRule은 mod_rewrite가 있어야 합니다. 로드된 모듈 목록에서 확인합니다.
# 우분투 / 데비안 계열
apache2ctl -M | grep rewrite
# rewrite_module (shared)
# CentOS / 로키 / 알마 계열
httpd -M | grep rewrite
# rewrite_module (shared)
아무것도 안 나오면 꺼져 있는 겁니다. 우분투는 a2enmod rewrite 실행 후 systemctl restart apache2로 켜면 됩니다. 서비스 재시작이 헷갈린다면 systemctl 서비스 시작·종료·부팅 자동실행 정리 쪽을 같이 보시면 됩니다.
백업 없이 수정하면 벌어지는 일
세 번째이자 제일 중요한 준비입니다. .htaccess는 오타 하나로 사이트 전체가 500 에러로 죽는 파일입니다. 특정 페이지만 깨지는 게 아니라 첫 화면부터 안 뜹니다. 수정 전에 무조건 복사본을 남기세요.
cd /var/www/html
cp .htaccess .htaccess.bak
ls -la | grep htaccess
# -rw-r--r-- 1 www-data www-data 512 Feb 10 14:22 .htaccess
# -rw-r--r-- 1 www-data www-data 512 Feb 10 14:22 .htaccess.bak
권한은 보통 644, 소유자는 웹서버 실행 계정(www-data, apache, nobody 등)이나 호스팅 계정입니다. 600이나 640으로 잠가버리면 아파치가 못 읽어서 무시되거나 500이 납니다.
FTP 클라이언트로 편집한다면 점으로 시작하는 파일이 목록에 안 보일 겁니다. FileZilla는 서버 메뉴의 “강제로 숨김 파일 표시”, 파일질라 외 대부분 클라이언트도 비슷한 옵션이 있습니다. 이걸 안 켜서 “파일이 없는 줄 알고 새로 만들었다가 기존 규칙을 통째로 날리는” 사고가 꽤 흔합니다. 파일 전체가 아니라 사이트 단위 백업이 필요하다면 워드프레스 백업, 파일과 DB 둘 다 챙기는 실전 순서를 참고하세요.
가장 단순한 방법, Redirect와 RedirectMatch
준비가 끝났으니 제일 쉬운 문법부터 봅니다. Redirect는 mod_alias만 있으면 되고 정규식도 거의 필요 없습니다. 조건 없이 “이 경로는 저리로”만 하면 되는 상황이라면 굳이 RewriteRule을 꺼낼 이유가 없습니다.
301과 302, 언제 무엇을 쓰나
숫자는 HTTP 상태 코드입니다. 이 차이가 실무에서는 꽤 큽니다.
| 구분 | 301 (Moved Permanently) | 302 (Found, 임시) |
|---|---|---|
| 의미 | 영구 이전 | 임시 이전 |
| 검색엔진 | 색인·평가를 새 URL로 이전 | 옛 URL을 계속 색인 |
| 브라우저 캐시 | 강하게 캐시됨 | 거의 캐시 안 함 |
| 되돌리기 | 어려움(캐시가 남음) | 쉬움 |
| 쓰는 상황 | 도메인 변경, https 전환, 영구 URL 변경 | 점검 페이지, A/B 테스트, 임시 우회 |
테스트 단계에서는 302로 걸고, 목적지가 확정된 뒤에 301로 바꾸는 게 안전합니다. 301은 브라우저가 아주 오래 캐시로 들고 있습니다. 잘못된 주소로 301을 걸어놓고 나중에 고쳐도, 이미 한 번 접속했던 사람 브라우저에서는 계속 옛 목적지로 갑니다. 크롬은 이 캐시를 수동으로 지우기 전엔 안 놓아줍니다.
경로 하나만 옮길 때
가장 기본형입니다. 왼쪽은 반드시 슬래시로 시작하는 경로, 오른쪽은 경로 또는 전체 URL입니다.
Redirect 301 /old-page.html /new-page.html
Redirect 301 /company/intro.html https://example.com/about/
왼쪽에 https://example.com/old-page.html처럼 전체 URL을 쓰면 동작하지 않습니다. 여기서 막히는 분이 많습니다. 왼쪽은 항상 경로만입니다.
하위 경로까지 통째로 옮길 때
디렉터리를 통째로 옮겼다면 상위 경로만 적으면 됩니다. 하위 경로가 자동으로 따라붙습니다.
Redirect 301 /blog /news
이렇게 쓰면 /blog/2024/hello는 /news/2024/hello로 넘어갑니다. 하나하나 다 적을 필요가 없습니다. 다만 구조가 그대로 유지될 때만 통합니다. 경로 형태가 바뀌었다면 정규식이 필요합니다.
RedirectMatch 301 ^/archive/(.*)$ /posts/$1
^/archive/로 시작하는 모든 요청을 잡아서, 괄호로 캡처한 뒷부분($1)을 /posts/ 뒤에 붙입니다. /archive/2023/note는 /posts/2023/note가 됩니다.
여기까지가 Redirect 계열로 할 수 있는 전부입니다. 그런데 “http로 들어왔을 때만”, “호스트 이름이 old.com일 때만”, “쿼리스트링에 특정 파라미터가 있을 때만” 같은 조건을 붙이려면 이걸로는 안 됩니다. 조건을 판단할 문법이 없기 때문입니다.
조건이 붙으면 RewriteRule
조건부 처리는 mod_rewrite가 담당합니다. 구조는 항상 같습니다. 엔진을 켜고, 조건을 적고, 조건이 맞으면 실행할 규칙을 적습니다.
RewriteEngine On
RewriteCond %{조건 대상} 조건값
RewriteRule 매칭패턴 목적지 [플래그]
RewriteCond는 바로 다음 줄의 RewriteRule 하나에만 적용됩니다. 조건 여러 개를 연달아 쓰면 기본적으로 AND로 묶입니다.

http를 https로 강제하기
가장 많이 찾는 규칙입니다. 일반적인 단독 서버라면 이걸 씁니다.
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
%{HTTPS}는 아파치가 제공하는 변수로, https 접속이면 on, 아니면 off입니다. %{HTTP_HOST}는 요청에 들어온 도메인이라 도메인을 하드코딩하지 않아도 됩니다. 서브도메인이 여러 개여도 그대로 씁니다.
문제는 Cloudflare나 로드밸런서, 리버스 프록시 뒤에 있는 서버입니다. 이 경우 방문자→프록시 구간은 https지만 프록시→원서버 구간은 http인 경우가 많습니다. 그러면 원서버 입장에서 %{HTTPS}는 계속 off입니다. 리다이렉트를 걸어도 다시 http로 들어오고, 또 리다이렉트하고, 무한 반복입니다. 브라우저에는 ERR_TOO_MANY_REDIRECTS가 뜹니다.
이럴 땐 프록시가 붙여주는 헤더를 봐야 합니다.
RewriteEngine On
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
X-Forwarded-Proto는 프록시가 “원래 클라이언트는 이 프로토콜로 들어왔다”고 알려주는 헤더입니다. 값이 https가 아닐 때만(!https) 리다이렉트합니다. Cloudflare를 쓰는데 무한 리다이렉트가 난다면 십중팔구 이 문제입니다.
www 있는 주소 / 없는 주소 통일하기
example.com과 www.example.com은 검색엔진 입장에서 서로 다른 사이트입니다. 같은 내용이 두 주소에 있으면 중복 콘텐츠로 평가가 갈립니다. 하나로 몰아야 합니다.
www를 붙이는 쪽으로 통일:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^example\.com [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]
www를 떼는 쪽으로 통일:
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.example\.com [NC]
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]
어느 쪽이든 상관없습니다. 하나만 정하고 끝까지 그걸 쓰면 됩니다. 다만 SSL 인증서가 선택한 쪽을 커버하는지는 확인해야 합니다. www 없는 주소로 통일했는데 인증서에 example.com이 안 들어 있으면 인증서 경고가 뜹니다.
옛 도메인에서 새 도메인으로
도메인 자체를 바꿨다면 옛 도메인 쪽 .htaccess에 이걸 넣습니다. 옛 도메인의 웹 루트가 새 도메인과 같은 폴더를 보고 있어도 동작합니다.
RewriteEngine On
RewriteCond %{HTTP_HOST} ^old\.com [NC,OR]
RewriteCond %{HTTP_HOST} ^www\.old\.com [NC]
RewriteRule (.*) https://new.com/$1 [R=301,L]
점 앞의 백슬래시는 정규식에서 점을 “아무 문자”가 아니라 진짜 점으로 읽으라는 표시입니다. 빼먹어도 대개 동작은 하지만, oldXcom 같은 엉뚱한 호스트에도 걸릴 수 있으니 붙이는 게 맞습니다. [OR]는 위아래 조건을 OR로 묶습니다.
플래그 [R=301,L] 뜯어보기
대괄호 안의 플래그가 규칙의 성격을 결정합니다. 자주 쓰는 것만 정리합니다.
| 플래그 | 뜻 | 언제 쓰나 |
|---|---|---|
[R=301] | 브라우저에 301 응답을 보내 주소를 실제로 바꿈 | 외부 리다이렉트. 생략하면 서버 내부에서만 경로가 바뀌고 주소창은 그대로 |
[L] | Last. 여기서 규칙 처리를 멈춤 | 뒤 규칙이 다시 걸려 꼬이는 걸 방지. 리다이렉트 규칙엔 거의 항상 붙임 |
[NC] | No Case. 대소문자 구분 안 함 | 호스트명·경로 매칭. Example.com도 잡으려면 필요 |
[QSA] | Query String Append. 원래 쿼리스트링을 목적지에 이어붙임 | ?utm_source=... 같은 파라미터를 살려야 할 때 |
$1과 %1은 다릅니다. $1은 RewriteRule의 괄호가 잡은 값, %1은 바로 위 RewriteCond의 괄호가 잡은 값입니다. 헷갈리면 서브도메인 처리 같은 규칙이 통째로 어긋나니 주의하세요.
워드프레스 서버에서 특히 조심할 것
워드프레스를 쓰고 있다면 .htaccess에 이미 규칙이 들어 있습니다. 여기에 아무 데나 규칙을 끼워 넣으면 실행이 안 되거나, 나중에 통째로 사라집니다. 기본 블록은 이렇게 생겼습니다.
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
BEGIN WordPress 블록 위에 써야 하는 이유
이유가 두 개입니다. 첫째, 워드프레스는 퍼머링크 설정을 저장할 때마다 # BEGIN WordPress부터 # END WordPress까지를 통째로 지우고 다시 씁니다. 그 안에 내 규칙을 넣어두면 어느 날 소리 없이 사라집니다.
둘째, 마지막 줄 RewriteRule . /index.php [L]이 문제입니다. 이 규칙은 실제 파일이나 폴더가 아닌 모든 요청을 index.php로 넘기고 [L]로 처리를 끝냅니다. 내 리다이렉트 규칙이 이 아래에 있으면 요청이 거기까지 도달하지 못합니다. 그래서 반드시 # BEGIN WordPress 위쪽에 써야 합니다.
# 2026-02-10 구 도메인 이전 (old.com → new.com)
RewriteEngine On
RewriteCond %{HTTP_HOST} ^old\.com [NC]
RewriteRule (.*) https://new.com/$1 [R=301,L]
# BEGIN WordPress
...
플러그인 리다이렉트와 겹칠 때
Redirection 같은 플러그인도 리다이렉트를 처리합니다. 다만 처리 시점이 다릅니다. .htaccess는 아파치가 PHP를 실행하기 전에 읽습니다. 플러그인은 워드프레스가 부팅된 다음에 동작합니다. 즉 .htaccess가 항상 먼저입니다. 두 곳에 같은 URL 규칙이 있으면 .htaccess 쪽이 이깁니다.
| 상황 | 권장 |
|---|---|
| 글 하나씩, 수시로 바뀜 | 플러그인 (비개발자도 관리 가능, 로그 확인 쉬움) |
| https 강제, www 통일 같은 전역 규칙 | .htaccess (PHP 실행 전 처리라 빠름) |
| 도메인 통째 이전 | .htaccess |
| 404 발생 URL을 보고 하나씩 대응 | 플러그인 |
멀티사이트 서브디렉터리 구조라면 .htaccess 규칙이 서브사이트 라우팅과 충돌하기 쉽습니다. 손대기 전에 백업하고, 규칙 하나 넣을 때마다 서브사이트를 눌러 확인하세요. 워드프레스 설정 파일 쪽을 같이 정리 중이라면 wp-config.php 필수 설정 12가지, 보안과 성능 한 번에 점검하기도 함께 보시면 됩니다.
안 먹히거나 사이트가 죽었을 때
규칙을 넣고 새로고침했는데 아무 변화가 없거나, 아예 사이트가 안 뜨는 상황. 여기서 대부분 막힙니다. 증상별로 원인이 꽤 명확하게 갈립니다.
500 에러가 뜰 때
500 Internal Server Error는 아파치가 .htaccess를 읽다가 이해 못 하는 줄을 만났다는 뜻입니다. 추측하지 말고 에러 로그를 보세요.
# 우분투 / 데비안
tail -f /var/log/apache2/error.log
# CentOS / 로키 / 알마
tail -f /var/log/httpd/error_log
tail -f를 켜둔 채로 브라우저에서 새로고침하면 그 순간의 에러가 흘러나옵니다. 자주 보게 되는 문구는 이렇습니다.
Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration
mod_rewrite가 로드되지 않았다는 뜻입니다. 앞에서 확인한 apache2ctl -M | grep rewrite로 돌아가세요.
.htaccess: RewriteRule: bad flag delimiters
플래그 대괄호가 잘못됐습니다. [R=301,L]에 공백이 들어갔거나([R=301, L]), 대괄호를 안 닫았거나, 목적지 URL과 대괄호 사이에 공백이 없는 경우입니다.
로그가 안 보이는 위치라면 호스팅 관리자 페이지의 에러 로그 메뉴를 확인하세요. 로그에서 특정 문구만 뽑는 방법은 grep으로 로그에서 원하는 줄만 뽑기, 실전 옵션 정리에 정리해 뒀습니다.
급하면 일단 되살리는 게 먼저입니다.
cp .htaccess.bak .htaccess
무한 리다이렉트에 걸렸을 때
ERR_TOO_MANY_REDIRECTS는 A로 보냈더니 다시 A로 오는 상황입니다. 원인은 대개 셋 중 하나입니다.
- 프록시 뒤 HTTPS 판정 문제 — Cloudflare나 로드밸런서를 쓰는데
%{HTTPS} off방식을 썼을 때.%{HTTP:X-Forwarded-Proto} !https로 바꾸세요. - https 규칙과 www 규칙이 서로 튕김 — https 규칙이 www 없는 주소로 보내고, www 규칙이 다시 http 쪽으로 되돌리는 경우. 두 규칙을 하나로 합치는 게 답입니다.
- Cloudflare SSL 모드가 Flexible — 이러면 Cloudflare가 원서버로 항상 http로 붙습니다. 대시보드에서 Full 또는 Full(strict)로 바꾸세요.
두 규칙을 하나로 합치면 이렇게 됩니다.
RewriteEngine On
RewriteCond %{HTTPS} off [OR]
RewriteCond %{HTTP_HOST} ^www\. [NC]
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]
http이거나 www가 붙어 있으면 한 번에 최종 주소로 보냅니다. 리다이렉트가 두 번 일어나지 않으니 체인도 줄어듭니다.
규칙이 통째로 무시될 때
500도 안 나고 리다이렉트도 안 됩니다. 그냥 원래 페이지가 뜹니다. 체크할 것은 넷입니다.
AllowOverride None— 호스팅사 문의 항목- 파일명이
htaccess.txt나.htaccess.txt로 저장됨 (윈도우 메모장이 잘 저지릅니다) - 상위 디렉터리의
.htaccess가 먼저 걸림 - 규칙을
# BEGIN WordPress블록 아래에 씀
파일명은 서버에서 직접 확인하는 게 확실합니다.
ls -la /var/www/html | grep -i htaccess
curl -I로 실제 응답 확인하기
브라우저는 301을 캐시로 들고 있어서 판단을 흐립니다. 실제 응답은 curl -I로 봅니다. -I는 헤더만 받아오는 옵션입니다.
curl -I https://example.com/old-page.html
HTTP/1.1 301 Moved Permanently
Date: Tue, 10 Feb 2026 05:31:22 GMT
Server: Apache/2.4.52 (Ubuntu)
Location: https://example.com/new-page.html
Content-Type: text/html; charset=iso-8859-1
첫 줄의 301 Moved Permanently와 Location 헤더를 확인하면 끝입니다. Location이 목적지와 다르면 규칙이 잘못된 거고, 200이 나오면 규칙이 안 걸린 겁니다.

-L 옵션을 더하면 리다이렉트를 끝까지 따라가면서 몇 단계를 거치는지 보여줍니다. 체인이 길면 여기서 드러납니다. 브라우저에서 확인하려면 반드시 시크릿 창을 쓰세요. 일반 창은 301 캐시 때문에 수정 결과가 안 보입니다.
마지막으로, .htaccess는 위에서 아래로 순서대로 읽습니다. 먼저 매칭된 규칙이 [L]을 만나면 거기서 끝납니다. 순서가 결과를 바꾼다는 걸 잊지 마세요.
굳혀둘 습관과 다른 선택지
원인 진단까지 끝냈다면, 이제 같은 사고를 반복하지 않는 쪽을 봅니다. 습관은 두 개면 충분합니다.
수정할 때마다 날짜를 붙여 백업합니다. 어제 파일과 오늘 파일을 비교할 수 있어야 원인을 찾습니다.
cp .htaccess .htaccess.$(date +%Y%m%d)
diff .htaccess.20260209 .htaccess
그리고 규칙마다 왜 넣었는지 주석으로 남깁니다. 1년 뒤에는 본인이 쓴 정규식도 못 알아봅니다.
# 2026-02-10 구 상품 상세 URL 구조 변경 (/goods → /products)
RedirectMatch 301 ^/goods/(.*)$ /products/$1
.htaccess와 서버 설정 파일 비교
.htaccess는 요청이 들어올 때마다 매번 읽힙니다. 해당 디렉터리와 그 위 모든 상위 디렉터리를 훑습니다. 규칙이 수십 개 넘어가고 트래픽이 많으면 이게 부담이 됩니다. 서버 설정 파일(VirtualHost)에 넣으면 아파치가 시작할 때 한 번만 읽어서 메모리에 올려두니 훨씬 빠릅니다.
| 항목 | .htaccess | VirtualHost 설정 | 워드프레스 플러그인 |
|---|---|---|---|
| 적용 시점 | 저장 즉시 | 아파치 재시작 필요 | 저장 즉시 |
| 성능 | 요청마다 파일 읽음 | 시작 시 1회 로드 | PHP 부팅 후 처리, 가장 느림 |
| 필요 권한 | FTP/파일 권한만 | root 권한 | 관리자 로그인 |
| 적용 범위 | 해당 디렉터리 이하 | 사이트 전체 | 워드프레스 요청만 |
| 공용 호스팅 | 가능 | 대부분 불가 | 가능 |
| 실수 시 여파 | 사이트 500 | 아파치 기동 실패 | 해당 규칙만 오작동 |
규칙이 20~30개를 넘어가기 시작하면 VirtualHost로 옮길 시점입니다. 문법은 거의 그대로 옮겨집니다. 다만 RewriteBase는 빼고, 경로 매칭 패턴 앞의 슬래시 유무가 달라지니 옮긴 뒤 apache2ctl configtest로 문법 검사를 꼭 하세요.
Nginx로 옮길 때의 대응 문법
서버를 Nginx로 바꾸면 .htaccess는 버려야 합니다. 대신 서버 블록에 직접 씁니다. 문법은 훨씬 짧습니다.
# http → https 강제
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# www 떼기
server {
listen 443 ssl;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
# 개별 페이지
location = /old-page.html {
return 301 /new-page.html;
}
$request_uri가 아파치의 %{REQUEST_URI}에 해당합니다. 수정 후에는 nginx -t로 문법을 검사하고 systemctl reload nginx로 반영합니다. 재시작이 아니라 reload면 접속이 끊기지 않습니다.
끝으로 리다이렉트 체인을 줄이세요. http://example.com → https://example.com → https://www.example.com처럼 세 단계를 거치면 사용자 체감 속도가 느려지고, 검색엔진도 체인이 길어지면 신호 전달이 약해집니다. 규칙을 합쳐서 한 번에 최종 주소로 보내는 게 항상 낫습니다.
자주 묻는 질문
301과 302 중 뭘 써야 하나요? 잘못 쓰면 되돌릴 수 있나요?
영구히 바뀐 주소면 301, 임시면 302입니다. 도메인 변경이나 https 전환은 301이 맞습니다. 문제는 되돌리기입니다. 301은 브라우저가 강하게 캐시하기 때문에 규칙을 지워도 이미 방문했던 사람은 계속 옛 목적지로 갑니다. 크롬은 주소창에 chrome://net-internals/#dns로 들어가 캐시를 지우거나, 아예 브라우저 캐시를 전부 삭제해야 풀립니다. 그래서 확정 전에는 302로 테스트하는 걸 권합니다.
.htaccess를 수정하면 아파치를 재시작해야 하나요?
아니요. .htaccess는 요청이 들어올 때마다 다시 읽히기 때문에 저장하는 순간 반영됩니다. 재시작이 필요한 건 httpd.conf나 VirtualHost 설정을 고쳤을 때입니다. 그런데도 변화가 안 보인다면 서버 문제가 아니라 캐시일 가능성이 큽니다. 시크릿 창으로 열어보거나 curl -I로 확인하세요. 워드프레스 캐시 플러그인이나 Cloudflare 캐시가 붙어 있으면 그쪽도 비워야 합니다.
https 강제 규칙을 넣었더니 무한 리다이렉트가 납니다. Cloudflare를 쓰고 있는데요
거의 확실히 %{HTTPS} off 방식 때문입니다. Cloudflare가 원서버로 http로 붙기 때문에 원서버는 계속 “이건 http네” 하고 판단해서 다시 리다이렉트를 던집니다. RewriteCond %{HTTP:X-Forwarded-Proto} !https로 바꾸세요. 그리고 Cloudflare 대시보드의 SSL/TLS 모드가 Flexible이면 Full 또는 Full(strict)로 변경하세요. 이 두 가지를 같이 손봐야 완전히 해결됩니다.
워드프레스에서 .htaccess가 자꾸 원래대로 돌아갑니다
# BEGIN WordPress와 # END WordPress 사이에 규칙을 넣었기 때문입니다. 워드프레스는 퍼머링크 설정을 저장하거나 일부 플러그인이 동작할 때 이 블록을 통째로 다시 씁니다. 그 안의 내용은 전부 날아갑니다. 내 규칙은 반드시 # BEGIN WordPress 위쪽, 파일 맨 앞에 쓰세요.
리다이렉트하면 검색 순위가 떨어지나요? 얼마나 유지해야 하나요?
301을 제대로 걸면 대부분의 평가가 새 URL로 넘어갑니다. 다만 색인이 완전히 이전되기까지 몇 주에서 몇 달이 걸리고, 그 사이 순위가 출렁일 수 있습니다. 리다이렉트 규칙은 최소 1년, 트래픽이 있던 페이지라면 그냥 영구히 두는 게 안전합니다. 외부 사이트의 링크와 북마크는 없어지지 않으니까요.
Nginx인데 .htaccess를 올려도 아무 반응이 없습니다
Nginx는 .htaccess라는 개념 자체가 없습니다. 파일을 읽지도 않고 에러도 내지 않습니다. 조용히 무시됩니다. Nginx 서버 블록에 return 301 문법으로 직접 써야 합니다. 위의 Nginx 예시를 참고하시고, 수정 후 nginx -t로 문법을 검사한 다음 systemctl reload nginx로 반영하세요. 서버 종류를 모르겠다면 curl -I로 Server 헤더를 먼저 확인하세요.
마무리 체크리스트
순서대로 따라왔다면 마지막으로 이것만 확인하면 됩니다.
- [ ]
.htaccess.bak또는 날짜 붙은 백업 파일이 남아 있는가 - [ ] 워드프레스라면 내 규칙이
# BEGIN WordPress위쪽에 있는가 - [ ]
curl -I 옛주소에서301 Moved Permanently와 올바른Location이 나오는가 - [ ]
http://,https://,www있음/없음 네 가지 조합을 전부 눌러봤는가 - [ ] 시크릿 창에서도 같은 결과가 나오는가 (301 캐시 배제)
- [ ]
tail -f /var/log/apache2/error.log에 새 경고가 안 찍히는가 - [ ]
curl -sIL로 확인했을 때 리다이렉트가 한 번에 끝나는가 (체인 없음) - [ ] 주석으로 날짜와 이유를 남겼는가
여기까지 통과했으면 정상입니다. 규칙이 늘어날 조짐이 보이면 지금 기준을 정해두세요. “20개를 넘으면 VirtualHost로 옮긴다” 정도면 충분합니다. 나중에 100개짜리 .htaccess를 앞에 두고 어디서부터 손대야 할지 막막해지는 것보다 낫습니다.
