Pular para o conteúdo
>_ITDITDPlataforma de Segurança Web

Guias de Segurança

Mantendo o .env fora da web pública em hospedagem compartilhada

Como proteger o .env em hospedagem compartilhada (apps estilo Laravel): a correção real é manter o corpo do app fora do web root e expor só public/. Primeiros socorros com .htaccess, a reestruturação definitiva e a autoverificação.

Publicado 2026-06-07 Atualizado 2026-06-07 4 min de leitura

Para: qualquer pessoa rodando apps estilo Laravel (onde só public/ deveria ser exposto) em hospedagem compartilhada com Apache/.htaccess. Destilado de um incidente real (→ .env exposto ao mundo) — para proteger o seu próprio deploy.

A visão deste site: isto não é 'o erro de uma pessoa'

A cultura inicial de hospedagem compartilhada colocava tudo diretamente sob public_html. O Laravel (que espera só public/ exposto) fluiu para dentro disso, e um layout perigoso virou o padrão de fato — até a própria documentação de fornecedores coloca projetos dentro de public_html. Então a correção é processo, não vigilância: mude o padrão do seu deploy.

Acerte o posicionamento (diagrama)

✗ Perigoso (corpo na raiz pública)

public_html/
├─ .env   ← legível!
├─ .git/  vendor/
└─ public/

✓ Seguro (corpo fora, só public)

app-laravel/   ← fora do web root
├─ .env  .git/  ← inalcançável
└─ public/ → requerido por public_html
Corpo fora do docroot, exponha só public/. Só isso já torna o .env inalcançável.

Passo 1: Primeiros socorros (rápido, reversível)

Adicione um bloco de negação no topo do .htaccess do web root (um exemplo de configuração defensiva). Páginas normais não são afetadas.

# === 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>
 
# se as entranhas do Laravel ficam visíveis em /<app>/storage/... etc.
RedirectMatch 403 (?i)^/[^/]+/(storage|bootstrap/cache|config|database|resources|routes|tests|vendor)(/|$)
# === END ===

Primeiros socorros não são o objetivo

Uma blacklist é um jogo de bate-toupeira — você vai esquecer o composer.lock, novos nomes de arquivo adicionam novos buracos. Trate-a como ganho de tempo até o Passo 2.

Passo 2: Reestruture (a correção permanente)

Mova o corpo do app para fora do web root e deixe um pequeno ponto de entrada que apenas o carrega.

# 1. Mova o corpo para fora do web root (mesmo sistema de arquivos → mv é instantâneo, atômico)
mv ~/example/public_html/app  ~/example/app-laravel
 
# 2. Coloque só uma entrada mínima no web root
mkdir -p ~/example/public_html/sub
cat > ~/example/public_html/sub/index.php <<'PHP'
<?php require __DIR__ . '/../../app-laravel/public/index.php';
PHP
 
# 3. Copie o public/.htaccess do Laravel, faça symlink dos assets estáticos
# 4. Limpe o cache de configuração
rm -f ~/example/app-laravel/bootstrap/cache/{config,services,packages,routes-v7}.php

A armadilha que este site aprendeu na pele: bootstrap-redirect em vez de symlink

Tornar o próprio public_html um symlink para public/ pode retornar 500 no docroot de um subdomínio cadastrado em alguns ambientes (ele não segue). Manter o docroot como um diretório real e dar require por caminho absoluto a partir de um pequeno index.php (a forma "bootstrap-redirect") é mais estável em hospedagem compartilhada com escopo restrito. Além disso, o opcache pode memorizar um carregamento falho e continuar retornando 500 — o último recurso é mudar o nome do arquivo de entrada (entry.php).

Passo 3: Rotacione as chaves (presuma que foram vistas)

Se houve qualquer janela de exposição, trate as chaves do .env como já vistas e substitua-as. Ordem: segredos de API externa / OAuth → chaves de criptografia → e-mail → banco de dados. Veja o glossário do .env. Reemitir um segredo OAuth pode invalidar os refresh_tokens, então tenha um fluxo de reautorização pronto primeiro.

Passo 4: Autoverificação (faça um hábito)

Por fim, confirme que nada está de fato exposto no seu próprio site (apenas contra domínios que você possui).

# Um 200 com corpo significa que está exposto. 403/404 está OK por enquanto.
curl -sI https://seu-dominio/.env | head -1
curl -sI https://seu-dominio/.git/config | head -1

Verificar isto em cada deploy pega erros de posicionamento cedo.

Leia a seguir

FAQ

QQual a coisa mais rápida que posso fazer agora?
A

Adicione um bloco de negação ao .htaccess do seu web root para .env, .git, dumps SQL, credentials.json etc. É reversível e não afeta páginas normais — mas é primeiros socorros; a correção real é reestruturar.

QPor que o .htaccess sozinho não basta?
A

Uma blacklist é um jogo de bate-toupeira — novos nomes de arquivo adicionam novos buracos. Mova o corpo do app para fora do web root e o .env simplesmente não pode ser alcançado, sem regras a manter.

QPor que não só fazer symlink do public/ para expô-lo?
A

Em alguns ambientes, reapontar o docroot de um subdomínio cadastrado para outra árvore via symlink não segue e retorna 500. Na experiência deste site, manter o docroot como um diretório real e dar `require` por caminho absoluto a partir de um pequeno index.php (a forma 'bootstrap-redirect') é mais estável.