Upload seguro de imagens em PHP (passo a passo moderno)

Permitir que o usuário envie arquivos pelo site parece simples, mas é uma das áreas mais delicadas em segurança.
Se você não cuidar direito, alguém pode tentar enviar:

  • Arquivos enormes que lotam seu servidor
  • Scripts maliciosos disfarçados de imagem
  • Extensões falsas só pra explorar alguma falha do PHP/servidor

Neste guia você vai aprender a fazer upload de imagens de forma segura, usando:

  • $_FILES de forma correta
  • Validação de tamanho
  • Validação de tipo MIME (e não só extensão)
  • Renomeação do arquivo para evitar conflitos
  • Pasta organizada para uploads

Objetivo: no final você terá um formulário + script PHP que aceita somente imagens válidas, com limite de tamanho e nome seguro.


1. Planejando o upload: o que vamos controlar

Antes de escrever qualquer linha de código, defina algumas regras:

  1. Quais tipos de arquivo vou permitir?
    Ex.: apenas jpg, jpeg, png e webp.
  2. Qual será o tamanho máximo?
    Ex.: no máximo 2 MB por imagem.
  3. Onde esses arquivos vão ficar?
    Ex.: uma pasta uploads/ fora da raiz pública, ou pelo menos bem organizada.
  4. Como vou renomear o arquivo?
    Nunca confie no nome que o usuário mandou.
    Use algo como uniqid() + extensão.

Com essas regras em mente, vamos começar.


2. Criando a pasta de uploads

No seu projeto, crie uma pasta chamada uploads (pode ser na raiz ou dentro de uma pasta específica).
Exemplo:

/meu-projeto
  upload.php
  uploads/

No servidor, garanta que a pasta uploads tenha permissão de escrita (por exemplo, 755 ou 775, dependendo da hospedagem).

Se quiser reforçar a segurança, você pode adicionar um .htaccess dentro da pasta uploads impedindo a execução de scripts, por exemplo:

# uploads/.htaccess
php_flag engine off
Options -ExecCGI

Isso ajuda a impedir que algum arquivo .php enviado ali seja executado.


3. Formulário HTML para envio da imagem

Crie um arquivo upload.php com o formulário e também com o processamento (vamos fazer tudo em um só para ficar didático).

Primeiro, a estrutura HTML e o formulário:

<?php
// upload.php
?>
<!doctype html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <title>Upload seguro de imagem em PHP</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        form { max-width: 400px; }
        label { display: block; margin-bottom: 8px; }
        input[type="file"] { margin-bottom: 10px; }
        button { padding: 8px 12px; }
        .mensagem { margin-top: 15px; padding: 10px; border-radius: 4px; }
        .sucesso { background: #dcfce7; color: #166534; }
        .erro { background: #fee2e2; color: #b91c1c; }
    </style>
</head>
<body>

<h1>Upload seguro de imagem</h1>

<form method="post" enctype="multipart/form-data">
    <label for="imagem">Selecione uma imagem (JPG, PNG ou WEBP):</label>
    <input type="file" name="imagem" id="imagem" accept=".jpg,.jpeg,.png,.webp" required>
    <button type="submit">Enviar</button>
</form>

</body>
</html>

Pontos importantes:

  • enctype="multipart/form-data" é obrigatório para upload de arquivos.
  • input type="file" com accept apenas ajuda o usuário, mas não é segurança.
  • A segurança de verdade será feita no PHP.

Agora vamos adicionar o código PHP para validar e salvar a imagem.


4. Processando o upload com validações

Vamos colocar o código PHP antes do HTML, no mesmo upload.php, para que ele processe o formulário e depois exiba o resultado.

<?php
// upload.php

// Tamanho máximo em bytes (2 MB)
$tamanhoMaximo = 2 * 1024 * 1024;

// Extensões permitidas
$extensoesPermitidas = ['jpg', 'jpeg', 'png', 'webp'];

// Tipos MIME permitidos
$tiposMimePermitidos = ['image/jpeg', 'image/png', 'image/webp'];

$mensagem = '';
$classeMensagem = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    if (!isset($_FILES['imagem']) || $_FILES['imagem']['error'] !== UPLOAD_ERR_OK) {
        $mensagem = 'Erro no upload da imagem. Tente novamente.';
        $classeMensagem = 'erro';
    } else {

        $arquivo = $_FILES['imagem'];

        $nomeOriginal      = $arquivo['name'];
        $tipoMimeEnviado   = $arquivo['type'];        // não confiável sozinho
        $nomeTemporario    = $arquivo['tmp_name'];
        $tamanhoArquivo    = $arquivo['size'];

        // 1) Verificar tamanho
        if ($tamanhoArquivo > $tamanhoMaximo) {
            $mensagem = 'Arquivo muito grande. O limite é de 2 MB.';
            $classeMensagem = 'erro';
        } else {

            // 2) Verificar extensão pela string
            $extensao = strtolower(pathinfo($nomeOriginal, PATHINFO_EXTENSION));

            if (!in_array($extensao, $extensoesPermitidas)) {
                $mensagem = 'Tipo de arquivo não permitido. Envie apenas JPG, PNG ou WEBP.';
                $classeMensagem = 'erro';
            } else {

                // 3) Verificar tipo MIME real usando finfo
                $infoArquivo = finfo_open(FILEINFO_MIME_TYPE);
                $tipoMimeReal = finfo_file($infoArquivo, $nomeTemporario);
                finfo_close($infoArquivo);

                if (!in_array($tipoMimeReal, $tiposMimePermitidos)) {
                    $mensagem = 'O arquivo enviado não parece ser uma imagem válida.';
                    $classeMensagem = 'erro';
                } else {

                    // 4) Gerar nome seguro e único
                    $nomeSeguro = uniqid('img_', true) . '.' . $extensao;

                    // 5) Definir pasta de destino
                    $diretorioUploads = __DIR__ . '/uploads';

                    if (!is_dir($diretorioUploads)) {
                        mkdir($diretorioUploads, 0755, true);
                    }

                    $caminhoDestino = $diretorioUploads . '/' . $nomeSeguro;

                    // 6) Mover o arquivo
                    if (move_uploaded_file($nomeTemporario, $caminhoDestino)) {
                        $mensagem = 'Upload realizado com sucesso!';
                        $classeMensagem = 'sucesso';

                        // Caminho relativo para exibir a imagem no HTML
                        $urlImagem = 'uploads/' . $nomeSeguro;
                    } else {
                        $mensagem = 'Falha ao mover o arquivo enviado.';
                        $classeMensagem = 'erro';
                    }
                }
            }
        }
    }
}
?>
<!doctype html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <title>Upload seguro de imagem em PHP</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        form { max-width: 400px; }
        label { display: block; margin-bottom: 8px; }
        input[type="file"] { margin-bottom: 10px; }
        button { padding: 8px 12px; }
        .mensagem { margin-top: 15px; padding: 10px; border-radius: 4px; }
        .sucesso { background: #dcfce7; color: #166534; }
        .erro { background: #fee2e2; color: #b91c1c; }
        img.preview { margin-top: 15px; max-width: 300px; display: block; }
    </style>
</head>
<body>

<h1>Upload seguro de imagem</h1>

<form method="post" enctype="multipart/form-data">
    <label for="imagem">Selecione uma imagem (JPG, PNG ou WEBP):</label>
    <input type="file" name="imagem" id="imagem" accept=".jpg,.jpeg,.png,.webp" required>
    <button type="submit">Enviar</button>
</form>

<?php if (!empty($mensagem)): ?>
    <div class="mensagem <?= htmlspecialchars($classeMensagem) ?>">
        <?= htmlspecialchars($mensagem) ?>
    </div>

    <?php if (isset($urlImagem) && $classeMensagem === 'sucesso'): ?>
        <img src="<?= htmlspecialchars($urlImagem) ?>" alt="Imagem enviada" class="preview">
    <?php endif; ?>
<?php endif; ?>

</body>
</html>

5. O que esse código está fazendo de importante

  1. Verifica se houve erro no upload
    Usa $_FILES['imagem']['error'] para garantir que o arquivo chegou sem problemas.
  2. Valida o tamanho
    Compara $_FILES['imagem']['size'] com o limite de 2 MB.
  3. Confere a extensão
    Usa pathinfo() para pegar a extensão em letras minúsculas e compara com uma lista permitida.
  4. Valida o tipo MIME real
    Usa a função finfo_file() para checar o tipo real do arquivo e não apenas confiar na extensão.
  5. Gera um nome seguro
    Usa uniqid('img_', true) para gerar um nome único, evitando sobrescrever arquivos e sem acentos/espaços.
  6. Cria a pasta se não existir
    mkdir($diretorioUploads, 0755, true) garante que a pasta exista.
  7. Move o arquivo de forma segura
    Usa move_uploaded_file(), que é a forma correta de mover o arquivo recebido via upload.
  8. Não exibe detalhes sensíveis de erro
    Para o usuário, mensagens simples. Para log mais detalhado, você pode registrar os erros em arquivo.

6. Cuidados extras (boa prática)

Alguns cuidados adicionais que valem muito a pena:

  • Limitar upload apenas para usuários logados, se for uma área administrativa.
  • Colocar a pasta de uploads fora da raiz pública e servir os arquivos usando um script PHP (com autenticação).
  • Habilitar no servidor alguma proteção para impedir a execução de scripts em pasta de upload (.htaccess, diretivas do Nginx etc.).
  • Monitorar o tamanho total ocupado pela pasta uploads (principalmente em hospedagens mais simples).

7. Conclusão

Com esse modelo de upload, você:

  • Aceita apenas imagens reais, verificando extensão e tipo MIME;
  • Tem limite de tamanho configurado;
  • Evita sobrescrever arquivos com nomes iguais;
  • Organiza tudo em uma pasta específica;
  • Deixa o caminho pronto para integrar com cadastro de produtos, usuários, perfis etc.

Esse tipo de conteúdo é ótimo pro seu blog porque:

  • Resolve uma dúvida muito comum;
  • Dá código pronto;
  • É fácil de você gravar vídeo/short explicando o mesmo passo a passo.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Rolar para cima