.htaccess 활용
.htaccess(Hypertext Access)는 디렉터리별로 Apache 설정을 재정의할 수 있는 분산 설정 파일입니다. 메인 설정 파일을 수정할 권한이 없는 공유 호스팅 환경에서 특히 유용합니다. URL 리다이렉트, 접근 제어, 캐시 헤더, HTTPS 강제 설정 등 다양한 기능을 구현할 수 있습니다.
.htaccess 동작 원리
Apache가 HTTP 요청을 처리할 때 요청된 파일까지의 경로에 있는 모든 디렉터리를 거슬러 올라가며 .htaccess 파일을 읽습니다.
요청: GET /blog/2024/post.html
Apache가 읽는 .htaccess 순서:
1. /.htaccess
2. /blog/.htaccess
3. /blog/2024/.htaccess ← 가장 구체적인 설정이 우선 적용
성능 주의:
.htaccess는 요청마다 파일을 읽어 처리하므로 많은 I/O를 발생시킵니다. 가능하면httpd.conf에 직접 설정하고AllowOverride None으로.htaccess를 비활성화하는 것이 성능에 유리합니다.
활성화 조건
.htaccess가 동작하려면 해당 디렉터리에 AllowOverride가 설정되어 있어야 합니다.
# httpd.conf 또는 VirtualHost 설정에서
<Directory "/var/www/html">
AllowOverride All # .htaccess 모든 지시어 허용
</Directory>
URL 리다이렉트 (mod_rewrite)
mod_rewrite는 Apache 가장 강력한 모듈 중 하나로, URL을 패턴 기반으로 변환·리다이렉트합니다.
기본 mod_rewrite 활성화
RewriteEngine On
HTTP → HTTPS 강제 리다이렉트
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
www → non-www 리다이렉트
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
non-www → www 리다이렉트
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
SPA(React/Vue) 클라이언트 라우팅
RewriteEngine On
# 실제 파일이나 디렉터리가 있으면 그대로 제공
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# 없으면 index.html로 폴백 (React Router 처리)
RewriteRule ^ /index.html [L]
또는 Apache 2.4.16+에서는 더 간단하게:
FallbackResource /index.html
URL 정리 (Pretty URL)
RewriteEngine On
# example.com/blog/123 → example.com/blog.php?id=123
RewriteRule ^blog/([0-9]+)/?$ blog.php?id=$1 [L,QSA]
# example.com/user/홍길동 → example.com/user.php?name=홍길동
RewriteRule ^user/(.+)/?$ user.php?name=$1 [L,QSA]
RewriteRule 플래그 정리
| 플래그 | 설명 |
|---|---|
R=301 | 301 영구 리다이렉트 |
R=302 | 302 임시 리다이렉트 |
L | 마지막 규칙 (이 규칙 매칭 후 더 이상 처리 안 함) |
QSA | 기존 쿼리 스트링 보존 |
NC | 대소문자 무시 |
NE | 특수문자 이스케이프 안 함 |
PT | Pass Through — 다음 핸들러로 넘김 |
F | 403 Forbidden 반환 |
G | 410 Gone 반환 |
RewriteCond 변수 목록
# 자주 사용하는 RewriteCond 변수
%{HTTPS} # on / off
%{HTTP_HOST} # 요청 호스트 헤더
%{REQUEST_URI} # 요청 URI (/path?query)
%{QUERY_STRING} # 쿼리 스트링
%{REMOTE_ADDR} # 클라이언트 IP
%{REQUEST_METHOD} # GET, POST 등
%{HTTP_USER_AGENT} # User-Agent
%{REQUEST_FILENAME} # 실제 파일 경로
%{SERVER_PORT} # 서버 포트
접근 제어
# 특정 IP 차단
<RequireAll>
Require all granted
Require not ip 203.0.113.5
Require not ip 198.51.100.0/24
</RequireAll>
# 특정 IP만 허용
Require ip 127.0.0.1
Require ip 192.168.1.0/24
# 특정 User-Agent 차단
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (BadBot|Scraper|Harvester) [NC]
RewriteRule .* - [F,L]
캐시 헤더 설정 (mod_expires, mod_headers)
# mod_expires 활성화 필요 (Ubuntu: sudo a2enmod expires)
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 1 week"
ExpiresByType text/html "access plus 0 seconds"
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/webp "access plus 1 month"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType application/pdf "access plus 1 month"
</IfModule>
# mod_headers로 Cache-Control 설정
<IfModule mod_headers.c>
<FilesMatch "\.(js|css)$">
Header set Cache-Control "max-age=31536000, public, immutable"
</FilesMatch>
<FilesMatch "\.(html|htm)$">
Header set Cache-Control "no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
</FilesMatch>
</IfModule>
Gzip 압축 (.htaccess)
<IfModule mod_deflate.c>
# 텍스트 기반 파일 압축
AddOutputFilterByType DEFLATE text/html text/plain text/css
AddOutputFilterByType DEFLATE application/javascript application/json
AddOutputFilterByType DEFLATE image/svg+xml font/woff font/woff2
# 이미 압축된 파일 제외
SetEnvIfNoCase Request_URI \
\.(?:gif|jpe?g|png|webp|zip|gz|bz2)$ no-gzip dont-vary
# 구형 브라우저 호환
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
</IfModule>
보안 헤더 설정
<IfModule mod_headers.c>
# 클릭재킹 방지
Header always set X-Frame-Options "SAMEORIGIN"
# MIME 타입 스니핑 방지
Header always set X-Content-Type-Options "nosniff"
# XSS 필터 활성화
Header always set X-XSS-Protection "1; mode=block"
# HSTS (HTTPS 강제 1년)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
# Referrer 정책
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# 서버 정보 숨기기
Header unset Server
Header unset X-Powered-By
</IfModule>
에러 페이지 커스터마이징
# 커스텀 에러 페이지
ErrorDocument 400 /errors/400.html
ErrorDocument 401 /errors/401.html
ErrorDocument 403 /errors/403.html
ErrorDocument 404 /errors/404.html
ErrorDocument 500 /errors/500.html
ErrorDocument 503 /errors/503.html
디렉터리 인덱스 설정
# 기본 인덱스 파일 지정
DirectoryIndex index.html index.php index.htm
# 디렉터리 목록 표시 비활성화 (보안)
Options -Indexes
.htaccess vs httpd.conf — 선택 기준
| 상황 | 권장 방법 |
|---|---|
| 공유 호스팅 (루트 접근 불가) | .htaccess |
| 전용 서버 (httpd.conf 직접 수정 가능) | httpd.conf (성능 우선) |
| CMS(WordPress, Drupal) | .htaccess (프레임워크에서 자동 생성) |
| 개발·테스트 환경 | .htaccess (빠른 변경 적용) |
| 운영 환경 고성능 | httpd.conf + AllowOverride None |
정리
.htaccess: 디렉터리별 분산 설정, 공유 호스팅·CMS에 적합mod_rewrite: HTTPS 강제, SPA 라우팅, URL 정리 등 URL 변환 핵심mod_expires+mod_headers: 캐시·보안 헤더 설정- 운영 환경에서는 가능하면
httpd.conf에 통합 +AllowOverride None으로 성능 최적화