DevToolBox무료
블로그

.htaccess 리다이렉트 치트시트: 복사-붙여넣기 예제

10분 읽기by DevToolBox

.htaccess 파일은 Apache 웹 서버의 가장 강력한 구성 도구 중 하나입니다. 메인 서버 설정을 수정하지 않고도 리디렉션, HTTPS 강제, 보안 헤더 설정, 캐싱 관리 등을 제어할 수 있습니다. 이 포괄적인 .htaccess 리디렉트 치트 시트는 모든 일반적인 시나리오에 대한 프로덕션 수준의 복사-붙여넣기 가능한 예제를 제공합니다.

기본 리디렉트

리디렉트는 .htaccess 파일의 가장 일반적인 용도입니다. 단일 페이지 이동, 전체 디렉토리 재구성, 새 도메인으로 마이그레이션 등 Apache의 RedirectRewriteRule 지시어로 간단하게 구현할 수 있습니다. SEO 링크 가치를 새 URL로 전달하기 위해 항상 301(영구) 리디렉트를 사용하세요.

단일 URL 리디렉트

# Redirect a single URL (301 permanent)
Redirect 301 /old-page.html https://example.com/new-page.html

# Using RewriteRule for more control
RewriteEngine On
RewriteRule ^old-page\.html$ /new-page.html [R=301,L]

# Redirect with pattern matching (e.g., old product URLs)
RewriteRule ^products/([0-9]+)\.html$ /shop/item/$1 [R=301,L]

전체 디렉토리 리디렉트

# Redirect entire directory to new location
RedirectMatch 301 ^/blog/(.*)$ https://example.com/articles/$1

# Using RewriteRule (preserves subdirectory structure)
RewriteEngine On
RewriteRule ^blog/(.*)$ /articles/$1 [R=301,L]

# Redirect directory but keep filenames
RewriteRule ^old-folder/(.+)$ /new-folder/$1 [R=301,L]

도메인 간 리디렉트

# Redirect entire old domain to new domain
RewriteEngine On
RewriteCond %{HTTP_HOST} ^(www\.)?olddomain\.com$ [NC]
RewriteRule ^(.*)$ https://newdomain.com/$1 [R=301,L]

# Redirect specific domain alias to primary domain
RewriteEngine On
RewriteCond %{HTTP_HOST} ^olddomain\.net$ [NC,OR]
RewriteCond %{HTTP_HOST} ^olddomain\.org$ [NC]
RewriteRule ^(.*)$ https://newdomain.com/$1 [R=301,L]

HTTPS 강제 적용

HTTPS 강제는 보안과 SEO에 필수적입니다. Google은 HTTPS를 순위 신호로 사용하며, 최신 브라우저는 안전하지 않은 HTTP 연결에 대해 경고합니다. 이 규칙들은 mod_rewrite를 사용하여 모든 HTTP 트래픽을 HTTPS로 리디렉트합니다.

HTTP에서 HTTPS로 (www 없이)

# Force HTTPS (redirect HTTP to HTTPS, non-www)
RewriteEngine On

# Redirect www to non-www
RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]

# Redirect HTTP to HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://example.com/$1 [R=301,L]

HTTP에서 HTTPS로 (www 포함)

# Force HTTPS with www prefix
RewriteEngine On

# Redirect non-www to www
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]

# Redirect HTTP to HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]

비-www + HTTPS 강제

# Force non-www + HTTPS in a single pass
# Works on shared hosting and most Apache setups
RewriteEngine On

# Handle both www removal and HTTPS enforcement together
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]

RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

# Alternative: Using environment variables (some hosts)
# RewriteCond %{ENV:HTTPS} !on
# RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

후행 슬래시 처리

일관성 없는 후행 슬래시는 SEO에 해로운 중복 콘텐츠 문제를 만듭니다. 검색 엔진은 /about/about/을 다른 URL로 취급합니다. 하나의 스타일을 선택하고 사이트 전체에서 일관되게 적용하세요.

후행 슬래시 추가

# Add trailing slash to all URLs (except files with extensions)
RewriteEngine On

# Only apply to URLs without a file extension
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !\.[a-zA-Z0-9]{1,5}$
RewriteRule ^(.+[^/])$ %{REQUEST_URI}/ [R=301,L]

# Simpler version (may cause issues with some file types)
# RewriteRule ^(.*[^/])$ $1/ [R=301,L]

후행 슬래시 제거

# Remove trailing slash from all URLs (except directories)
RewriteEngine On

# Do not remove trailing slash from actual directories
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [R=301,L]

# Remove trailing slash except for root URL
# RewriteCond %{REQUEST_URI} !^/$
# RewriteRule ^(.*)/$ /$1 [R=301,L]

쿼리 문자열 처리

쿼리 문자열이 있는 URL 리디렉트에는 특별한 주의가 필요합니다. 기본적으로 Apache는 원래 쿼리 문자열을 리디렉트 대상에 추가합니다. QSA(쿼리 문자열 추가)와 ?(폐기) 플래그를 사용하여 쿼리 매개변수를 유지, 폐기 또는 수정할 수 있습니다.

쿼리 문자열 유지 리디렉트

# Redirect preserving the original query string (default behavior)
# /search?q=test -> /find?q=test
RewriteEngine On
RewriteRule ^search$ /find [R=301,L]

# Redirect and APPEND additional query parameters (QSA flag)
# /page?id=5 -> /new-page?id=5&ref=old
RewriteRule ^page$ /new-page?ref=old [R=301,L,QSA]

# Match specific query string and redirect
RewriteCond %{QUERY_STRING} ^id=([0-9]+)$
RewriteRule ^product\.php$ /products/%1? [R=301,L]

쿼리 문자열 폐기 리디렉트

# Redirect and DISCARD all query parameters
# /old-page?any=params -> /new-page (clean URL)
RewriteEngine On
RewriteRule ^old-page$ /new-page? [R=301,L]
# The trailing ? strips the query string

# Discard specific query parameters only
# /page?utm_source=x&id=5 -> /page?id=5 (strip tracking params)
RewriteCond %{QUERY_STRING} (^|&)utm_[^&]*
RewriteRule ^(.*)$ /$1? [R=301,L]

쿼리 매개변수 수정 리디렉트

# Rewrite query parameter to path segment
# /index.php?page=about -> /about
RewriteEngine On
RewriteCond %{QUERY_STRING} ^page=(.+)$
RewriteRule ^index\.php$ /%1? [R=301,L]

# Rewrite path segment to query parameter
# /category/electronics -> /shop.php?cat=electronics
RewriteRule ^category/([a-zA-Z0-9-]+)$ /shop.php?cat=$1 [L]

# Rename a query parameter
# /search?q=test -> /search?query=test
RewriteCond %{QUERY_STRING} ^q=(.+)$
RewriteRule ^search$ /search?query=%1 [R=301,L]

커스텀 에러 페이지

커스텀 에러 페이지는 일반적인 서버 오류 대신 유용한 정보를 제공하여 사용자 경험을 향상시킵니다. ErrorDocument 지시어로 모든 HTTP 상태 코드에 대한 커스텀 페이지를 정의할 수 있습니다.

# Custom error pages
# Place error page files in your document root

# 404 Not Found - page does not exist
ErrorDocument 404 /errors/404.html

# 403 Forbidden - access denied
ErrorDocument 403 /errors/403.html

# 500 Internal Server Error
ErrorDocument 500 /errors/500.html

# 401 Unauthorized - authentication required
ErrorDocument 401 /errors/401.html

# 503 Service Unavailable - maintenance mode
ErrorDocument 503 /errors/maintenance.html

# You can also use inline messages (not recommended for production)
# ErrorDocument 404 "Page not found. Please check the URL."

# Or redirect to an external URL
# ErrorDocument 404 https://example.com/not-found

# ── Maintenance mode (redirect all traffic to maintenance page) ──
# Uncomment during maintenance, recomment when done
# RewriteEngine On
# RewriteCond %{REMOTE_ADDR} !^123\.456\.789\.000$  # Allow your IP
# RewriteCond %{REQUEST_URI} !/errors/maintenance.html$ [NC]
# RewriteCond %{REQUEST_URI} !\.(css|js|png|jpg|gif|ico)$ [NC]
# RewriteRule ^(.*)$ /errors/maintenance.html [R=503,L]

.htaccess를 통한 보안 헤더

HTTP 보안 헤더는 클릭재킹, 크로스 사이트 스크립팅(XSS), 콘텐츠 인젝션과 같은 일반적인 공격으로부터 사이트를 보호합니다. mod_headers가 활성화되어야 합니다.

# Security headers via .htaccess
# Requires mod_headers to be enabled: a2enmod headers

<IfModule mod_headers.c>

  # X-Frame-Options: Prevent clickjacking by blocking iframes
  # Options: DENY | SAMEORIGIN | ALLOW-FROM uri
  Header always set X-Frame-Options "SAMEORIGIN"

  # X-Content-Type-Options: Prevent MIME-type sniffing
  Header always set X-Content-Type-Options "nosniff"

  # X-XSS-Protection: Enable browser XSS filter (legacy)
  Header always set X-XSS-Protection "1; mode=block"

  # Referrer-Policy: Control referrer information
  Header always set Referrer-Policy "strict-origin-when-cross-origin"

  # Content-Security-Policy: Control resource loading
  # Customize the sources based on your site's needs
  Header always set Content-Security-Policy "default-src 'self'; \
    script-src 'self' 'unsafe-inline' https://cdn.example.com; \
    style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; \
    img-src 'self' data: https:; \
    font-src 'self' https://fonts.gstatic.com; \
    connect-src 'self' https://api.example.com; \
    frame-ancestors 'self';"

  # Strict-Transport-Security (HSTS): Force HTTPS for 2 years
  # Only add this if your site fully supports HTTPS
  Header always set Strict-Transport-Security \
    "max-age=63072000; includeSubDomains; preload"

  # Permissions-Policy: Disable unused browser features
  Header always set Permissions-Policy \
    "camera=(), microphone=(), geolocation=(), interest-cohort=()"

  # Remove X-Powered-By header (hides PHP/server version)
  Header unset X-Powered-By
  Header always unset X-Powered-By

</IfModule>

# Hide Apache version in server headers
ServerSignature Off

브라우저 캐싱 규칙

브라우저 캐싱은 브라우저에 정적 자산을 로컬에 저장하도록 지시하여 재방문자의 페이지 로드 시간을 크게 개선합니다. mod_expires 모듈이 Cache-ControlExpires 헤더를 자동으로 설정합니다.

# Browser caching with mod_expires
# Enable the module: a2enmod expires

<IfModule mod_expires.c>
  ExpiresActive On

  # Default expiration: 1 month
  ExpiresDefault "access plus 1 month"

  # HTML files: short cache (content changes frequently)
  ExpiresByType text/html "access plus 1 hour"

  # CSS and JavaScript: long cache (use fingerprinted filenames)
  ExpiresByType text/css "access plus 1 year"
  ExpiresByType application/javascript "access plus 1 year"
  ExpiresByType text/javascript "access plus 1 year"

  # Images: cache for 1 month
  ExpiresByType image/jpeg "access plus 1 month"
  ExpiresByType image/png "access plus 1 month"
  ExpiresByType image/gif "access plus 1 month"
  ExpiresByType image/webp "access plus 1 month"
  ExpiresByType image/avif "access plus 1 month"
  ExpiresByType image/svg+xml "access plus 1 month"
  ExpiresByType image/x-icon "access plus 1 year"

  # Fonts: cache for 1 year
  ExpiresByType font/woff2 "access plus 1 year"
  ExpiresByType font/woff "access plus 1 year"
  ExpiresByType font/ttf "access plus 1 year"
  ExpiresByType application/font-woff2 "access plus 1 year"
  ExpiresByType application/font-woff "access plus 1 year"

  # JSON/XML data: short cache
  ExpiresByType application/json "access plus 1 hour"
  ExpiresByType application/xml "access plus 1 hour"

  # PDF and documents
  ExpiresByType application/pdf "access plus 1 month"
</IfModule>

# Alternative: Cache-Control headers with mod_headers
<IfModule mod_headers.c>
  # Immutable cache for fingerprinted assets
  <FilesMatch "\.(js|css)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
  </FilesMatch>

  # Short cache for HTML
  <FilesMatch "\.html$">
    Header set Cache-Control "public, max-age=3600, must-revalidate"
  </FilesMatch>

  # No cache for dynamic content
  <FilesMatch "\.(php|cgi)$">
    Header set Cache-Control "no-store, no-cache, must-revalidate"
  </FilesMatch>
</IfModule>

Gzip / Brotli 압축

압축은 전송 응답 크기를 60-90% 줄여 페이지 로드 시간을 크게 개선합니다. Apache는 Gzip(mod_deflate)과 Brotli(mod_brotli, Apache 2.4.26+)를 모두 지원합니다.

Gzip 압축 (mod_deflate)

# Gzip compression using mod_deflate
# Enable the module: a2enmod deflate

<IfModule mod_deflate.c>
  # Compress text-based content types
  AddOutputFilterByType DEFLATE text/plain
  AddOutputFilterByType DEFLATE text/html
  AddOutputFilterByType DEFLATE text/xml
  AddOutputFilterByType DEFLATE text/css
  AddOutputFilterByType DEFLATE text/javascript
  AddOutputFilterByType DEFLATE application/xml
  AddOutputFilterByType DEFLATE application/xhtml+xml
  AddOutputFilterByType DEFLATE application/rss+xml
  AddOutputFilterByType DEFLATE application/atom+xml
  AddOutputFilterByType DEFLATE application/javascript
  AddOutputFilterByType DEFLATE application/x-javascript
  AddOutputFilterByType DEFLATE application/json
  AddOutputFilterByType DEFLATE application/ld+json
  AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
  AddOutputFilterByType DEFLATE font/opentype
  AddOutputFilterByType DEFLATE font/ttf
  AddOutputFilterByType DEFLATE font/woff
  AddOutputFilterByType DEFLATE font/woff2
  AddOutputFilterByType DEFLATE image/svg+xml
  AddOutputFilterByType DEFLATE image/x-icon

  # Do not compress images (already compressed)
  SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|webp|avif)$ no-gzip

  # Handle browser quirks
  BrowserMatch ^Mozilla/4 gzip-only-text/html
  BrowserMatch ^Mozilla/4\.0[678] no-gzip
  BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

  # Add Vary header for proper caching
  Header append Vary Accept-Encoding
</IfModule>

Brotli 압축 (mod_brotli)

# Brotli compression using mod_brotli (Apache 2.4.26+)
# Enable the module: a2enmod brotli

<IfModule mod_brotli.c>
  # Compress text-based content types with Brotli
  AddOutputFilterByType BROTLI_COMPRESS text/plain
  AddOutputFilterByType BROTLI_COMPRESS text/html
  AddOutputFilterByType BROTLI_COMPRESS text/xml
  AddOutputFilterByType BROTLI_COMPRESS text/css
  AddOutputFilterByType BROTLI_COMPRESS text/javascript
  AddOutputFilterByType BROTLI_COMPRESS application/xml
  AddOutputFilterByType BROTLI_COMPRESS application/xhtml+xml
  AddOutputFilterByType BROTLI_COMPRESS application/javascript
  AddOutputFilterByType BROTLI_COMPRESS application/json
  AddOutputFilterByType BROTLI_COMPRESS application/ld+json
  AddOutputFilterByType BROTLI_COMPRESS font/opentype
  AddOutputFilterByType BROTLI_COMPRESS font/ttf
  AddOutputFilterByType BROTLI_COMPRESS font/woff
  AddOutputFilterByType BROTLI_COMPRESS font/woff2
  AddOutputFilterByType BROTLI_COMPRESS image/svg+xml

  # Brotli compression quality (0-11, default: 11)
  # Lower = faster compression, larger files
  # Higher = slower compression, smaller files
  BrotliCompressionQuality 6

  # Brotli window size (10-24, default: 22)
  BrotliCompressionWindow 22
</IfModule>

# Fallback: Use Gzip if Brotli is not available
# Both modules can coexist; Apache serves Brotli to
# browsers that support it and Gzip to others.

흔한 실수와 해결 방법

.htaccess 문제 디버깅은 어려울 수 있습니다. 개발자들이 가장 자주 하는 실수와 그 해결 방법을 소개합니다.

무한 리디렉트 루프

가장 흔한 실수는 재작성된 URL이 같은 규칙에 다시 매칭되는 리디렉트 루프를 만드는 것입니다. 규칙 적용 전에 RewriteCond로 현재 상태를 확인하고, 매칭 후 [L] 플래그로 처리를 중지하세요.

# BAD: Creates an infinite loop
# (The rewritten URL "/new" matches "^(.*)$" again)
RewriteRule ^(.*)$ /new/$1 [R=301]

# GOOD: Use RewriteCond to prevent the loop
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/new/ [NC]
RewriteRule ^(.*)$ /new/$1 [R=301,L]

# GOOD: Another approach - check if already redirected
RewriteEngine On
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^(.*)$ /new/$1 [R=301,L]

잘못된 RewriteBase

RewriteBase는 디렉토리별 재작성의 기본 URL 경로를 정의합니다. .htaccess가 하위 디렉토리에 있으면 RewriteBase는 해당 경로와 일치해야 합니다.

# If .htaccess is in the document root:
RewriteEngine On
RewriteBase /

# If .htaccess is in /blog/ subdirectory:
RewriteEngine On
RewriteBase /blog/

# If .htaccess is in /app/public/ subdirectory:
RewriteEngine On
RewriteBase /app/public/

# Common mistake: Using RewriteBase /blog when file is at root
# This causes all rewritten URLs to be prefixed with /blog

규칙 순서가 중요합니다

Apache는 .htaccess 규칙을 위에서 아래로 처리합니다. 일반적인 규칙이 특정 규칙 앞에 있으면 먼저 매칭되어 특정 규칙은 실행되지 않습니다. 특정 규칙을 일반 규칙 앞에 배치하고 [L] 플래그를 사용하세요.

# BAD: General rule before specific rule
RewriteEngine On
RewriteRule ^(.*)$ /index.php?page=$1 [L]        # Catches everything!
RewriteRule ^about$ /about-us.html [R=301,L]      # Never reached!

# GOOD: Specific rules first, general rules last
RewriteEngine On
RewriteRule ^about$ /about-us.html [R=301,L]      # Specific: runs first
RewriteRule ^contact$ /contact-us.html [R=301,L]  # Specific: runs second
RewriteCond %{REQUEST_FILENAME} !-f                # Skip existing files
RewriteCond %{REQUEST_FILENAME} !-d                # Skip existing dirs
RewriteRule ^(.*)$ /index.php?page=$1 [L]          # General: fallback
실수해결 방법
RewriteEngine On 누락재작성 규칙 블록 상단에 항상 "RewriteEngine On" 추가
패턴에서 점을 이스케이프하지 않음정규식 패턴에서 . 대신 \. 사용 (점은 모든 문자와 매칭)
[L] 플래그 누락매칭 후 규칙 처리를 중지하기 위해 [L] 추가
Redirect와 RewriteRule 혼용하나의 방법만 사용; 혼용은 예측할 수 없는 동작 유발
브라우저 캐시 삭제 없이 테스트301 리디렉트는 브라우저에 캐시됨; 테스트 시 302 사용, 프로덕션에서 301로 변경
.htaccess 파일 권한 오류권한을 644로 설정

자주 묻는 질문

.htaccess란 무엇이며 어디에 배치해야 하나요?

.htaccess는 Apache 웹 서버의 분산 설정 파일입니다. 웹사이트 루트 디렉토리(보통 public_html 또는 www)에 배치합니다. 해당 디렉토리와 모든 하위 디렉토리에 영향을 미칩니다. 파일명은 앞의 점을 포함하여 정확히 ".htaccess"여야 합니다.

301과 302 리디렉트의 차이점은?

301 리디렉트는 영구적으로, 검색 엔진에 링크 가치를 새 URL로 이전하도록 지시합니다. 302 리디렉트는 임시적으로, 검색 엔진이 원래 URL을 인덱스에 유지합니다. 영구적 이동에는 301, 임시 상황에는 302를 사용하세요. 브라우저는 301을 적극적으로 캐시하므로 테스트 시에는 302를 사용하세요.

.htaccess 규칙이 작동하지 않는 이유는?

가장 흔한 원인: 1) mod_rewrite 미활성화; 2) AllowOverride가 None으로 설정; 3) 파일 권한 오류(644여야 함); 4) 구문 오류(Apache 에러 로그 확인); 5) 브라우저가 이전 301 리디렉트를 캐시.

.htaccess는 Nginx에서 사용할 수 있나요?

아니요, .htaccess는 Apache 전용입니다. Nginx를 사용하는 경우 동등한 지시어를 Nginx 서버 블록 설정에 직접 추가해야 합니다. 온라인 도구로 .htaccess 규칙을 Nginx 설정으로 변환할 수 있지만, 완전한 1:1 변환은 아닙니다.

사이트 리디자인 후 이전 URL을 리디렉트하는 방법은?

이전 URL에서 새 URL로의 매핑을 만들고 각각에 Redirect 또는 RewriteRule을 추가합니다. 패턴 기반 변경에는 정규식 캡처 그룹 사용: RewriteRule ^blog/(.*)$ /articles/$1 [R=301,L]. 항상 curl -I로 리디렉트를 테스트하세요.

.htaccess는 사이트 성능에 어떤 영향을 미치나요?

Apache는 모든 요청마다 .htaccess 파일을 읽어 약간의 성능 오버헤드가 있습니다. 고트래픽 사이트에서는 규칙을 Apache 메인 설정으로 이동하는 것이 좋습니다. 하지만 대부분의 웹사이트에서 성능 영향은 무시할 수 있을 정도입니다.

이 .htaccess 치트 시트는 가장 필수적인 리디렉트 패턴, 보안 구성 및 성능 최적화를 다룹니다. 프로덕션에 배포하기 전에 curl 또는 온라인 리디렉트 체커로 변경 사항을 철저히 테스트하고, 작동하는 .htaccess 파일의 백업을 유지하세요.

𝕏 Twitterin LinkedIn
도움이 되었나요?

최신 소식 받기

주간 개발 팁과 새 도구 알림을 받으세요.

스팸 없음. 언제든 구독 해지 가능.

Try These Related Tools

.ht.htaccess GeneratorNXNginx Config Generator%20URL Encoder/Decoder🤖Robots.txt Generator

Related Articles

Nginx 설정 예시: 리버스 프록시, SSL, 정적 사이트

프로덕션용 Nginx 설정 예시: 리버스 프록시, SSL/TLS, 정적 파일, 로드 밸런싱.

Content Security Policy (CSP) 완전 가이드: 기초부터 프로덕션까지

CSP를 처음부터 배우기: 모든 디렉티브, 일반적인 설정, 리포팅, 배포.