Fazer upload de arquivos em PHP parece simples:
- Cria um formulário
- Usa
move_uploaded_file() - E pronto…
Na prática, é aí que começam os problemas:
- gente subindo arquivos gigantes
- formatos que você não quer aceitar
- nomes estranhos gerando conflito
- até risco de segurança se o upload for mal feito
Neste guia, vou te mostrar como fazer upload em PHP moderno, com:
- validação de tamanho
- validação de tipo
- nomes de arquivo seguros
- estrutura organizada
1. Estrutura base do projeto
Você pode usar uma estrutura simples assim:
meu_upload/
public/
index.php (formulário de upload)
processa_upload.php (tratamento do upload)
uploads/
(aqui vão os arquivos enviados)
A pasta
uploads/precisa ter permissão de escrita pelo servidor (no Linux, normalmente 755 ou 775, dependendo do ambiente).
2. Formulário de upload (HTML)
Arquivo: public/index.php
<?php
// Opcional: mostrar mensagem de sucesso/erro via GET
$mensagem = $_GET['msg'] ?? '';
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Upload de arquivo em PHP</title>
</head>
<body>
<h1>Upload de arquivo</h1>
<?php if ($mensagem !== ''): ?>
<p><strong><?= htmlspecialchars($mensagem, ENT_QUOTES, 'UTF-8'); ?></strong></p>
<?php endif; ?>
<form action="processa_upload.php" method="post" enctype="multipart/form-data">
<label for="arquivo">Escolha um arquivo (imagem JPG/PNG, até 2MB):</label><br><br>
<input type="file" name="arquivo" id="arquivo" required>
<br><br>
<button type="submit">Enviar</button>
</form>
</body>
</html>
Pontos importantes:
enctype="multipart/form-data"é obrigatório para upload funcionar.- Estamos limitando por texto a JPG/PNG até 2MB (vamos validar isso de verdade no PHP).
3. Tratando o upload com segurança
Arquivo: public/processa_upload.php
<?php
declare(strict_types=1);
// Pasta onde os arquivos serão salvos (fora de public seria ainda melhor)
$diretorioUploads = __DIR__ . '/../uploads';
// Cria a pasta se não existir
if (!is_dir($diretorioUploads)) {
mkdir($diretorioUploads, 0775, true);
}
// Verifica se o arquivo foi enviado
if (!isset($_FILES['arquivo'])) {
redirecionarComMensagem('Nenhum arquivo enviado.');
}
$arquivo = $_FILES['arquivo'];
// Verifica se houve erro no upload
if ($arquivo['error'] !== UPLOAD_ERR_OK) {
redirecionarComMensagem('Erro no upload. Código: ' . $arquivo['error']);
}
// Valida tamanho (exemplo: máximo 2MB)
$tamanhoMaximo = 2 * 1024 * 1024; // 2MB em bytes
if ($arquivo['size'] > $tamanhoMaximo) {
redirecionarComMensagem('Arquivo muito grande. Tamanho máximo: 2MB.');
}
// Valida extensão / tipo
$nomeOriginal = $arquivo['name'];
// Pega a extensão verdadeira do arquivo
$extensao = strtolower(pathinfo($nomeOriginal, PATHINFO_EXTENSION));
// Lista de extensões permitidas
$extensoesPermitidas = ['jpg', 'jpeg', 'png'];
if (!in_array($extensao, $extensoesPermitidas, true)) {
redirecionarComMensagem('Tipo de arquivo não permitido. Envie apenas JPG ou PNG.');
}
// (Opcional) Verificar MIME type de forma extra
$tipoMimePermitidos = ['image/jpeg', 'image/png'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$tipoMimeReal = $finfo->file($arquivo['tmp_name']);
if (!in_array($tipoMimeReal, $tipoMimePermitidos, true)) {
redirecionarComMensagem('O arquivo enviado não parece ser uma imagem válida.');
}
// Gera um novo nome único pro arquivo
$nomeSeguro = gerarNomeSeguro($extensao);
// Caminho final
$caminhoDestino = $diretorioUploads . DIRECTORY_SEPARATOR . $nomeSeguro;
// Move o arquivo temporário para o destino final
if (!move_uploaded_file($arquivo['tmp_name'], $caminhoDestino)) {
redirecionarComMensagem('Falha ao salvar o arquivo no servidor.');
}
// Sucesso
redirecionarComMensagem('Upload realizado com sucesso!');
/**
* Gera um nome único e seguro para o arquivo.
*/
function gerarNomeSeguro(string $extensao): string
{
// uniqid + random_bytes deixam o nome pouco previsível
$parteAleatoria = bin2hex(random_bytes(16));
return $parteAleatoria . '.' . $extensao;
}
/**
* Redireciona de volta para o index com mensagem.
*/
function redirecionarComMensagem(string $mensagem): void
{
$mensagem = urlencode($mensagem);
header("Location: index.php?msg={$mensagem}");
exit;
}
O que estamos fazendo de certo aqui?
- Checando erro do upload (
$arquivo['error']) - Definindo um tamanho máximo (2MB) e validando em bytes
- Bloqueando extensões indesejadas (por exemplo
.php,.exeetc.) - Confirmando o MIME type real do arquivo com
finfo - Gerando um novo nome aleatório (nada de confiar no nome original do usuário)
- Usando
move_uploaded_file()com caminho calculado de forma segura
Isso já está muito melhor do que:
move_uploaded_file($_FILES['arquivo']['tmp_name'], 'uploads/' . $_FILES['arquivo']['name']);
que é o que a maioria faz e abre várias portas pra problema.
4. Melhorando ainda mais: proteção de acesso à pasta de upload
Idealmente, arquivos de upload não deveriam ficar dentro de public/ (raiz web), para evitar que alguém acesse diretamente um .php malicioso que foi enviado por engano.
Você já colocou a pasta uploads/ fora de public:
meu_upload/
public/
(arquivos acessados pela web)
uploads/
(arquivos fora do alcance direto da URL)
Se por algum motivo você precisar que uploads/ fique dentro de public/, é interessante:
- bloquear execução de
.phpnessa pasta - ou configurar servidor (Apache/nginx) pra tratar ali só como arquivos estáticos.
Mas, pra um cenário simples, só de colocar fora de public/ já é um ótimo avanço.
5. Como exibir as imagens enviadas
Se você salvou o nome do arquivo no banco, ou quer listar os arquivos de uploads/, pode criar algo tipo:
<?php
$diretorioUploads = __DIR__ . '/../uploads';
$arquivos = array_diff(scandir($diretorioUploads), ['.', '..']);
?>
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Imagens enviadas</title>
</head>
<body>
<h1>Imagens enviadas</h1>
<?php foreach ($arquivos as $arquivo): ?>
<div style="margin-bottom: 10px;">
<img src="../uploads/<?= htmlspecialchars($arquivo, ENT_QUOTES, 'UTF-8'); ?>"
alt="" style="max-width: 200px;">
</div>
<?php endforeach; ?>
</body>
</html>
Isso funciona se a pasta
uploads/estiver acessível pela URL.
Se ela estiver fora da pasta pública, você precisa criar um script que lê o arquivo e envia o conteúdo (tipo um “proxy de imagens”).
6. Resumindo: o fluxo de um upload moderno e seguro em PHP
- Criar um formulário com
enctype="multipart/form-data". - Conferir se
$_FILES['arquivo']existe e se não teve erro (UPLOAD_ERR_OK). - Validar:
- tamanho
- extensão
- tipo MIME real
- Gerar um nome de arquivo seguro e único (nada de usar o nome original cru).
- Salvar em uma pasta específica (
uploads/), de preferência fora da raiz pública. - Usar
htmlspecialchars()ao exibir os nomes em HTML.
Com esse padrão, você já foge de:
- upload de script malicioso sendo executado
- arquivos gigantes travando o servidor
- extensões estranhas
- bagunça de arquivos com o mesmo nome se sobrescrevendo