.htaccess: guía práctica de redirecciones 301, HTTPS y seguridad en Apache
Cómo forzar HTTPS sin bucles, encadenar bien las redirecciones 301, activar gzip y caché, añadir cabeceras de seguridad y depurar un .htaccess que devuelve error 500.
Qué es realmente .htaccess
.htaccess es un archivo de configuración por directorio que Apache lee en cada petición. No es un lenguaje aparte: son las mismas directivas que irían en el VirtualHost, pero aplicadas al directorio donde vive el archivo y a todos sus hijos. Por eso es la única forma de configurar Apache en hosting compartido, donde no tienes acceso a /etc/apache2.
Dos consecuencias directas:
- Los cambios son inmediatos. No hay que recargar el servicio.
- Hay un coste por petición. Apache recorre el árbol de directorios buscando
.htaccessen cada nivel. En un servidor propio, mover las reglas al VirtualHost y ponerAllowOverride Nonees medible.
Para que las directivas se apliquen, el VirtualHost debe permitirlo con AllowOverride All. Si tus reglas se ignoran por completo y no aparece ningún error en el log, ese es el primer sitio donde mirar.
Forzar HTTPS sin provocar un bucle infinito
La versión de libro es esta:
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
Funciona cuando Apache termina el TLS. Deja de funcionar cuando delante hay Cloudflare, un balanceador o un proxy inverso: en ese caso el tráfico llega a Apache por HTTP, %{HTTPS} nunca vale on y el navegador entra en un bucle de redirecciones (ERR_TOO_MANY_REDIRECTS). La condición correcta detrás de un proxy es la cabecera reenviada:
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
Elige un dominio canónico y respétalo
example.com y www.example.com son URLs distintas para un buscador. Sirve el contenido en una sola y redirige la otra con un 301 único. El error habitual es encadenar: http://www → https://www → https://. Son dos saltos, dos RTT extra y una señal más difusa. Ordena las reglas para que el primer 301 lleve ya al destino final.
301 frente a 302
- 301 (permanente): transfiere la autoridad de enlaces y los navegadores lo cachean con agresividad. Úsalo en migraciones, cambios de estructura y unificación de HTTPS/www. Un 301 equivocado tarda en revertirse en los clientes que ya lo guardaron.
- 302 (temporal): no consolida la URL nueva en el índice. Úsalo para mantenimientos, pruebas y redirecciones que dependan de la sesión.
Al mover contenido, redirige cada URL a su equivalente más cercano. Mandar todo a la portada produce soft 404: Google lo interpreta como contenido desaparecido y acabas perdiendo las posiciones que intentabas salvar.
Rendimiento: compresión y caché en diez líneas
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css application/javascript application/json image/svg+xml
</IfModule>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType text/html "access plus 0 seconds"
</IfModule>
Comprime texto, no imágenes ni vídeo (ya vienen comprimidos: sólo gastarías CPU). Cachea un año los assets cuyo nombre incluya un hash y deja el HTML sin caché para que cada despliegue se vea al instante. Esto mejora el LCP, una de las Core Web Vitals que Google usa como señal de experiencia de página.
Seguridad: lo que deberías tener siempre
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>
<FilesMatch "^\.|\.(env|sql|log|bak|ini)$">
Require all denied
</FilesMatch>
Options -Indexes
El bloque FilesMatch es el que más incidentes evita: en hosting compartido, un .env o un backup .sql accesible por URL es una filtración completa de credenciales. Options -Indexes impide que Apache liste el contenido de carpetas sin index.html. Añade Strict-Transport-Security sólo cuando HTTPS funcione en todo el dominio y sus subdominios, porque el navegador recordará la política durante todo el max-age.
Fallback para aplicaciones SPA
Una SPA con rutas de cliente necesita que las URLs desconocidas sirvan index.html, pero sólo si el recurso no existe:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [L]
Sin las dos condiciones !-f y !-d romperás imágenes, CSS y JavaScript, porque también los reescribirías al HTML.
Depurar un .htaccess que no funciona
1. Confirma el nombre exacto (.htaccess, con punto) y la ubicación (raíz del sitio). 2. Comprueba AllowOverride All en el VirtualHost. 3. Verifica los módulos cargados con apachectl -M | grep -E 'rewrite|headers|deflate|expires'. 4. Lee el error log: una directiva inválida aparece con el número de línea. 5. Aísla: comenta todo y descomenta bloque a bloque.
Y recuerda el detalle que más tiempo hace perder: en .htaccess el patrón de RewriteRule se evalúa sin la barra inicial. ^/blog no encaja nunca; ^blog sí.
Genera el archivo sin escribirlo a mano
Monta tu configuración con el generador de .htaccess: eliges HTTPS forzado, dominio canónico, redirecciones 301, caché, gzip, cabeceras y fallback SPA, y obtienes un archivo comentado listo para subir. Si administras el servidor, traslada esas reglas con el generador de VirtualHost de Apache, y si migras a Nginx, el generador de configuración Nginx te da el equivalente. Prueba tus patrones antes con el tester de regex.
Herramientas relacionadas
Crea un .htaccess para Apache con HTTPS forzado, redirecciones 301, caché, gzip, cabeceras de seguridad y fallback SPA.
Configura VirtualHosts con SSL, redirects 80→443 y logs personalizados.
Genera bloques server, proxy inverso y SSL con Let's Encrypt listos para producción.
Prueba expresiones regulares con resaltado de coincidencias y grupos capturados.