Un archivo .htaccess contiene directivas de Apache que se aplican al directorio donde está y a todos sus subdirectorios. Apache lo lee en cada petición, así que los cambios tienen efecto al instante, sin recargar el servidor. Es la forma habitual de configurar redirecciones y reglas de reescritura en hostings compartidos y en aplicaciones como WordPress. En este tutorial activarás .htaccess en Apache sobre Ubuntu 24.04 y lo usarás para las tareas más comunes: redirecciones, HTTPS obligatorio, URL limpias, protección con contraseña y cabeceras de caché y seguridad.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, y un usuario no root con privilegios
sudo. - Apache instalado (
sudo apt install apache2) con un sitio cuyoDocumentRootsea/var/www/your_domain. Sustituyeyour_domainpor tu dominio en toda la guía. - Para las reglas de HTTPS, un certificado TLS ya instalado en el sitio, por ejemplo de Let's Encrypt.
Notasi administras el servidor, las mismas directivas son más eficientes dentro del VirtualHost, porque Apache las lee una sola vez al arrancar en lugar de buscar archivos
.htaccessen cada directorio y en cada petición. Usa.htaccesscuando la aplicación lo requiera, cuando otras personas deban cambiar reglas sin acceso root o en hostings compartidos.
Paso 1: Permitir .htaccess en el sitio
Apache solo lee .htaccess en los directorios donde AllowOverride lo permite. En Ubuntu, /etc/apache2/apache2.conf define AllowOverride None para /var/www/, así que por defecto los archivos .htaccess se ignoran sin ningún aviso.
Abre el VirtualHost de tu sitio:
sudo nano /etc/apache2/sites-available/your_domain.conf
Añade un bloque <Directory> para el DocumentRoot dentro de <VirtualHost *:80> (y dentro de <VirtualHost *:443> si tienes un archivo aparte para HTTPS, por ejemplo your_domain-le-ssl.conf creado por Certbot):
<VirtualHost *:80>
ServerName your_domain
ServerAlias www.your_domain
DocumentRoot /var/www/your_domain
<Directory /var/www/your_domain>
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
AllowOverride All permite cualquier directiva válida en .htaccess. Si quieres restringirlo, indica solo las categorías que usa esta guía: AllowOverride FileInfo AuthConfig Indexes Options. FileInfo cubre reescrituras, redirecciones, cabeceras y páginas de error; AuthConfig, la autenticación y Require; Indexes, las directivas de caché de mod_expires, y Options, la directiva Options.
Paso 2: Activar los módulos necesarios
Las reglas de esta guía usan mod_rewrite para reescrituras, mod_headers para cabeceras y mod_expires para la caché del navegador. En Ubuntu vienen instalados pero no habilitados:
sudo a2enmod rewrite headers expires
Valida la configuración y reinicia Apache para cargar los módulos:
sudo apache2ctl configtest
sudo systemctl restart apache2
Syntax OK
Comprueba que están cargados:
sudo apache2ctl -M | grep -E 'rewrite|headers|expires'
expires_module (shared)
headers_module (shared)
rewrite_module (shared)
Paso 3: Comprobar que Apache lee .htaccess
Crea el archivo en la raíz del sitio con una línea deliberadamente inválida:
echo "EstaDirectivaNoExiste" | sudo tee /var/www/your_domain/.htaccess
Pide la página:
curl -sI http://your_domain | head -n 1
HTTP/1.1 500 Internal Server Error
Un error 500 confirma que Apache lee el archivo. El log de errores muestra la causa:
sudo tail -n 1 /var/log/apache2/error.log
[core:alert] ... /var/www/your_domain/.htaccess: Invalid command 'EstaDirectivaNoExiste', perhaps misspelled or defined by a module not included in the server configuration
Si la respuesta es 200 OK, AllowOverride no se está aplicando: revisa el paso 1 y que editaste el VirtualHost correcto (sudo apache2ctl -S lista los sitios activos). Esta misma técnica te servirá para depurar: cualquier error de sintaxis en .htaccess produce un 500 y deja el motivo en el log.
Vacía el archivo para empezar a añadir reglas reales:
sudo truncate -s 0 /var/www/your_domain/.htaccess
Todas las reglas siguientes van en /var/www/your_domain/.htaccess. Ábrelo con:
sudo nano /var/www/your_domain/.htaccess
Paso 4: Forzar HTTPS y un único nombre de dominio
Conviene que el sitio responda en una sola dirección canónica, por ejemplo https://your_domain sin www. Añade al principio del archivo:
RewriteEngine On
# Redirigir www a la versión sin www, ya por HTTPS
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [L,R=301]
# Redirigir HTTP a HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Cómo funciona:
RewriteConddefine una condición que debe cumplirse para aplicar laRewriteRuleque la sigue.%1es lo capturado entre paréntesis en la condición, es decir, el dominio sinwww..^enRewriteRulecoincide con cualquier ruta;%{REQUEST_URI}conserva la ruta y la consulta original.R=301genera una redirección permanente yLdetiene el procesamiento de las reglas siguientes.
Si el sitio está detrás de un balanceador o un proxy que termina TLS, Apache recibe siempre HTTP y la condición %{HTTPS} off crea un bucle de redirecciones. En ese caso usa la cabecera que envía el proxy:
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Guarda el archivo y comprueba las redirecciones:
curl -sI http://www.your_domain/contacto | grep -E '^(HTTP|Location)'
HTTP/1.1 301 Moved Permanently
Location: https://your_domain/contacto
Consejomientras pruebes reglas nuevas usa
R=302. Los navegadores guardan en caché las redirecciones 301 y seguirán aplicando una regla errónea aunque la corrijas. Cambia a301cuando todo funcione.
Paso 5: Redirigir páginas movidas
Para redirecciones fijas de una URL a otra no necesitas mod_rewrite: la directiva Redirect de mod_alias, habilitado por defecto, es más simple. Añade al archivo:
Redirect 301 /pagina-antigua.html /pagina-nueva/
Redirect 301 /blog/ https://blog.your_domain/
La segunda línea redirige también todo lo que cuelga de /blog/, conservando el resto de la ruta: /blog/post-1 va a https://blog.your_domain/post-1. Para patrones, usa RedirectMatch con una expresión regular:
RedirectMatch 301 ^/productos/([0-9]+)\.html$ /tienda/producto/$1
Compruébalo:
curl -sI https://your_domain/pagina-antigua.html | grep -E '^(HTTP|Location)'
HTTP/1.1 301 Moved Permanently
Location: https://your_domain/pagina-nueva/
Paso 6: Crear URL limpias
Estas reglas permiten acceder a acerca.html como /acerca, sin extensión, y dirigen al index.php de la aplicación todas las rutas que no correspondan a un archivo o directorio real (el patrón de controlador frontal que usan WordPress, Laravel y la mayoría de frameworks PHP). Añádelas después de las reglas del paso 4:
# Servir /acerca desde acerca.html si existe
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^(.+)$ $1.html [L]
# Enviar el resto de rutas inexistentes a index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L]
-f comprueba que existe un archivo y -d un directorio. Sin la bandera R, la reescritura es interna: el navegador sigue viendo /acerca en la barra de direcciones. Usa solo el segundo bloque si tu aplicación es PHP, o solo el primero si es un sitio HTML estático.
Crea una página de prueba y compruébala:
echo "<h1>Acerca de</h1>" | sudo tee /var/www/your_domain/acerca.html
curl -s https://your_domain/acerca
<h1>Acerca de</h1>
Paso 7: Proteger un directorio con contraseña
La autenticación básica de Apache es útil para proteger un área de administración o un entorno de pruebas. Instala htpasswd, incluido en el paquete apache2-utils:
sudo apt install apache2-utils
Crea el archivo de contraseñas fuera del DocumentRoot, para que nunca se pueda descargar desde la web. La opción -c crea el archivo y solo debe usarse con el primer usuario; te pedirá la contraseña:
sudo htpasswd -c /etc/apache2/.htpasswd your_user
Apache debe poder leerlo, pero nadie más:
sudo chown root:www-data /etc/apache2/.htpasswd
sudo chmod 640 /etc/apache2/.htpasswd
Crea un .htaccess en el directorio que quieras proteger, por ejemplo /var/www/your_domain/admin:
sudo mkdir -p /var/www/your_domain/admin
sudo nano /var/www/your_domain/admin/.htaccess
AuthType Basic
AuthName "Zona restringida"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user
Comprueba que sin credenciales se deniega el acceso y con ellas se permite:
curl -sI https://your_domain/admin/ | head -n 1
curl -sI -u your_user https://your_domain/admin/ | head -n 1
HTTP/1.1 401 Unauthorized
HTTP/1.1 200 OK
El segundo comando pide la contraseña. Si el directorio está vacío, verás 403 o 404 en lugar de 200, lo que igualmente indica que la autenticación se ha superado. La autenticación básica envía las credenciales codificadas pero sin cifrar, así que úsala solo sobre HTTPS.
Paso 8: Bloquear el acceso a archivos sensibles
Apache ya deniega en Ubuntu el acceso a los archivos que empiezan por .ht (como el propio .htaccess). Añade reglas para otros archivos que no deben descargarse nunca, como copias de seguridad, configuraciones o el directorio .git, y desactiva los listados de directorios. En el .htaccess de la raíz:
Options -Indexes
<FilesMatch "\.(env|ini|log|sql|bak|swp)$">
Require all denied
</FilesMatch>
RewriteRule (^|/)\.git(/|$) - [R=404,L]
Options -Indexeshace que un directorio sinindexdevuelva403en lugar de listar su contenido.FilesMatchconRequire all denieddevuelve403para las extensiones indicadas.- La
RewriteRuleresponde404a cualquier ruta dentro de.git, como si no existiera, sin confirmar su presencia. Colócala justo después deRewriteEngine On, antes de las reglas del paso 6, para que se evalúe primero.
Compruébalo. La primera prueba solo devuelve 403 si existe un archivo .env en la raíz del sitio:
curl -sI https://your_domain/.env | head -n 1
curl -sI https://your_domain/.git/config | head -n 1
HTTP/1.1 403 Forbidden
HTTP/1.1 404 Not Found
Paso 9: Añadir cabeceras de caché y seguridad
mod_expires indica al navegador cuánto tiempo puede reutilizar cada tipo de archivo sin volver a pedirlo. Aplícalo a los recursos estáticos que llevan versión en el nombre o cambian poco:
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/webp "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType text/css "access plus 1 week"
ExpiresByType text/javascript "access plus 1 week"
ExpiresByType application/javascript "access plus 1 week"
ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
No asignes una caché larga al HTML, o los visitantes tardarán en ver los cambios del sitio. Añade también algunas cabeceras de seguridad con mod_headers:
<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>
Comprueba las cabeceras de un archivo CSS de tu sitio (sustituye la ruta por una real):
curl -sI https://your_domain/css/style.css | grep -iE 'cache-control|expires|x-content-type'
Cache-Control: max-age=604800
Expires: Fri, 02 Oct 2026 10:00:00 GMT
X-Content-Type-Options: nosniff
Páginas de error personalizadas
Con ErrorDocument puedes sustituir las páginas de error genéricas de Apache por las tuyas. La ruta es relativa al DocumentRoot:
ErrorDocument 404 /errores/404.html
ErrorDocument 403 /errores/403.html
Crea los archivos en /var/www/your_domain/errores/ y comprueba que una URL inexistente devuelve tu página con el código 404.
Solución de problemas
- Las reglas no hacen nada: Apache no lee el archivo. Repite la prueba del paso 3; si no obtienes un 500, revisa
AllowOverrideen el VirtualHost que realmente atiende el dominio. 500 Internal Server Errortras editar el archivo: hay un error de sintaxis o se usa un módulo no habilitado.sudo tail /var/log/apache2/error.logindica la línea y la directiva.Invalid command 'RewriteEngine'o'Header'en el log: el módulo no está activo. Actívalo consudo a2enmod rewriteosudo a2enmod headersy reinicia Apache.ERR_TOO_MANY_REDIRECTS: el sitio está detrás de un proxy o CDN que termina TLS y la regla%{HTTPS} offse cumple siempre. Usa la variante conX-Forwarded-Protodel paso 4.options not allowed hereen el log:AllowOverrideno incluye la categoría de esa directiva (por ejemploOptionsnecesitaAllowOverride OptionsoAll).
Conclusión
Has habilitado .htaccess en Apache, has comprobado que el servidor lo lee y lo has usado para forzar HTTPS con un dominio canónico, redirigir páginas, crear URL limpias, proteger un directorio con contraseña, bloquear archivos sensibles y añadir cabeceras de caché y seguridad.
Como siguientes pasos puedes:
- Mover las reglas estables al VirtualHost y volver a
AllowOverride Nonesi no necesitas que se cambien sin acceso root, para ganar rendimiento. - Activar la compresión de respuestas con
mod_deflateomod_brotli. - Añadir la cabecera
Strict-Transport-Securityuna vez que todo el sitio funcione solo por HTTPS.
