Programação Por Equipe DevBras

Capa do artigo: Configurar o PHP para produção: erro no log, upload e fuso sem susto

Configurar o PHP para produção: erro no log, upload e fuso sem susto

Descubra qual php.ini o servidor realmente carrega e ajuste três coisas: erros para o log, limites de upload coerentes com o servidor web e fuso horário explícito.

Você muda display_errors para Off no php.ini, reinicia, abre o site com um erro proposital e o caminho completo do arquivo continua aparecendo na tela. Não é bug do PHP: você editou o arquivo do terminal, e o site é servido pelo PHP-FPM, que lê outro. A resposta curta: primeiro descubra qual configuração cada modo do PHP carrega; depois ajuste três coisas, erros para o log em vez da tela, limites de upload que combinem com o servidor web e fuso horário explícito.

O exemplo usa um servidor Ubuntu com Nginx e PHP 8.3 no FPM, servindo uma loja chamada Loja Bras. Os nomes de pasta mudam um pouco entre distribuições, mas o raciocínio é o mesmo.

Por que existem vários php.ini

O PHP roda em modos diferentes, chamados SAPI: o terminal (CLI), o FPM que atende o Nginx, o módulo do Apache. Em instalações Debian e Ubuntu, cada um tem sua pasta, como /etc/php/8.3/cli e /etc/php/8.3/fpm, cada uma com seu php.ini e sua subpasta conf.d. Editar um não altera o outro.

Além do arquivo principal, o PHP lê todos os .ini da pasta conf.d em ordem alfabética, e o último valor lido vence. É por isso que um arquivo de extensão instalado depois pode desfazer o que você ajustou no php.ini. E o pool do FPM pode sobrescrever tudo com php_admin_value.

Como descobrir o que está valendo

  1. No terminal, rode php --ini para ver o arquivo do CLI.
  2. Para o site, crie um script temporário que imprime os valores efetivos, abra no navegador e apague em seguida.
  3. Compare os dois resultados antes de editar qualquer coisa.
$ php --ini
Configuration File (php.ini) Path: /etc/php/8.3/cli
Loaded Configuration File:         /etc/php/8.3/cli/php.ini
Scan for additional .ini files in: /etc/php/8.3/cli/conf.d
<?php
// conferir-ini.php: apague depois de usar
header('Content-Type: text/plain; charset=utf-8');
echo php_sapi_name(), PHP_EOL;
echo php_ini_loaded_file(), PHP_EOL;
foreach (['display_errors', 'log_errors', 'error_log', 'upload_max_filesize',
          'post_max_size', 'memory_limit', 'date.timezone'] as $chave) {
    echo $chave, ' = ', var_export(ini_get($chave), true), PHP_EOL;
}

Se a primeira linha disser fpm-fcgi e o arquivo for /etc/php/8.3/fpm/php.ini, é nele (ou na pasta conf.d ao lado) que você mexe. Não deixe esse script no ar: ele entrega detalhes do servidor, assim como um phpinfo() esquecido.

Os três ajustes, num arquivo só

Em vez de editar o php.ini principal, que o gerenciador de pacotes pode querer substituir numa atualização, crie um arquivo seu na pasta conf.d com prefixo alto, para ser lido por último:

; /etc/php/8.3/fpm/conf.d/99-loja.ini
; Erros: tudo registrado, nada na tela
display_errors = Off
display_startup_errors = Off
log_errors = On
error_reporting = E_ALL
error_log = /var/log/php/loja-erros.log

; Upload: o POST inteiro precisa caber
upload_max_filesize = 20M
post_max_size = 25M
memory_limit = 256M

; Relógio
date.timezone = America/Sao_Paulo

; Não anunciar a versão no cabeçalho
expose_php = Off
sudo mkdir -p /var/log/php
sudo chown www-data:www-data /var/log/php
sudo systemctl restart php8.3-fpm

Erro para o log, não para a página

Em produção, display_errors desligado e log_errors ligado. O error_reporting = E_ALL continua: você quer registrar tudo, só não quer mostrar. O arquivo de log fica fora da pasta pública e precisa ter dono com escrita para o usuário do FPM, senão o PHP não consegue gravar e você perde a pista sem perceber. Em desenvolvimento, o contrário: exibição ligada para ver o problema na hora. Os riscos do erro exposto estão em mostrar o erro do PHP na página entrega o caminho do servidor.

Upload: o menor limite vence

Um arquivo de 18 MB passa por três portões. O Nginx recusa o corpo inteiro se ele passar de client_max_body_size, que por padrão é 1 MB, e devolve 413. O PHP descarta o POST inteiro se ele passar de post_max_size: $_POST e $_FILES chegam vazios, sem erro explícito. E rejeita o arquivo individual acima de upload_max_filesize, marcando UPLOAD_ERR_INI_SIZE no campo error de $_FILES. Por isso o post_max_size fica um pouco acima do limite de arquivo, para caber os outros campos do formulário. No Nginx da Loja Bras:

server {
    client_max_body_size 25m;
    # ...
}

A conta completa, com a regra do seu próprio código, está em limite de upload: o menor número entre o PHP, o servidor e a sua regra.

Fuso horário explícito

Sem date.timezone, o PHP usa UTC. Num servidor configurado em UTC, o pedido feito às 22h de Brasília nasce no dia seguinte, e o relatório de vendas do dia fecha errado. Defina o fuso na configuração e, de preferência, também no código com date_default_timezone_set, para que o comportamento não dependa do servidor. Detalhes e armadilhas em data e fuso no PHP.

No Docker e em hospedagem compartilhada

A imagem oficial php do Docker vem sem php.ini ativo; ela traz php.ini-production e php.ini-development na pasta indicada pela variável PHP_INI_DIR. Escolha um e acrescente o seu arquivo:

FROM php:8.3-fpm
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY 99-loja.ini "$PHP_INI_DIR/conf.d/"

Em hospedagem compartilhada sem acesso ao servidor, com PHP rodando como FPM ou CGI, um arquivo .user.ini na raiz do site aceita a maioria dessas diretivas. Com o PHP como módulo do Apache, o equivalente é php_value no .htaccess. Em ambos, confira com o script de antes se o valor pegou.

Erros comuns

  • Editar o php.ini do CLI achando que é o do site. Nada muda no navegador e você conclui que a diretiva "não funciona".
  • Esquecer de reiniciar o FPM. O processo continua com a configuração antiga até ser recarregado.
  • Desligar o erro na tela sem ligar o log. O sintoma some, e quando a loja quebrar você não terá nenhum registro do motivo.
  • Apontar error_log para uma pasta sem permissão de escrita. O PHP falha em silêncio ao gravar e o arquivo fica vazio.
  • Subir só upload_max_filesize. O post_max_size ou o Nginx continuam menores e o upload falha com formulário vazio.
  • Depender do fuso padrão da imagem. Na troca de servidor ou de imagem, as datas mudam de dia sem nenhuma alteração no código.

Checklist

  • Você sabe qual php.ini e quais arquivos de conf.d o FPM carrega.
  • Seus ajustes ficam num arquivo próprio, com prefixo alto, versionado junto com o projeto.
  • display_errors está Off e log_errors está On em produção.
  • O arquivo de log existe, está fora da pasta pública e recebe escrita.
  • post_max_size é maior que upload_max_filesize, e o servidor web aceita pelo menos o mesmo tamanho.
  • date.timezone está definido explicitamente.
  • O script de conferência foi apagado depois do uso.

Provoque agora um erro controlado no seu ambiente de produção, por exemplo chamando uma função inexistente numa página de teste protegida. O erro apareceu na tela, ou apareceu no log, e você sabe dizer em qual arquivo ele foi parar?

Avalie este artigo

Referência: http://blog.thiagobelem.net/configurando-o-php/