Guías de seguridad
Mantener el .env fuera de la web pública en hosting compartido
Protege el .env en hosting compartido: cómo dejar .env, .git y vendor inalcanzables al desplegar apps tipo Laravel. La solución real es sacar la app de la raíz web y exponer solo public/. Primeros auxilios con .htaccess y reestructuración.
Para: cualquiera que ejecute apps tipo Laravel (donde solo public/ debería estar expuesto) en hosting compartido con Apache/.htaccess. Destilado de un incidente real (→ .env expuesto al mundo) — para asegurar tu propio despliegue.
La visión de este sitio: esto no es 'el error de una persona'
La cultura temprana del hosting compartido ponía todo directamente bajo public_html. Laravel (que espera que solo se exponga public/) fluyó hacia eso, y un diseño peligroso se convirtió en el estándar de facto — incluso la propia documentación de los proveedores pone los proyectos dentro de public_html. Así que la solución es proceso, no vigilancia: cambia tu valor por defecto de despliegue.
Acierta con la ubicación (diagrama)
✗ Peligroso (cuerpo en la raíz pública)
public_html/ ├─ .env ← ¡legible! ├─ .git/ vendor/ └─ public/
✓ Seguro (cuerpo fuera, solo public)
app-laravel/ ← fuera de la raíz web ├─ .env .git/ ← inalcanzables └─ public/ → requerido desde public_html
Paso 1: Primeros auxilios (rápido, reversible)
Añade un bloque de denegación al principio del .htaccess de la raíz web (un ejemplo de configuración defensiva). Las páginas normales no se ven afectadas.
# === SECURITY BLOCK ===
RedirectMatch 404 (?i)/\.(env|git)(\..*)?(/|$)
<FilesMatch "(?i)\.(sql|sql\.gz|bak|old|swp|save|orig|tgz)$|^credentials\.json$|\.bk[._]">
Require all denied
</FilesMatch>
<FilesMatch "(?i)^(phpinfo|info)\.php$">
Require all denied
</FilesMatch>
# si las partes internas de Laravel son visibles en /<app>/storage/... etc.
RedirectMatch 403 (?i)^/[^/]+/(storage|bootstrap/cache|config|database|resources|routes|tests|vendor)(/|$)
# === END ===Los primeros auxilios no son el objetivo
Una lista negra es un juego del topo — olvidarás composer.lock, los nombres de archivo nuevos añaden agujeros nuevos. Trátalo como ganar tiempo hasta el Paso 2.
Paso 2: Reestructurar (la solución permanente)
Mueve el cuerpo de la app fuera de la raíz web y deja un punto de entrada diminuto que solo lo cargue.
# 1. Mueve el cuerpo fuera de la raíz web (mismo sistema de archivos → mv es instantáneo, atómico)
mv ~/example/public_html/app ~/example/app-laravel
# 2. Pon solo una entrada mínima en la raíz web
mkdir -p ~/example/public_html/sub
cat > ~/example/public_html/sub/index.php <<'PHP'
<?php require __DIR__ . '/../../app-laravel/public/index.php';
PHP
# 3. Copia el public/.htaccess de Laravel, haz symlink de los recursos estáticos
# 4. Limpia la caché de configuración
rm -f ~/example/app-laravel/bootstrap/cache/{config,services,packages,routes-v7}.phpLa trampa que este sitio aprendió a las malas: bootstrap-redirect en lugar de symlink
Hacer del propio public_html un symlink a public/ puede devolver 500 en el docroot de un subdominio registrado en algunos entornos (no se sigue). Mantener el docroot como un directorio real y hacer require por ruta absoluta desde un pequeño index.php (la forma "bootstrap-redirect") es más estable en hosting compartido con alcance restringido. Además, opcache puede memorizar una carga fallida y seguir devolviendo 500 — el último recurso es cambiar el nombre del archivo de entrada (entry.php).
Paso 3: Rota las claves (asume que fueron vistas)
Si hubo cualquier ventana de exposición, trata las claves del .env como ya vistas y reemplázalas. Orden: secretos de API externa / OAuth → claves de cifrado → correo → BD. Ver el glosario del .env. Reemitir un secreto de OAuth puede invalidar los refresh_token, así que ten lista primero una vía de reautorización.
Paso 4: Autocomprobación (hazlo un hábito)
Por último, confirma que nada esté de verdad expuesto en tu propio sitio (solo contra dominios que poseas).
# Un 200 con cuerpo significa que está expuesto. 403/404 está bien por ahora.
curl -sI https://your-domain/.env | head -1
curl -sI https://your-domain/.git/config | head -1Comprobar esto en cada despliegue detecta pronto los errores de ubicación.
Sigue leyendo
- Incidente: El .env expuesto al mundo entero
- Glosario: qué es .env
- Fundamentos: qué tiene de peligroso el .env y las claves de API
FAQ
Q¿Qué es lo más rápido que puedo hacer ahora mismo?
Añade un bloque de denegación al .htaccess de tu raíz web para .env, .git, volcados SQL, credentials.json, etc. Es reversible y no afecta a las páginas normales — pero son primeros auxilios; la solución real es reestructurar.
Q¿Por qué no basta con el .htaccess solo?
Una lista negra es un juego del topo — los nombres de archivo nuevos añaden agujeros nuevos. Mueve el cuerpo de la app fuera de la raíz web y el .env simplemente no se puede alcanzar, sin reglas que mantener.
Q¿Por qué no hacer simplemente un symlink de public/ para exponerlo?
En algunos entornos, reapuntar el docroot de un subdominio registrado a otro árbol vía symlink no se sigue y devuelve 500. Según la experiencia de este sitio, mantener el docroot como un directorio real y hacer `require` por ruta absoluta desde un pequeño index.php (la forma 'bootstrap-redirect') es más estable.