Limite de upload: descubra qual camada barra o arquivo antes do seu código
Identifique pelo sintoma quem cortou o envio, Nginx, Apache, post_max_size ou upload_max_filesize, alinhe os tetos com a sua regra e devolva uma mensagem clara em vez de erro vazio.
O site da imobiliária diz "até 5 fotos de 8 MB cada". O corretor escolhe cinco fotos de 6 MB, clica em enviar e, depois de uma barra de progresso inteira, recebe uma página branca do Nginx com "413 Request Entity Too Large". Em outra máquina, com outra hospedagem, o mesmo envio volta para o formulário vazio, sem mensagem nenhuma. A resposta curta: o limite que vale é o menor de todas as camadas, e cada uma falha de um jeito. Descubra pelo sintoma qual cortou, deixe todos os tetos técnicos acima da sua regra de negócio e faça a recusa ser a sua, com mensagem clara.
Este guia é o diagnóstico camada por camada. A parte de onde gravar o arquivo e como validar o conteúdo está em upload em PHP: o limite não é só o do seu if e em aceitar o arquivo sem aceitar o que vem dentro.
Por que o seu if quase nunca é quem recusa
O arquivo atravessa várias camadas antes de o seu código rodar, e cada uma tem um teto próprio:
- CDN, WAF ou balanceador na frente do site, com limite de corpo por plano.
- Proxy ou ingress, como o ingress-nginx no Kubernetes, configurado pela anotação
nginx.ingress.kubernetes.io/proxy-body-size. - Servidor web: no Nginx,
client_max_body_size, cujo padrão é 1 MB; no Apache,LimitRequestBody. - PHP:
post_max_size, o teto da requisição inteira, com padrão de 8 MB;upload_max_filesize, o teto de cada arquivo, com padrão de 2 MB; emax_file_uploads, a quantidade de arquivos por envio. - A sua regra: os 8 MB por foto que a interface promete.
O pedido só chega ao seu código se passar por todas. Os padrões explicam o caso clássico de "a aplicação aceita 20 MB e ninguém passa de 2 MB": ninguém mexeu no upload_max_filesize. E repare que os limites são de coisas diferentes. Cinco fotos de 6 MB cabem na regra de 8 MB por foto, mas somam 30 MB numa requisição só, e é o teto da requisição que estoura.
Como saber quem barrou, pelo sintoma
- Página de erro 413 com a assinatura do servidor web, sem o visual do seu site: foi o Nginx, o Apache ou um proxy antes deles. O PHP nem rodou.
- Página de erro da CDN, com o nome do serviço: foi a borda.
$_POSTe$_FILESvazios, comCONTENT_LENGTHpreenchido: estourou opost_max_size. O PHP descarta o corpo inteiro e registra um aviso no log de erros dizendo que o Content-Length passou do limite.$_FILEScomerrorigual aUPLOAD_ERR_INI_SIZE: aquele arquivo passou doupload_max_filesize; os outros campos chegaram.- Só os primeiros arquivos chegam: bateu no
max_file_uploads. - A sua mensagem: tudo certo, quem recusou foi a regra de negócio.
Como medir e alinhar os tetos
- Calcule o pior caso da sua regra. Cinco fotos de 8 MB são 40 MB, mais os campos de texto e a sobra do formato multipart.
- Defina os tetos técnicos com folga acima disso: 10 MB por arquivo e 48 MB por requisição, por exemplo.
- Leia o que o PHP do site realmente usa, pela web, não pelo terminal. O PHP de linha de comando costuma ler outro
php.ini. - Ajuste o PHP, o servidor web e o proxy, de dentro para fora.
- Teste com arquivos logo abaixo e logo acima de cada limite e confira o sintoma de cada um.
- Trate no código os casos que chegam ao PHP, para devolver mensagem clara.
- Confira o tamanho no navegador antes de enviar, para poupar a espera de quem escolheu um arquivo grande demais.
Lendo os limites reais
<?php
// limites.php: apague depois de conferir
header('Content-Type: text/plain; charset=utf-8');
foreach (['upload_max_filesize', 'post_max_size', 'max_file_uploads', 'max_input_time'] as $chave) {
echo $chave . ' = ' . ini_get($chave) . PHP_EOL;
}
echo 'php.ini = ' . php_ini_loaded_file() . PHP_EOL;
Acessado pelo navegador, ele mostra os valores do PHP-FPM ou do módulo do Apache que atende o site, e qual arquivo de configuração foi carregado. Não deixe esse arquivo publicado.
Ajustando cada camada
; php.ini, ou .user.ini na pasta do site quando o PHP roda como FPM ou CGI
upload_max_filesize = 10M
post_max_size = 48M
max_file_uploads = 20
# Nginx, no bloco http, server ou location do upload
client_max_body_size 48m;
# Apache, em bytes
LimitRequestBody 50331648
No PHP, M significa 1.048.576 bytes, então 48M são 50.331.648 bytes, o mesmo valor do Apache. Esses dois limites do PHP não podem ser mudados com ini_set() dentro do script, porque o corpo da requisição já foi lido quando o seu código roda. O .user.ini vale para PHP como FPM ou CGI; com o módulo do Apache, o caminho é php_value no .htaccess. Depois de mudar o php.ini principal, reinicie o PHP-FPM; depois do Nginx, rode nginx -t e recarregue. Em hospedagem compartilhada, algumas dessas camadas não são suas, e o número da hospedagem é o teto.
Testando com arquivos de tamanho conhecido
head -c 9M /dev/urandom > foto-9mb.bin
head -c 11M /dev/urandom > foto-11mb.bin
curl -i -F "fotos[]=@foto-9mb.bin" https://homolog.imobiliaria.exemplo/anuncios/123/fotos
curl -i -F "fotos[]=@foto-11mb.bin" https://homolog.imobiliaria.exemplo/anuncios/123/fotos
O primeiro deve chegar ao seu código e ser recusado pela regra de 8 MB, com a sua mensagem. O segundo deve bater no upload_max_filesize e também receber uma mensagem sua, porque o PHP ainda roda nesse caso. Repita com seis arquivos de 9 MB para ver o teto da requisição, e anote o que cada camada devolveu.
Tratando no código o que chega ao PHP
<?php
const LIMITE_FOTO = 8 * 1024 * 1024;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && empty($_POST) && empty($_FILES)
&& (int) ($_SERVER['CONTENT_LENGTH'] ?? 0) > 0) {
http_response_code(413);
exit('O envio passou do tamanho total permitido. Envie menos fotos de cada vez.');
}
$erros = [];
foreach ($_FILES['fotos']['error'] ?? [] as $i => $erro) {
$nome = $_FILES['fotos']['name'][$i];
if ($erro === UPLOAD_ERR_INI_SIZE || $erro === UPLOAD_ERR_FORM_SIZE) {
$erros[] = $nome . ': a foto passa de 8 MB.';
} elseif ($erro === UPLOAD_ERR_PARTIAL) {
$erros[] = $nome . ': o envio foi interrompido. Tente de novo.';
} elseif ($erro === UPLOAD_ERR_OK && $_FILES['fotos']['size'][$i] > LIMITE_FOTO) {
$erros[] = $nome . ': a foto passa de 8 MB.';
}
}
O primeiro bloco detecta o estouro do post_max_size, que de outra forma pareceria um formulário enviado em branco. O laço trata cada foto pelo código de erro que o PHP preencheu, e só então aplica a regra de 8 MB. Para a pessoa, as três situações dizem a mesma coisa útil: qual foto e qual é o limite.
Avisando antes de enviar
const campo = document.getElementById('fotos');
const LIMITE = 8 * 1024 * 1024;
campo.addEventListener('change', function () {
const grandes = Array.from(campo.files).filter(function (f) { return f.size > LIMITE; });
campo.setCustomValidity(grandes.length
? 'Estas fotos passam de 8 MB: ' + grandes.map(function (f) { return f.name; }).join(', ')
: '');
campo.reportValidity();
});
Isso só poupa espera; o servidor continua conferindo. Para a barra de progresso que acompanha o envio, veja barra de progresso do upload.
Erros comuns
- Aumentar só o
upload_max_filesize. O arquivo passa do teto por arquivo e esbarra nopost_max_size, que agora descarta o formulário inteiro sem aviso. - Conferir o limite com
php -ino terminal. O PHP de linha de comando lê outro arquivo, e o número que você vê não é o do site. - Esquecer o Nginx ou o proxy. O PHP aceita 48 MB, mas o padrão de 1 MB do
client_max_body_sizecorta antes. - Usar
ini_set()para subir o limite. Não tem efeito, porque o corpo já foi processado quando a linha roda. - Regra de negócio acima do teto técnico. A interface promete 10 MB, a camada corta em 8, e a pessoa vê uma página de erro sem explicação.
- Esquecer que vários arquivos somam. Cada foto cabe, o envio inteiro não.
Checklist
- Você sabe o pior caso da sua regra: quantidade vezes tamanho, mais os campos.
upload_max_filesizeepost_max_sizelidos pela web estão acima desse pior caso.client_max_body_size,LimitRequestBodye o proxy estão alinhados com opost_max_size.- O estouro do
post_max_sizedevolve uma mensagem, não um formulário em branco. - Cada código de erro do
$_FILESvira uma frase com o nome do arquivo. - O navegador avisa o arquivo grande antes de enviar.
- Você testou abaixo e acima de cada limite e anotou o resultado.
- O número escrito na interface é o número que de fato passa.
Envie hoje um arquivo de teste logo acima do limite que a sua interface promete. Quem respondeu: a sua mensagem, uma página 413 sem estilo ou um formulário vazio? E dessas camadas, qual você consegue ajustar e qual pertence à hospedagem?
Referência: http://manual.webhost.com.br/content/limite-de-upload