Pasta Public do GLPI: Configuração Segura para Produção

Por que o DocumentRoot do GLPI 10 tem que apontar para a pasta public: o que fica exposto quando não aponta, virtual host endurecido no Apache e Nginx, script de verificação e o checklist de produção da sustentação NexTool.

Se o DocumentRoot do seu GLPI aponta para a raiz da instalação e não para a pasta public, o arquivo com a senha do seu banco de dados está a um GET de distância. Desde o GLPI 10 essa é a decisão de segurança mais importante do deploy - e a que mais encontramos errada em ambientes que chegam para sustentação, justamente porque o sistema funciona perfeitamente mesmo mal configurado.

Por que o webroot errado passa despercebido

O GLPI 10 continua abrindo e operando normalmente se você apontar o servidor web para a raiz (/var/www/glpi) em vez de /var/www/glpi/public. Nada quebra na tela, ninguém abre chamado, e por isso a falha sobrevive por anos. Na sustentação de parques GLPI de clientes, esse é o achado silencioso mais comum no onboarding: o ambiente responde bem, mas https://glpi.cliente.com/files/_log/php-errors.log devolve o log da aplicação como um download comum.

O detalhe que só aparece na operação: no Apache, a instalação antiga era parcialmente salva pelos arquivos .htaccess espalhados dentro de config/, files/ e afins, que negavam o acesso. Isso dá uma falsa sensação de proteção. No Nginx esses .htaccess simplesmente não existem - o Nginx nunca os lê. Então migrar de Apache para Nginx carregando o root errado transforma uma exposição parcial em exposição total: files/, config/ e todo o código-fonte viram conteúdo estático servido para qualquer um. Troca de servidor web é o momento clássico em que essa bomba estoura.

O que fica exposto quando o DocumentRoot está errado

A pasta public existe para publicar apenas o roteador (index.php), o .htaccess e os arquivos estáticos (CSS, JS, imagens). Todo o resto deve estar fora do alcance do navegador. A tabela abaixo é a matriz de verificação que usamos: com o public como webroot, todas as URLs abaixo devem retornar 404.

CaminhoConteúdoImpacto se expostoStatus esperado
/config/config_db.phpHost, usuário e senha do bancoVazamento total do banco de dados404
/files/_log/php-errors.log, sql-errors.logRecon da aplicação e vazamento de PII404
/files/_dumps/Dumps SQL gerados pelo GLPICópia do banco baixável404
/files/_sessions/Arquivos de sessão ativosSequestro de sessão e roubo de token CSRF404
/src/Código-fonte PHP da aplicaçãoMapa da aplicação para exploração404
/vendor/Dependências ComposerExploração de CVEs de bibliotecas404
/install/install.phpAssistente de instalaçãoReconfiguração e takeover do ambiente404 (remover após instalar)

Cuidado com o falso "seguro": com o PHP ativo, /config/config_db.php pode responder 200 com corpo vazio (o arquivo apenas define uma classe, não imprime nada). Um 200 vazio não significa que está tudo bem - significa que o arquivo está dentro do webroot. O que realmente vaza como download são os não-PHP: files/_log/*.log, files/_dumps/*.sql e qualquer backup esquecido na árvore. Por isso a régua é 404, não "página vazia".

Apache: virtual host endurecido

O DocumentRoot aponta para public, o listing de diretório é desligado e o HTTP puro é redirecionado para HTTPS. O FallbackResource substitui o roteamento do .htaccess sem exigir AllowOverride All:

# /etc/apache2/sites-available/glpi.conf
<VirtualHost *:80>
    ServerName glpi.suaempresa.com
    # Nada de GLPI em HTTP puro
    Redirect permanent / https://glpi.suaempresa.com/
</VirtualHost>

<VirtualHost *:443>
    ServerName glpi.suaempresa.com
    DocumentRoot /var/www/glpi/public

    <Directory /var/www/glpi/public>
        Require all granted
        Options -Indexes +FollowSymLinks
        # Roteia sem depender de AllowOverride All
        FallbackResource /index.php
    </Directory>

    # Nega qualquer arquivo oculto (.git, .env, .htaccess)
    <FilesMatch "^\.">
        Require all denied
    </FilesMatch>

    # Cabeçalhos de segurança
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    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"

    SSLEngine On
    SSLCertificateFile /etc/ssl/certs/glpi.crt
    SSLCertificateKeyFile /etc/ssl/private/glpi.key
</VirtualHost>

Nginx: sem a rede de segurança do .htaccess

No Nginx não há .htaccess para salvar você, então o root tem que ser public desde o início. Repare em duas linhas que quase sempre faltam: o try_files $uri =404; dentro do bloco PHP e o bloqueio explícito de arquivos ocultos.

# /etc/nginx/sites-available/glpi.conf
server {
    listen 80;
    server_name glpi.suaempresa.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name glpi.suaempresa.com;
    root /var/www/glpi/public;
    index index.php;

    ssl_certificate     /etc/ssl/certs/glpi.crt;
    ssl_certificate_key /etc/ssl/private/glpi.key;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ \.php$ {
        # =404 impede execução via PATH_INFO forjado
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Nginx não lê .htaccess: negue arquivos ocultos na mão
    location ~ /\. {
        deny all;
    }
}

O erro comum aqui é copiar um bloco PHP genérico da internet, sem o try_files $uri =404;. Sem ele, uma requisição como /uploads/foto.jpg/x.php pode ser passada ao FPM via PATH_INFO e executada, abrindo caminho para RCE se o atacante conseguir gravar um arquivo na árvore. Com o =404, o Nginx recusa antes de chegar ao PHP.

Verificação pós-deploy: o teste de 30 segundos

Depois de recarregar o servidor web, rode este script contra o domínio real. Toda linha precisa dizer OK. Qualquer FALHA é um caminho servido que não deveria existir:

#!/usr/bin/env bash
BASE="https://glpi.suaempresa.com"

# Todas as URLs abaixo DEVEM retornar 404 num webroot correto
for path in \
  /config/config_db.php \
  /src/ \
  /vendor/ \
  /files/_log/php-errors.log \
  /install/install.php ; do
  code=$(curl -sk -o /dev/null -w '%{http_code}' "${BASE}${path}")
  if [ "$code" = "404" ]; then
    echo "OK    ${path} -> ${code}"
  else
    echo "FALHA ${path} -> ${code} (EXPOSTO)"
  fi
done

Na sustentação nós fixamos esse script no checklist de onboarding de todo cliente novo e o rodamos de novo a cada troca de servidor web, upgrade ou mudança de proxy reverso - porque webroot correto não é estado permanente, é algo que uma migração desatenta reverte em segundos.

Checklist de produção

  1. DocumentRoot / root apontando para .../public, nunca para a raiz da instalação.
  2. Remover install/install.php assim que a instalação/atualização terminar (o próprio GLPI mostra um aviso vermelho enquanto o arquivo existe).
  3. Forçar HTTPS com redirect 301 do 80 e Strict-Transport-Security ligado.
  4. Endurecer opcional: mover config/ e files/ para fora da árvore web via GLPI_CONFIG_DIR e GLPI_VAR_DIR, deixando o webroot só com public.
  5. Rodar o script de verificação e exigir 404 em todos os caminhos.
  6. Conferir o painel de segurança do próprio GLPI (Configurar) depois de subir - ele sinaliza pendências como o install.php presente.

Ajustar o webroot leva dez minutos; descobrir que a senha do banco esteve baixável por três anos custa muito mais. Se você está subindo um GLPI novo, migrando de Apache para Nginx ou herdando um ambiente sem histórico de configuração, a equipe de sustentação GLPI da NexTool valida o webroot, endurece o virtual host e roda a bateria de verificação como parte do onboarding. Fale com a gente sobre sustentação de GLPI.


Este conteúdo foi produzido com auxílio de inteligência artificial e revisado pela equipe Nextool Solutions.

Perguntas Frequentes

Porque o roteamento do GLPI 10 não depende do webroot para carregar as telas: ele resolve os caminhos internamente. A aplicação abre e opera normal com o DocumentRoot na raiz da instalação, então nada quebra visualmente. O problema é silencioso: config/, files/ e o código-fonte passam a ser alcançáveis pelo navegador. Como não há sintoma, a falha sobrevive por anos até alguém testar as URLs certas.

Não. O .htaccess é um recurso exclusivo do Apache. O Nginx ignora completamente esses arquivos, inclusive os que o GLPI espalha dentro de config/ e files/ para negar acesso. Por isso uma migração de Apache para Nginx que herda o root errado é perigosa: a proteção parcial que o Apache dava desaparece e a exposição vira total. No Nginx, apontar o root para public é obrigatório, não opcional.

Sim. Enquanto o install.php existir, o próprio GLPI exibe um aviso de segurança em vermelho, e o arquivo permite reconfigurar o ambiente. Remova (rm public/install/install.php) ou renomeie logo após a instalação ou atualização. Vale lembrar: se o webroot estiver correto em public, o caminho /install/install.php já retorna 404 - mas remover o arquivo é a defesa em profundidade recomendada.

Sim, e é o endurecimento recomendado para ambientes sensíveis. O GLPI suporta as constantes GLPI_CONFIG_DIR e GLPI_VAR_DIR para apontar config/ e files/ para diretórios fora da árvore web (por exemplo /etc/glpi e /var/lib/glpi). Assim, mesmo que alguém erre o webroot no futuro, não há credencial nem log dentro do caminho servido pelo servidor web.

Faça um curl para URLs que nunca deveriam existir num webroot correto, como /config/config_db.php, /vendor/ e /files/_log/php-errors.log. Todas precisam retornar 404. Atenção ao falso positivo: config_db.php pode responder 200 com corpo vazio se o PHP executá-lo - isso já é sinal de webroot errado. A régua é 404, não página em branco.

Precisa de ajuda?