Como Fazer Upload Multipart para Amazon S3 com Presigned URLs usando Django (Guia Completo)
Aprenda a implementar upload multipart para Amazon S3 utilizando Django, Presigned URLs e boto3. Faça uploads de arquivos grandes com segurança, alta performance e sem sobrecarregar seu backend.

Como Fazer Upload Multipart para Amazon S3 com Django (Guia Completo)
Se sua aplicação permite que usuários enviem vídeos, backups, documentos ou qualquer arquivo grande, provavelmente você já enfrentou algum destes problemas:
uploads lentos;
timeout do servidor;
alto consumo de memória;
backend sobrecarregado;
uploads que precisam começar do zero quando a internet cai.
A boa notícia é que a Amazon S3 possui uma solução exatamente para isso: Multipart Upload.
Neste tutorial você aprenderá como implementar esse recurso utilizando Django, Amazon S3, Presigned URLs e boto3, seguindo uma arquitetura utilizada em aplicações de produção.
O que você vai construir
Ao final deste tutorial, o fluxo da aplicação será o seguinte:
Usuário
│
│ Seleciona um arquivo
▼
Frontend (React/Vue/JS)
│
│ POST /uploads/start
▼
Django REST API
│
│ CreateMultipartUpload()
▼
Amazon S3
▲
│
retorna UploadId
│
▼
Frontend solicita
Presigned URLs para
cada parte do arquivo
│
▼
Upload paralelo das partes
diretamente ao S3
│
▼
CompleteMultipartUpload
│
▼
Arquivo FinalPerceba um detalhe importante:
O arquivo nunca passa pelo backend.
O Django apenas coordena o processo.
Isso reduz drasticamente:
uso de CPU;
consumo de memória;
tráfego no servidor;
custo de infraestrutura;
tempo de resposta.
É exatamente essa arquitetura que serviços como Dropbox, Google Drive e diversos sistemas corporativos utilizam.
Como funciona o Multipart Upload?
Antes de escrever qualquer linha de código, vale entender o problema que estamos resolvendo.
Imagine um vídeo de 8 GB.
No upload tradicional, o navegador tenta enviar o arquivo inteiro em uma única requisição.
Cliente
│
│────────────── 8 GB ───────────────►
│
ServidorSe a conexão cair aos 7,9 GB, todo o processo precisa começar novamente.
Além disso:
um único request fica aberto por vários minutos;
proxies podem encerrar a conexão;
load balancers possuem timeout;
o servidor precisa manter recursos alocados durante todo o upload.
Agora veja o Multipart Upload.
O mesmo arquivo é dividido em pequenos blocos.
Parte 1 25 MB
Parte 2 25 MB
Parte 3 25 MB
...
Parte 320 25 MBCada parte é enviada independentemente.
Cliente
├────────► Parte 1
├────────► Parte 2
├────────► Parte 3
├────────► Parte 4
├────────► Parte 5Depois que todas chegam ao S3, a própria AWS monta o arquivo final.
Isso traz diversas vantagens.
Upload paralelo
É possível enviar várias partes simultaneamente.
Thread 1 → Parte 1
Thread 2 → Parte 2
Thread 3 → Parte 3
Thread 4 → Parte 4Em conexões rápidas isso pode reduzir bastante o tempo total.
Retomada de upload
Suponha que a parte 18 falhou.
Não é necessário reenviar tudo.
Apenas:
Parte 18Esse é um dos maiores benefícios do Multipart Upload.
Menor uso do servidor
No modelo tradicional:
Cliente
↓
Backend
↓
S3Todo o tráfego passa pelo servidor.
No modelo utilizando Presigned URLs:
Cliente
↓
S3O backend apenas gera URLs temporárias.
Na prática, ele troca dezenas de gigabytes de tráfego por algumas chamadas de API com poucos kilobytes.
Como as Presigned URLs entram nessa história?
Essa é uma dúvida comum.
Se o navegador envia o arquivo diretamente para o S3, como impedir que qualquer pessoa faça upload no bucket?
A resposta é: Presigned URLs.
O backend gera uma URL assinada digitalmente.
Exemplo simplificado:
https://bucket.s3.amazonaws.com/video.mp4?
X-Amz-Algorithm=...
&X-Amz-Credential=...
&X-Amz-Date=...
&X-Amz-Signature=...Essa URL possui características importantes:
validade de alguns minutos;
acesso limitado a uma operação específica;
permite enviar apenas uma parte do arquivo;
não expõe as credenciais da AWS.
Ou seja:
O frontend nunca conhece a Access Key nem a Secret Key da sua conta.
Fluxo completo da aplicação
Antes de programarmos, é importante visualizar todas as etapas.
Etapa 1
O usuário escolhe um arquivo.
video.mp4
4.2 GBEtapa 2
O frontend chama:
POST /uploads/startNosso backend executará:
s3.create_multipart_upload(...)Resposta:
{
"upload_id": "abcd1234",
"key": "videos/video.mp4"
}O UploadId identifica aquele upload e será usado em todas as próximas etapas.
Etapa 3
Agora precisamos gerar URLs para cada parte.
Supondo um arquivo de 100 MB dividido em partes de 25 MB:
Parte 1
Parte 2
Parte 3
Parte 4O frontend solicita:
POST /uploads/presigned-urlResposta:
{
"part_number": 1,
"url": "https://..."
}Depois repete o processo para as demais partes.
Etapa 4
Agora começa o upload real.
Cada parte é enviada diretamente ao S3.
Frontend
↓
PUT URL Parte 1
↓
Amazon S3Nenhum byte passa pelo Django.
Etapa 5
Após cada upload, o S3 devolve um cabeçalho chamado ETag.
Exemplo:
ETag
"93f4ab90ef..."Guarde esse valor.
Ele será necessário para finalizar o upload.
Etapa 6
Depois que todas as partes forem enviadas, o frontend chama:
POST /uploads/completeEnviando algo semelhante a:
{
"upload_id": "...",
"parts": [
{
"PartNumber": 1,
"ETag": "..."
},
{
"PartNumber": 2,
"ETag": "..."
}
]
}Nesse momento, o backend executa:
complete_multipart_upload()A AWS então une todas as partes e cria o arquivo final.
Para o usuário, o upload está concluído.
Pré-requisitos
Antes de começarmos a implementação, certifique-se de ter:
Python 3.12 ou superior
Django 5
Django REST Framework
Conta na AWS
Bucket S3 criado
Usuário IAM com permissões para Multipart Upload
AWS CLI configurada (opcional, mas recomendada)
Conhecimentos básicos de APIs REST
Criando o Bucket S3Se você já possui um bucket, pode utilizá-lo. Caso contrário, vamos criar um.
Acesse o console da AWS e navegue até o serviço Amazon S3.
Clique em Create Bucket.
Escolha um nome globalmente único. Por exemplo:
mycompany-uploadsou
storage-blog-demoEscolha a região mais próxima dos seus usuários.
Por exemplo:
us-east-1
sa-east-1
eu-west-1
Dica: depois de colocar uma aplicação em produção, evite trocar de região. Isso pode aumentar a latência e complicar migrações futuras.
Bloqueio de acesso público
Na maioria dos casos, mantenha o Block Public Access ativado.
Isso significa que ninguém poderá acessar arquivos apenas conhecendo a URL.
Mais tarde, você poderá disponibilizar downloads utilizando Presigned URLs ou, se necessário, configurar uma CDN como o CloudFront.
Para aplicações corporativas, essa é a opção mais segura.
Criando um usuário IAM
Uma prática comum entre iniciantes é utilizar a conta raiz da AWS na aplicação.
Nunca faça isso.
Em vez disso:
AWS Console →
IAM →
Users →
Create User
Nome:
django-storageAgora conceda apenas as permissões necessárias.
Política mínima
Em muitos tutoriais você verá algo parecido com:
{
"Action": "s3:*",
"Resource": "*"
}Isso funciona.
Também é uma péssima ideia.
Estamos dando acesso completo a todos os buckets da conta.
O ideal é conceder apenas as permissões necessárias.
Exemplo:
{
"Version":"2012-10-17",
"Statement":[
{
"Effect":"Allow",
"Action":[
"s3:CreateMultipartUpload",
"s3:UploadPart",
"s3:CompleteMultipartUpload",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts",
"s3:PutObject"
],
"Resource":"arn:aws:s3:::storage-blog-demo/*"
}
]
}Observe que restringimos o acesso apenas ao bucket da aplicação.
Esse é um princípio importante de segurança chamado Princípio do Menor Privilégio (Least Privilege): conceda somente as permissões estritamente necessárias.
Obtendo as credenciais
Após criar o usuário, gere uma Access Key.
Você receberá:
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEYGuarde essas informações em um local seguro.
Nunca publique essas chaves no GitHub.
Configurando as variáveis de ambiente
Crie um arquivo chamado .env.
AWS_ACCESS_KEY_ID=xxxxxxxx
AWS_SECRET_ACCESS_KEY=xxxxxxxx
AWS_STORAGE_BUCKET_NAME=storage-blog-demo
AWS_REGION=sa-east-1Perceba que não colocamos essas informações no código.
Isso permite utilizar credenciais diferentes em desenvolvimento, homologação e produção.
Carregando o .env
No settings.py:
from dotenv import load_dotenv
import os
load_dotenv()Agora podemos acessar qualquer variável:
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
AWS_STORAGE_BUCKET_NAME = os.getenv("AWS_STORAGE_BUCKET_NAME")
AWS_REGION = os.getenv("AWS_REGION")Criando o cliente S3
Em uploads/services/s3.py:
import boto3
from django.conf import settings
s3_client = boto3.client(
"s3",
region_name=settings.AWS_REGION,
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
)Separar essa lógica em um serviço facilita testes, reutilização e manutenção.
Criando o endpoint de início do upload
Vamos implementar a primeira etapa do fluxo:
Frontend
↓
POST /uploads/start
↓
AWS CreateMultipartUpload
↓
UploadIdCrie uploads/views.py:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .services.s3 import s3_client
from django.conf import settings
class StartMultipartUploadAPIView(APIView):
def post(self, request):
filename = request.data["filename"]
response = s3_client.create_multipart_upload(
Bucket=settings.AWS_STORAGE_BUCKET_NAME,
Key=filename,
)
return Response(
{
"upload_id": response["UploadId"],
"key": response["Key"],
},
status=status.HTTP_201_CREATED,
)Esse endpoint faz apenas uma chamada ao S3 para iniciar o upload multipart.
A resposta será semelhante a:
{
"upload_id": "2~HjFk93L...",
"key": "video.mp4"
}O UploadId identifica de forma única aquela sessão de upload e será utilizado em todas as etapas seguintes.
Registrando a rota
Em uploads/urls.py:
from django.urls import path
from .views import StartMultipartUploadAPIView
urlpatterns = [
path(
"start/",
StartMultipartUploadAPIView.as_view(),
name="multipart-start",
),
]Depois, inclua essas rotas no arquivo principal do projeto:
from django.urls import include, path
urlpatterns = [
path("api/uploads/", include("uploads.urls")),
]Agora você já consegue testar o primeiro endpoint.
Faça uma requisição:
POST /api/uploads/start
Content-Type: application/jsonCorpo:
{
"filename": "meu-video.mp4"
}Resposta esperada:
{
"upload_id": "2~HjFk93L...",
"key": "meu-video.mp4"
}🎉 Parabéns! Seu backend já consegue iniciar um upload multipart na AWS.

