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

    Rack de servidores representando um endpoint criado com xmlrpc.server no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    xmlrpc.server: crie servidores XML-RPC

    Aprenda xmlrpc.server no Python para criar servidores XML-RPC, registrar funções, limitar métodos e caminhos e evitar exposição insegura.

    Ler mais

    Tempo de leitura: 6 minutos
    21/08/2026
    Cabos conectados a servidor representando chamadas remotas com xmlrpc.client no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    xmlrpc.client no Python: chamadas RPC

    Aprenda xmlrpc.client no Python para chamar serviços XML-RPC, tratar Fault e ProtocolError, usar TLS, tipos compatíveis e limites seguros.

    Ler mais

    Tempo de leitura: 4 minutos
    21/08/2026
    Código de erro sobre dados binários representando falhas tratadas com urllib.error no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    urllib.error no Python: trate falhas HTTP

    Aprenda urllib.error no Python para tratar URLError, HTTPError, downloads incompletos, retries seletivos e diagnósticos de rede mais claros.

    Ler mais

    Tempo de leitura: 6 minutos
    21/08/2026
    Teclas com a palavra HTML representando entidades HTML no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    html.entities: converta entidades HTML

    Aprenda html.entities no Python para consultar entidades HTML, converter nomes e code points e evitar confundir decoding com sanitização.

    Ler mais

    Tempo de leitura: 7 minutos
    21/08/2026
    Pasta com arquivos representando tipos MIME identificados com mimetypes no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    mimetypes: detecte tipos MIME de arquivos

    Aprenda mimetypes no Python para identificar tipos de arquivos, validar uploads e gerar headers HTTP com mais segurança.

    Ler mais

    Tempo de leitura: 7 minutos
    20/08/2026
    Código HTML em uma tela representando análise com html.parser no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    html.parser no Python: analise HTML

    Aprenda html.parser no Python para extrair texto, links e metadados, processar HTML em blocos e evitar confundir parsing com sanitização.

    Ler mais

    Tempo de leitura: 6 minutos
    20/08/2026