ftplib no Python: FTP e FTPS seguros

Publicado em: 21/08/2026
Tempo de leitura: 5 minutos
A developer typing code on a laptop with a Python book beside in an office.

O módulo ftplib implementa o lado cliente do protocolo FTP na biblioteca padrão do Python. Ele permite listar diretórios, baixar, enviar, renomear e excluir arquivos em servidores compatíveis. Ainda é usado em integrações legadas, hospedagens, equipamentos e fluxos empresariais de troca de arquivos.

FTP simples transmite credenciais e dados sem criptografia. Em redes não confiáveis, prefira FTPS com FTP_TLS ou, quando o servidor exigir SFTP, use uma biblioteca própria para SSH — SFTP não é FTP e não é implementado por ftplib. Este guia prioriza FTPS, timeouts, limites e validação.

Conexão FTP básica

from ftplib import FTP

with FTP("ftp.example.com", timeout=15, encoding="utf-8") as ftp:
    ftp.login("usuario", "senha")
    print(ftp.pwd())
    print(ftp.nlst())

Esse exemplo usa FTP sem criptografia e serve apenas para rede controlada ou servidor anônimo. Nunca envie senha real por FTP através da internet.

FTPS com FTP_TLS

import ssl
from ftplib import FTP_TLS

contexto = ssl.create_default_context()
contexto.minimum_version = ssl.TLSVersion.TLSv1_2

with FTP_TLS(
    "ftp.example.com",
    timeout=15,
    context=contexto,
    encoding="utf-8",
) as ftps:
    ftps.login("usuario", "senha")
    ftps.prot_p()
    print(list(ftps.mlsd()))

FTP_TLS protege o canal de controle, mas o canal de dados só fica privado depois de prot_p(). Sem essa chamada, listagens e arquivos podem trafegar sem a proteção esperada.

O contexto padrão valida certificado e hostname. Não use contexto não verificado como correção para certificados expirados ou nomes incorretos. Consulte ssl no Python.

Credenciais

Não coloque usuário e senha no código-fonte. Carregue-os de variáveis de ambiente ou gerenciador de segredos. Arquivos .netrc podem ajudar em ferramentas locais, mas também contêm segredos e precisam de permissões restritas. O guia de netrc no Python mostra cuidados importantes.

Não habilite debug de protocolo em produção com credenciais reais. set_debuglevel(2) registra comandos e respostas e pode expor informações sensíveis.

Listagem estruturada com MLSD

Quando o servidor oferece suporte, mlsd() é preferível a interpretar texto de LIST.

with FTP_TLS(HOST, context=contexto, timeout=15) as ftps:
    ftps.login(USER, PASSWORD)
    ftps.prot_p()
    for nome, fatos in ftps.mlsd(
        "/entrada",
        facts=["type", "size", "modify"],
    ):
        print(nome, fatos)

O servidor não é obrigado a devolver todos os fatos solicitados. Use fatos.get("size") e valide valores antes de convertê-los.

NLST e LIST

nlst() devolve nomes, enquanto dir() e retrlines("LIST") produzem listagens textuais dependentes do servidor. Não extraia tamanho e data de LIST com posições fixas; formatos variam entre Unix, Windows e implementações.

Baixando arquivo em blocos

from pathlib import Path

DESTINO = Path("relatorio.csv")
LIMITE = 50 * 1024 * 1024

recebido = 0

def gravar(bloco: bytes) -> None:
    global recebido
    recebido += len(bloco)
    if recebido > LIMITE:
        raise ValueError("arquivo excede o limite")
    arquivo.write(bloco)

with DESTINO.open("wb") as arquivo:
    ftps.retrbinary(
        "RETR relatorio.csv",
        gravar,
        blocksize=64 * 1024,
    )

Use um arquivo temporário e renomeie somente após sucesso. Se a transferência falhar, não deixe o nome final apontar para conteúdo parcial.

Download atômico

from pathlib import Path
from tempfile import NamedTemporaryFile

final = Path("relatorio.csv")

with NamedTemporaryFile(
    mode="wb",
    dir=final.parent,
    delete=False,
) as temporario:
    caminho_temp = Path(temporario.name)
    ftps.retrbinary("RETR relatorio.csv", temporario.write)

caminho_temp.replace(final)

Inclua tratamento que remova o temporário em caso de erro. O guia de tempfile no Python explica padrões seguros.

Verificação de integridade

FTP e FTPS não garantem que um arquivo seja o artefato esperado. Compare tamanho, hash publicado por canal confiável ou assinatura.

import hashlib

with open("relatorio.csv", "rb") as arquivo:
    digest = hashlib.file_digest(arquivo, "sha256").hexdigest()

if digest != SHA256_ESPERADO:
    raise ValueError("hash divergente")

Veja hashlib no Python. Um hash obtido do mesmo servidor comprometido não oferece autenticação independente.

Enviando arquivos

from pathlib import Path

caminho = Path("saida.zip")

if caminho.stat().st_size > 100 * 1024 * 1024:
    raise ValueError("arquivo grande demais")

with caminho.open("rb") as arquivo:
    ftps.storbinary(
        "STOR saida.zip.part",
        arquivo,
        blocksize=64 * 1024,
    )

ftps.rename("saida.zip.part", "saida.zip")

Enviar com nome temporário e renomear no servidor evita que consumidores encontrem um arquivo ainda incompleto. Confirme se o rename é atômico na implementação usada.

Modo texto e modo binário

Use retrbinary() e storbinary() para quase todos os arquivos, inclusive CSV e texto, quando você quer preservar bytes exatamente. Os métodos retrlines() e storlines() aplicam semântica de linhas e podem alterar finais de linha.

Encoding de nomes

Desde Python 3.9, o padrão é UTF-8 conforme RFC 2640. Servidores antigos podem usar outra codificação. Configure encoding somente com base no contrato; não tente sequências aleatórias silenciosamente.

Normalize nomes Unicode quando a aplicação exige comparação consistente, mas preserve o nome original para operações no servidor. Não use nomes remotos diretamente como caminhos locais sem validação.

Protegendo caminhos locais

from pathlib import Path

RAIZ = Path("downloads").resolve()

def destino_seguro(nome_remoto: str) -> Path:
    nome = Path(nome_remoto).name
    if nome in {"", ".", ".."}:
        raise ValueError("nome inválido")
    destino = (RAIZ / nome).resolve()
    if RAIZ not in destino.parents:
        raise ValueError("path traversal")
    return destino

Remova separadores, nomes reservados e caracteres de controle conforme o sistema operacional.

Diretórios remotos

cwd(), mkd(), rmd() e pwd() manipulam diretórios. Evite montar comandos com caminhos não validados. O servidor interpreta a string enviada; não existe escaping universal que torne qualquer nome seguro.

Modo passivo

O modo passivo é ativado por padrão e costuma funcionar melhor com NAT e firewalls. set_pasv(False) usa modo ativo, que pode exigir conexões de entrada no cliente. Escolha conforme a infraestrutura e restrinja portas no servidor.

Retomando transferências

retrbinary() e storbinary() aceitam rest, geralmente um offset em bytes. O servidor pode não suportar.

offset = caminho_temp.stat().st_size
with caminho_temp.open("ab") as arquivo:
    ftps.retrbinary(
        "RETR imagem.iso",
        arquivo.write,
        rest=offset,
    )

Antes de retomar, confirme que o arquivo remoto não mudou, usando tamanho, data e preferencialmente hash. Caso contrário, você pode concatenar versões diferentes.

Exceções do ftplib

As principais classes são error_temp para respostas 4xx temporárias, error_perm para 5xx permanentes, error_reply e error_proto. all_errors também inclui falhas de socket.

from ftplib import error_perm, error_temp

try:
    ftps.retrbinary("RETR arquivo.dat", callback)
except error_temp as erro:
    print("falha possivelmente temporária", erro)
except error_perm as erro:
    print("permissão ou arquivo inexistente", erro)

Não repita automaticamente erros permanentes. Para temporários, use backoff, jitter e limite de tentativas.

Timeouts

Defina timeout na conexão. Também controle tempo total da tarefa e tamanho. Uma transferência que continua recebendo pequenos blocos pode durar indefinidamente apesar de não disparar timeout de socket.

Fechando a conexão

Use context manager. quit() envia o comando QUIT de forma educada, mas pode falhar se a conexão já estiver quebrada. O fechamento do contexto garante limpeza. Não reutilize uma instância depois de quit() ou close().

FTP, FTPS e SFTP

FTP é o protocolo original sem criptografia. FTPS adiciona TLS ao FTP e é suportado por FTP_TLS. SFTP é um protocolo diferente sobre SSH. Confundir os nomes gera erros de conexão e decisões inseguras.

Testes recomendados

Teste login inválido, certificado inválido, falta de prot_p(), nome Unicode, listagem sem MLSD, arquivo vazio, limite excedido, conexão interrompida, retomada incompatível, rename, erro temporário, erro permanente e limpeza do temporário.

Erros comuns

Os erros frequentes são usar FTP simples com senha, esquecer prot_p(), desativar validação TLS, interpretar LIST com posições fixas, confiar em nomes remotos, escrever diretamente no destino final, não limitar tamanho, registrar credenciais, repetir uploads não idempotentes e confundir FTPS com SFTP.

Conclusão

ftplib atende integrações FTP e FTPS sem dependências externas. Use FTP_TLS com contexto validado e prot_p(), prefira mlsd(), transfira em blocos para temporários, valide tamanho e integridade, proteja caminhos e trate erros por categoria.

Consulte a documentação oficial de ftplib, o RFC 959 do FTP e o RFC 4217 do FTPS. Para sistemas novos, considere protocolos mais simples de proteger e operar.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Desenvolvedor configurando servidor HTTPS e certificado TLS com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crie servidor HTTPS local no Python

    Aprenda HTTPSServer no Python para servir HTTPS localmente, configurar certificados, usar threads e entender limites de segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    08/10/2026
    Arquivos protegidos representando extração segura de TAR com Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extraia TAR com segurança

    Aprenda tarfile extraction_filter no Python para extrair arquivos TAR com validação, segurança e controle de caminhos.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python exibindo avisos controlados com catch_warnings
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: capture warnings em testes Python

    Aprenda catch_warnings no Python para capturar, testar e controlar avisos com filtros específicos e segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python representando referências persistentes do pickle
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serialize referências externas

    Aprenda pickle persistent_id no Python para serializar referências externas com IDs estáveis, validação, segurança e compatibilidade.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binário representando buffers e memória no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: conte valores sem copiar buffers

    Aprenda memoryview.count no Python para contar bytes e valores em buffers sem cópias, com formatos, limites e boas práticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python com anotações de tipos
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evite imports circulares em anotações

    Aprenda annotationlib no Python para ler anotações adiadas, evitar imports circulares e criar ferramentas de introspecção seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026