El archivo .htaccess es una de las herramientas de configuración más potentes para servidores web Apache. Te permite controlar redirecciones, forzar HTTPS, establecer encabezados de seguridad, gestionar el caché y mucho más, sin tocar la configuración principal del servidor. Esta guía completa de redirecciones .htaccess proporciona ejemplos listos para producción que puedes copiar y pegar directamente.
Redirecciones básicas
Las redirecciones son el uso más común de los archivos .htaccess. Ya sea moviendo una sola página, reestructurando un directorio completo o migrando a un nuevo dominio, las directivas Redirect y RewriteRule de Apache lo hacen sencillo. Usa siempre redirecciones 301 (permanentes) para SEO y transferir el valor de los enlaces a la nueva URL.
Redirigir una URL única
# 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]Redirigir un directorio completo
# 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]Redirección de dominio a dominio
# 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]Forzar HTTPS
Forzar HTTPS es esencial para la seguridad y el SEO. Google usa HTTPS como señal de ranking, y los navegadores modernos advierten sobre conexiones HTTP inseguras. Estas reglas usan mod_rewrite para redirigir todo el tráfico HTTP a HTTPS.
HTTP a HTTPS (sin 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 a HTTPS (con 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]Forzar sin www con 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]Manejo de barra final
Las barras finales inconsistentes crean problemas de contenido duplicado que perjudican el SEO. Los motores de búsqueda tratan /about y /about/ como URLs diferentes. Elige un estilo y aplícalo consistentemente en todo tu sitio.
Agregar barra final
# 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]Eliminar barra final
# 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]Manejo de cadenas de consulta
Redirigir URLs con cadenas de consulta requiere atención especial. Por defecto, Apache añade la cadena de consulta original al destino. Usa los flags QSA y ? para conservar, descartar o modificar parámetros.
Redirigir conservando la cadena de consulta
# 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]Redirigir descartando la cadena de consulta
# 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]Redirigir modificando los parámetros
# 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]Páginas de error personalizadas
Las páginas de error personalizadas mejoran la experiencia del usuario proporcionando información útil. La directiva ErrorDocument permite definir páginas personalizadas para cualquier código de estado 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]Encabezados de seguridad vía .htaccess
Los encabezados de seguridad HTTP protegen tu sitio contra ataques comunes como clickjacking, XSS e inyección de contenido. Añadirlos vía .htaccess requiere mod_headers habilitado.
# 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 OffReglas de caché del navegador
El caché del navegador mejora dramáticamente los tiempos de carga para visitantes recurrentes. El módulo mod_expires establece automáticamente los encabezados Cache-Control y Expires.
# 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>Compresión Gzip / Brotli
La compresión reduce el tamaño de las respuestas entre un 60-90%, mejorando significativamente los tiempos de carga. Apache soporta Gzip (vía mod_deflate) y Brotli (vía mod_brotli, Apache 2.4.26+).
Compresión 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>Compresión 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.Errores comunes y cómo corregirlos
Depurar problemas de .htaccess puede ser frustrante. Aquí están los errores más comunes y cómo evitarlos.
Bucles de redirección infinitos
El error más común es crear un bucle donde la URL reescrita coincide nuevamente con la misma regla. Usa siempre RewriteCond para verificar el estado actual y el flag [L] para detener el procesamiento.
# 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 incorrecto
RewriteBase define la ruta URL base para reescrituras por directorio. Si tu .htaccess está en un subdirectorio, el RewriteBase debe coincidir con esa ruta.
# 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 /blogEl orden de las reglas importa
Apache procesa las reglas de arriba a abajo. Si una regla general aparece antes que una específica, coincidirá primero. Coloca siempre las reglas específicas antes que las generales y usa el flag [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| Error | Corrección |
|---|---|
| Falta RewriteEngine On | Agrega siempre "RewriteEngine On" al inicio del bloque de reglas |
| Puntos no escapados en patrones | Usa \. en lugar de . en patrones regex |
| Olvidar el flag [L] | Agrega [L] para detener el procesamiento después de una coincidencia |
| Mezclar Redirect y RewriteRule | Usa un solo método; mezclarlos causa comportamiento impredecible |
| Probar sin limpiar la caché del navegador | Las redirecciones 301 se cachean; usa 302 durante las pruebas |
| Permisos incorrectos en .htaccess | Establece los permisos a 644 |
Preguntas frecuentes
¿Qué es .htaccess y dónde debo colocarlo?
.htaccess es un archivo de configuración distribuido para servidores web Apache. Colócalo en el directorio raíz de tu sitio (generalmente public_html o www). Afecta al directorio y todos los subdirectorios. El archivo debe llamarse exactamente ".htaccess" con el punto inicial.
¿Cuál es la diferencia entre las redirecciones 301 y 302?
Una redirección 301 es permanente: indica a los motores de búsqueda que transfieran el valor de los enlaces a la nueva URL. Una 302 es temporal: los motores mantienen la URL original en su índice. Usa 301 para movimientos permanentes y 302 para situaciones temporales. Los navegadores cachean agresivamente las 301, así que usa 302 durante las pruebas.
¿Por qué no funcionan mis reglas .htaccess?
Causas más comunes: 1) mod_rewrite no habilitado; 2) AllowOverride establecido en None; 3) Permisos de archivo incorrectos (deben ser 644); 4) Errores de sintaxis (revisa el log de errores de Apache); 5) El navegador cacheó una redirección 301 anterior.
¿Funciona .htaccess con Nginx?
No, .htaccess es específico de Apache. Nginx no soporta archivos .htaccess. Con Nginx, debes agregar las directivas equivalentes directamente en la configuración del bloque server. Existen herramientas en línea para convertir reglas .htaccess a sintaxis Nginx.
¿Cómo redirijo URLs antiguas después de un rediseño?
Crea un mapeo de URLs antiguas a nuevas y agrega un Redirect o RewriteRule para cada una. Para cambios basados en patrones, usa grupos de captura regex: RewriteRule ^blog/(.*)$ /articles/$1 [R=301,L]. Siempre prueba con curl -I.
¿Cómo afecta .htaccess al rendimiento del sitio?
Apache lee los archivos .htaccess en cada solicitud, lo que añade un pequeño overhead de rendimiento. Para sitios de alto tráfico, es mejor mover las reglas a la configuración principal de Apache. Para la mayoría de los sitios, el impacto es insignificante.
Esta guía .htaccess cubre los patrones de redirección esenciales, configuraciones de seguridad y optimizaciones de rendimiento. Siempre prueba tus cambios con curl antes de desplegar en producción y mantén un respaldo de tu archivo .htaccess funcional.