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
- No terminal, rode
php --inipara ver o arquivo do CLI. - Para o site, crie um script temporário que imprime os valores efetivos, abra no navegador e apague em seguida.
- 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.inido 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_logpara 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. Opost_max_sizeou 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.inie quais arquivos deconf.do FPM carrega. - Seus ajustes ficam num arquivo próprio, com prefixo alto, versionado junto com o projeto.
display_errorsestáOffelog_errorsestáOnem produção.- O arquivo de log existe, está fora da pasta pública e recebe escrita.
post_max_sizeé maior queupload_max_filesize, e o servidor web aceita pelo menos o mesmo tamanho.date.timezoneestá 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?
Referência: http://blog.thiagobelem.net/configurando-o-php/