Guias de Segurança

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

Como deixar .env, .git e vendor inacessíveis em hospedagem compartilhada: código fora do web root, só public/ exposto, bloqueio com .htaccess e autoverificação.

Publicado 2026-06-07 Atualizado 2026-10-06 10 min de leitura
Nesta página

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.

Qual correção serve para o seu ambiente

"Manter o .env fora da web" muda de forma conforme o tipo de servidor e as suas permissões. Encontre primeiro a sua linha e depois siga os passos abaixo.

AmbienteCorreção permanentePrimeiros socorrosAtenção
Hospedagem compartilhada (com SSH)Tire o corpo do app da pasta pública e deixe ali só um pequeno index.php (Passo 2)Negar no .htaccessTransformar a própria pasta pública em symlink retorna 500 em alguns ambientes
Hospedagem compartilhada (só FTP)Reenvie o corpo do app um nível acima da pasta pública (public_html etc.) com o seu cliente FTPNegar no .htaccessA maioria das hospedagens permite gravar acima da pasta pública; se a sua não permitir, fale com o suporte
O painel de controle permite definir a pasta públicaAponte a pasta pública direto para app-laravel/publicNão é necessário (verifique mesmo assim)Depois de trocar, apague qualquer cópia do app que tenha ficado na pasta pública antiga
VPS (Apache)Aponte o DocumentRoot do VirtualHost para public/Regras de negação na configuração do servidorCom AllowOverride None (o padrão do Apache 2.4), o .htaccess não é lido
VPS (Nginx)Aponte root para public/ e negue arquivos que começam com pontoRegras de negação na configuração do servidorO Nginx não lê .htaccess

O objetivo é o mesmo em todos os casos: nada de .env, .git, vendor/ ou storage/ dentro da pasta pública. A documentação oficial do Laravel também avisa que mover o index.php para a raiz do projeto e servir a partir dali expõe muitos arquivos de configuração sensíveis à internet pública.

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>
 
# if Laravel internals are visible at /<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 sempre deixa algo escapar — o composer.lock ou qualquer novo nome de arquivo. 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. Move the body outside the web root (same filesystem → mv is instant, atomic)
mv ~/example/public_html/app  ~/example/app-laravel
 
# 2. Put only a minimal entry in the 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. Copy Laravel public/.htaccess, symlink static assets
# 4. Clear config cache
rm -f ~/example/app-laravel/bootstrap/cache/{config,services,packages,routes-v7}.php

Uma lição da operação deste site: 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).

# A 200 with a body means it's exposed. 403/404 is okay for now.
curl -sI https://your-domain/.env | head -1
curl -sI https://your-domain/.git/config | head -1

Verificar isto em cada deploy pega erros de posicionamento cedo.

Como verificar a sua configuração (com mais detalhe)

Além das duas linhas do Passo 4, estas verificações pegam mais do que costuma escapar. Rode-as apenas contra o seu próprio servidor e os seus próprios domínios.

1

Olhe o conteúdo, não só o código de status

Alguns apps respondem a qualquer URL desconhecida com a página inicial e um 200, então o status sozinho não diz nada. Imprima só o começo, por exemplo curl -s https://your-domain/.env | head -c 120, e veja se aparecem linhas de configuração como APP_KEY= ou DB_PASSWORD=. No .git/HEAD, uma linha que começa com ref: refs/heads/ significa que está exposto.

2

Verifique todos os apps, não só um

Repita a verificação para cada app em subdomínio ou subdiretório, não só no domínio principal. Erros de posicionamento tendem a ser copiados quando você adiciona apps, então corrigir um não significa que os outros estão corrigidos.

3

Procure na pasta pública do servidor

Com SSH, procure arquivos arriscados diretamente, por exemplo find ~/public_html -maxdepth 4 \( -name ".env*" -o -name ".git" -o -name "*.sql" -o -name "*.zip" \). Nenhuma saída é o resultado desejado. Só com FTP, ative "mostrar arquivos ocultos" no seu cliente FTP e revise a listagem.

4

Confirme que o .htaccess realmente faz efeito

Coloque na pasta pública um arquivo fictício inofensivo, sem segredos (por exemplo check.bak contendo só test), e abra-o pela URL. Você deve receber 403. Se ele abrir, o .htaccess não está sendo lido; verifique o tipo de servidor (Nginx, por exemplo) ou as configurações dele. Apague o arquivo fictício ao terminar.

5

Garanta que o modo de depuração está desligado

No .env, APP_ENV deve ser production e APP_DEBUG deve ser false. A documentação do Laravel diz que APP_DEBUG=true em produção arrisca expor valores de configuração sensíveis aos seus usuários. Com SSH, php artisan about --only=environment mostra o mesmo.

Erros comuns e como corrigi-los

  • Apagar o .env mas deixar o .git: um .git exposto pode permitir reconstruir commits antigos, incluindo um .env ou chaves antigas. Tire o .git da pasta pública também e reemita qualquer segredo que já tenha sido commitado.
  • Deixar cópias como .env.bak ou .env.old na pasta pública: uma cópia com valores reais é tão sensível quanto o .env. Mova-a para fora ou apague-a. Mantenha só valores de exemplo no .env.example.
  • Enviar o projeto inteiro como ZIP e deixá-lo lá depois de descompactar: o ZIP contém o .env. Apague-o logo depois de descompactar.
  • Rodar produção com APP_DEBUG=true: páginas de erro podem revelar valores de configuração. Em produção, deixe sempre false.
  • Achar que está seguro porque escreveu o .htaccess: Nginx e configurações com AllowOverride None nunca o leem. Use a verificação com arquivo fictício acima para confirmar.
  • Trocar o APP_KEY de repente: segundo a documentação do Laravel, mudar a chave desconecta todos os usuários logados e torna indecifráveis os dados criptografados com a chave antiga. No Laravel 11 e posteriores, coloque a chave antiga em APP_PREVIOUS_KEYS antes de trocar.

Um exemplo real: arquivos .env expostos coletados em grande escala

Em agosto de 2024, a equipe de pesquisa de uma empresa de segurança relatou uma operação que coletava automaticamente arquivos .env expostos em servidores web, usava as credenciais de nuvem contidas neles para copiar dados e depois exigia resgate. Segundo o relatório, cerca de 110.000 domínios foram alvo e mais de 90.000 variáveis de ambiente foram coletadas, das quais cerca de 7.000 eram credenciais de serviços de nuvem e cerca de 1.500 eram credenciais de contas de redes sociais.

O relatório atribui a causa não a uma vulnerabilidade do lado da nuvem, mas a erros de configuração das organizações vítimas, que haviam exposto seus arquivos .env por engano. Duas lições:

  • Arquivos .env expostos não são achados por acaso; programas os procuram em grande escala
  • Um único .env pode espalhar o dano para outros serviços, como armazenamento em nuvem e e-mail

Não presuma que um site pequeno não será encontrado. Um caso anonimizado do mesmo erro de posicionamento está em O .env exposto ao mundo inteiro.

Leia a seguir

Fontes

  • Documentação do Laravel, "Deployment" (aponte as requisições para public/index.php, nunca sirva a partir da raiz do projeto, APP_DEBUG=false em produção): laravel.com
  • Documentação do Laravel, "Configuration" (não commite o .env no controle de versão; Debug Mode): laravel.com
  • Documentação do Laravel, "Encryption" (rotação do APP_KEY e APP_PREVIOUS_KEYS): laravel.com
  • Apache HTTP Server 2.4, "core - AllowOverride" (o .htaccess não é lido quando está como None; o padrão é None): httpd.apache.org
  • Apache HTTP Server 2.4, ".htaccess files" (quando usar e ressalvas): httpd.apache.org
  • OWASP, "WSTG-CONF-04 Review Old Backup and Unreferenced Files for Sensitive Information": owasp.org
  • Unit 42 (Palo Alto Networks), "Leaked Environment Variables Allow Large-Scale Extortion Operation in Cloud Environments" (15 de agosto de 2024): unit42.paloaltonetworks.com

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 duradoura é reestruturar.

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

Uma blacklist exige uma nova regra para cada novo nome de arquivo, então algo sempre escapa. 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.

QComo verifico se o .env do meu site está visível de fora?
A

Contra um domínio que você possui, abra /.env e /.git/HEAD com curl ou no navegador e olhe tanto o status HTTP quanto o conteúdo. 403 ou 404 está OK por enquanto. Um 200 que mostra linhas de configuração como 'APP_KEY=' significa que está exposto. Se você roda vários apps em subdomínios ou subdiretórios, verifique cada um. Com SSH, procure também por .env ou .git na pasta pública do servidor.

QE se o servidor roda Nginx, ou o .htaccess não faz efeito?
A

O Nginx não lê .htaccess. No Apache, o .htaccess é ignorado quando AllowOverride está como None. Nesse caso, aponte o web root (root ou DocumentRoot) para o public/ do Laravel na configuração do servidor. O exemplo de Nginx na documentação oficial do Laravel também inclui uma regra que nega arquivos que começam com ponto (exceto .well-known).

QSe meu .env ficou exposto, basta apagar o arquivo?
A

Não. Se alguém baixou o arquivo enquanto estava exposto, as chaves continuam funcionando depois que você o apaga. Reemita chaves de API, segredos OAuth, senhas de banco e de e-mail e o APP_KEY, e verifique na atividade de cada serviço se há operações que você não reconhece. Se o .git também ficou exposto, trate como vazado qualquer segredo que já esteve em algum commit antigo.