DocumentRoot: dónde vive tu sitio
DocumentRoot es la ruta del sistema de archivos desde la que Apache publica contenido para un VirtualHost. Todo lo que esté dentro de esa carpeta (y permitido por la configuración) puede ser servido al navegador como archivos estáticos (HTML, CSS, imágenes, JS, etc.).
Objetivo práctico: definir una carpeta de sitio limpia, con permisos correctos, un index predecible y tipos MIME correctos para que el navegador interprete cada archivo como corresponde.
Estructura recomendada del sitio
Una estructura simple y mantenible para un sitio estático o la parte pública de una app:
/var/www/mi-sitio/ (raíz del proyecto, no necesariamente pública) /var/www/mi-sitio/public/ (DocumentRoot: solo lo publicable) /var/www/mi-sitio/public/index.html /var/www/mi-sitio/public/assets/ /var/www/mi-sitio/public/assets/css/styles.css /var/www/mi-sitio/public/assets/img/logo.pngRecomendación clave: usa una carpeta public/ como DocumentRoot para evitar exponer por accidente archivos sensibles (por ejemplo, .env, backups, repositorios, etc.).
Guía paso a paso: crear y publicar un sitio
1) Crear carpetas y archivos de prueba
Crea la estructura y tres recursos: un HTML, un CSS y una imagen.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
sudo mkdir -p /var/www/mi-sitio/public/assets/css sudo mkdir -p /var/www/mi-sitio/public/assets/imgcat > /var/www/mi-sitio/public/index.html <<'HTML' <!doctype html> <html lang="es"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Mi sitio</title> <link rel="stylesheet" href="/assets/css/styles.css"> </head> <body> <h1>Hola Apache</h1> <p>Probando HTML + CSS + imagen.</p> <img src="/assets/img/logo.png" alt="Logo" width="160"> </body> </html> HTMLcat > /var/www/mi-sitio/public/assets/css/styles.css <<'CSS' body { font-family: system-ui, Arial, sans-serif; margin: 2rem; } h1 { color: #1f6feb; } img { border: 2px solid #ddd; padding: 6px; border-radius: 8px; } CSSPara la imagen, puedes copiar cualquier PNG existente o generar una de prueba. Ejemplo copiando desde un lugar conocido (ajusta la ruta):
sudo cp /ruta/a/una/imagen.png /var/www/mi-sitio/public/assets/img/logo.png2) Definir el VirtualHost con DocumentRoot
Crea un VirtualHost (nombre de archivo orientativo; puede variar según tu distro):
sudo nano /etc/apache2/sites-available/mi-sitio.confContenido mínimo recomendado:
<VirtualHost *:80> ServerName mi-sitio.local DocumentRoot /var/www/mi-sitio/public <Directory /var/www/mi-sitio/public> Options -Indexes +FollowSymLinks AllowOverride None Require all granted DirectoryIndex index.html index.php </Directory> ErrorLog ${APACHE_LOG_DIR}/mi-sitio_error.log CustomLog ${APACHE_LOG_DIR}/mi-sitio_access.log combined </VirtualHost>Options -Indexes: evita listado de directorios si falta un index (buena práctica de seguridad).+FollowSymLinks: permite seguir enlaces simbólicos (útil, pero úsalo con criterio).AllowOverride None: desactiva.htaccess(mejor rendimiento y control centralizado).DirectoryIndex: define el orden de archivos índice.
Activa el sitio y recarga Apache:
sudo a2ensite mi-sitio.conf sudo apache2ctl configtest sudo systemctl reload apache2Si estás usando un nombre local como mi-sitio.local, añade una entrada en tu /etc/hosts apuntando a la IP del servidor (por ejemplo, 127.0.0.1 si es local):
sudo sh -c 'echo "127.0.0.1 mi-sitio.local" >> /etc/hosts'Permisos y propiedad: servir archivos sin abrir puertas
Usuario/grupo del servicio web
Apache suele ejecutarse como un usuario sin privilegios (por ejemplo, www-data en Debian/Ubuntu). Ese usuario necesita permisos de lectura sobre los archivos del sitio y permisos de ejecución (traversal) sobre los directorios para poder entrar en ellos.
Regla práctica: directorios 755 y archivos 644 suelen ser suficientes para contenido estático.
Diferencias entre chmod y chown
chmod: cambia permisos (lectura/escritura/ejecución) para propietario, grupo y otros.chown: cambia propietario y/o grupo del archivo/directorio.
Ejemplo: dejar propietario a tu usuario (para editar) y grupo al del servidor (para lectura controlada), con permisos seguros:
# Ajusta TU_USUARIO y el grupo del servidor (ej. www-data) sudo chown -R TU_USUARIO:www-data /var/www/mi-sitio# Directorios 755 y archivos 644 sudo find /var/www/mi-sitio/public -type d -exec chmod 755 {} \; sudo find /var/www/mi-sitio/public -type f -exec chmod 644 {} \;Impacto en seguridad:
- Evita
777: permite escritura a cualquiera; facilita defacement y escaladas si hay otra vulnerabilidad. - Evita que el usuario del servidor web tenga permisos de escritura en el
DocumentRootsi no es estrictamente necesario (reduce el impacto de una posible ejecución de código o subida de archivos). - Si necesitas subidas (uploads), crea un directorio específico con permisos mínimos y controles adicionales, en lugar de abrir todo el árbol.
Comprobación rápida de permisos efectivos
Verifica permisos y propiedad:
ls -ld /var/www/mi-sitio/public ls -l /var/www/mi-sitio/public/index.htmlComprueba que el usuario del servicio puede leer (sin convertirlo en propietario). En Debian/Ubuntu, por ejemplo:
sudo -u www-data test -r /var/www/mi-sitio/public/index.html && echo OK || echo FAILDirectiva Directory: control fino del acceso
La sección <Directory ...> define cómo Apache trata una ruta del sistema de archivos. Es el lugar adecuado para aplicar políticas de seguridad y comportamiento del contenido.
Options: qué funcionalidades se permiten
Indexes: si está activo y no hayDirectoryIndex, Apache muestra listado de archivos. Recomendación: desactivarlo con-Indexes.FollowSymLinks: permite seguir symlinks. Útil, pero revisa que no apunten fuera de lo esperado.ExecCGI: permite ejecutar CGI (no lo actives si no lo usas).
Ejemplo seguro típico para contenido estático:
<Directory /var/www/mi-sitio/public> Options -Indexes +FollowSymLinks Require all granted </Directory>AllowOverride y .htaccess: cuándo permitirlo y cuándo evitarlo
.htaccess permite que reglas de Apache vivan dentro del propio directorio del sitio. Es útil en entornos compartidos o cuando no tienes acceso a la configuración global, pero tiene dos costes:
- Rendimiento: Apache debe comprobar
.htaccessen cada petición (y en cada nivel de directorio). - Gobernanza/seguridad: reglas críticas quedan dispersas y pueden ser modificadas por quien tenga escritura en el árbol.
Recomendación:
- En servidores bajo tu control: usa
AllowOverride Noney configura todo en el VirtualHost. - Permite
.htaccesssolo si lo necesitas (por ejemplo, para reglas de reescritura en un hosting donde no puedes editar el VirtualHost), y limita el alcance.
Ejemplo permitiendo solo directivas de reescritura (si necesitas mod_rewrite):
<Directory /var/www/mi-sitio/public> Options -Indexes +FollowSymLinks AllowOverride FileInfo Require all granted </Directory>Ejemplo de .htaccess mínimo (solo demostrativo) para forzar un index alternativo o reglas de cache (evita ponerlo si puedes hacerlo en el VirtualHost):
# /var/www/mi-sitio/public/.htaccess DirectoryIndex index.htmlDirectoryIndex: qué archivo se sirve por defecto
Si el usuario solicita / o un directorio, Apache busca en orden los nombres indicados en DirectoryIndex. Si no encuentra ninguno y Indexes está desactivado, devolverá 403.
Ejemplo:
DirectoryIndex index.html index.phpTipos MIME: que el navegador interprete bien el contenido
Los tipos MIME se envían en la cabecera Content-Type. Si el servidor envía un tipo incorrecto, el navegador puede no aplicar estilos, no mostrar imágenes o bloquear recursos por políticas de seguridad.
Apache suele mapear extensiones a MIME mediante mime.types (y el módulo de MIME). En la práctica, .html debe ser text/html, .css debe ser text/css, y .png debe ser image/png.
Si necesitas forzar o añadir tipos, se puede hacer con directivas como AddType (preferiblemente en la config del sitio, no en .htaccess salvo necesidad):
<IfModule mod_mime.c> AddType text/css .css AddType image/png .png </IfModule>Comprobaciones: servir HTML, CSS e imagen y validar cabeceras
1) Probar respuesta HTTP y cabeceras con curl
Comprueba que el HTML se sirve y revisa Content-Type:
curl -I http://mi-sitio.local/Deberías ver algo similar a:
HTTP/1.1 200 OK Content-Type: text/htmlComprueba el CSS:
curl -I http://mi-sitio.local/assets/css/styles.cssEsperado:
Content-Type: text/cssComprueba la imagen:
curl -I http://mi-sitio.local/assets/img/logo.pngEsperado:
Content-Type: image/png2) Verificar que el contenido realmente llega
Descarga una muestra del HTML y del CSS:
curl -s http://mi-sitio.local/ | head curl -s http://mi-sitio.local/assets/css/styles.cssPara la imagen, valida que no sea una página de error HTML guardada como PNG:
curl -s -o /tmp/logo.png http://mi-sitio.local/assets/img/logo.png file /tmp/logo.pngfile debería indicar algo como PNG image data.
3) Diagnóstico rápido si algo falla
| Síntoma | Causa típica | Qué revisar |
|---|---|---|
| 403 Forbidden | Permisos insuficientes o reglas de acceso | Permisos de directorios (x) y archivos (r), sección <Directory>, Require all granted |
| 404 Not Found | Ruta incorrecta o archivo no existe | DocumentRoot, rutas en el HTML (/assets/...), estructura real en disco |
| Se ve HTML pero no aplica CSS | MIME incorrecto o ruta incorrecta | curl -I al CSS, Content-Type: text/css, ubicación del archivo |
| Imagen rota | Archivo no accesible o tipo incorrecto | curl -I a la imagen, permisos, file sobre el descargado |