Exportar arquivos para SFTP no Node.js com ssh2-sftp-client
Faça upload de um arquivo local com put(), baixe-o com get() e compare os bytes antes de
informar sucesso. Este passo a passo usa ssh2-sftp-client com autenticação por chave SSH e um servidor
OpenSSH local temporário, para que você possa testar a transferência completa sem uma conta SFTP
existente.
Autenticar o servidor além do usuário
O SFTP transfere arquivos por SSH. A sua chave de cliente identifica você para o servidor; a chave
de host do servidor identifica o servidor para você. O cliente ssh2
subjacente aceita chaves de host automaticamente, a menos que você forneça um hostVerifier, por isso o
exemplo verifica uma explicitamente.
Para um servidor existente, obtenha a impressão digital da chave de host com o administrador dele
por um canal confiável. Com hostHash: 'sha256', ssh2 passa ao verificador um digest hexadecimal em
minúsculas, de 64 caracteres, dos bytes brutos da chave pública SSH. Essa é uma codificação
diferente da impressão digital SHA256: em base64 exibida pelas ferramentas do OpenSSH. Não obtenha o
valor esperado a partir de uma primeira conexão não verificada.
Criar o projeto cliente
Os comandos abaixo usam Bash, Node.js 26.8.1, Yarn 4.12.0, ssh-keygen e Docker Engine 28 ou mais
recente no Linux. O cliente está fixado no ssh2-sftp-client 12.1.1.
O Node executa o arquivo TypeScript diretamente; não é necessária nenhuma
etapa de build. O exemplo mantém as duas cópias do arquivo em memória, então use um arquivo pequeno
que caiba folgadamente na RAM.
Execute isto em um diretório onde node-sftp-demo ainda não exista. A cadeia && interrompe a
configuração se a criação do diretório ou a entrada nele falhar. O lockfile vazio mantém este como
um projeto Yarn separado, inclusive quando o diretório pai é outro projeto.
mkdir node-sftp-demo &&
cd node-sftp-demo &&
printf '{"private":true,"type":"module"}\n' > package.json &&
touch yarn.lock &&
yarn add --exact ssh2-sftp-client@12.1.1 &&
ssh-keygen -q -t ed25519 -N '' -f client_key &&
printf 'Hello over SFTP.\n' > example.txt
Permaneça neste diretório para os comandos restantes. client_key é uma chave privada descartável e não
criptografada para este exercício local. Apenas a metade pública dela vai para o contêiner. Mantenha
chaves privadas reais fora do controle de versão e use a configuração de autenticação aprovada para
o seu servidor.
Iniciar um servidor SFTP local
Execute o seguinte no primeiro terminal. Ele instala o OpenSSH dentro de um contêiner Ubuntu 24.04,
cria o usuário demo e dá a esse usuário um diretório /home/demo/incoming privado e gravável.
ForceCommand internal-sftp restringe as sessões
ao SFTP; o login por senha e o encaminhamento ficam desativados.
docker run --rm --name node-sftp-demo \
--publish 127.0.0.1::22 \
--mount "type=bind,src=$PWD/client_key.pub,dst=/client_key.pub,readonly" \
ubuntu:24.04 bash -euc '
if ! command -v sshd >/dev/null; then
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssh-server
fi
useradd -m -s /bin/sh demo
passwd -d demo
install -d -m 700 -o demo -g demo /home/demo/.ssh /home/demo/incoming
install -m 600 -o demo -g demo /client_key.pub /home/demo/.ssh/authorized_keys
mkdir -p /run/sshd
ssh-keygen -q -t ed25519 -N "" -f /etc/ssh/demo_host_key
exec /usr/sbin/sshd -D -e -f /dev/null \
-o HostKey=/etc/ssh/demo_host_key \
-o PasswordAuthentication=no \
-o KbdInteractiveAuthentication=no \
-o PermitRootLogin=no \
-o AllowUsers=demo \
-o DisableForwarding=yes \
-o "Subsystem=sftp internal-sftp" \
-o ForceCommand=internal-sftp
'
Deixe isto em execução depois que ele imprimir uma linha começando com Server listening on. Se ele encerrar
antes, resolva o erro impresso antes de continuar. O Docker escolhe uma porta disponível no host e
a vincula ao loopback. Os arquivos e a chave de host do
servidor existem apenas neste contêiner e desaparecem quando ele é removido.
Abra um segundo terminal em node-sftp-demo. Leia a porta atribuída e copie a chave pública do host pela
sua conexão local com o Docker, que é o canal administrativo confiável deste exemplo:
docker port node-sftp-demo 22/tcp &&
docker cp node-sftp-demo:/etc/ssh/demo_host_key.pub server_host_key.pub
O primeiro comando imprime um endereço como 127.0.0.1:32768. Use a porta real dele abaixo. Copiar a chave
pública substitui qualquer server_host_key.pub existente neste diretório de demonstração. Cada novo contêiner
tem uma nova chave de host, então repita esta etapa ao recriá-lo.
Fazer upload de um arquivo e verificá-lo
Salve isto como transfer.ts. O caminho local e o caminho completo do arquivo remoto são argumentos de
linha de comando. O diretório pai remoto já precisa existir e permitir que a sua conta grave e leia
arquivos.
import { readFile } from 'node:fs/promises'
import Client from 'ssh2-sftp-client'
let stage = 'configuration'
async function main(): Promise<void> {
const [localPath, remotePath] = process.argv.slice(2)
const { SFTP_HOST, SFTP_PORT, SFTP_USERNAME, SFTP_KEY_FILE, SFTP_HOST_SHA256 } = process.env
const port = Number(SFTP_PORT ?? '22')
if (
!localPath || !remotePath || !SFTP_HOST || !SFTP_USERNAME || !SFTP_KEY_FILE ||
!/^[a-f0-9]{64}$/.test(SFTP_HOST_SHA256 ?? '') ||
!Number.isInteger(port) || port < 1 || port > 65535
) {
throw new Error('Provide two paths, SFTP settings, and a verified SHA-256 hex fingerprint')
}
stage = 'reading local files'
const original = await readFile(localPath)
const privateKey = await readFile(SFTP_KEY_FILE)
const sftp = new Client()
try {
stage = 'connect'
await sftp.connect({
host: SFTP_HOST,
port,
username: SFTP_USERNAME,
privateKey,
hostHash: 'sha256',
hostVerifier: (fingerprint: string) => fingerprint === SFTP_HOST_SHA256,
readyTimeout: 10000,
})
stage = 'upload'
await sftp.put(original, remotePath)
stage = 'download verification'
// Version 12.1.1 can return an empty array for a zero-byte download.
const downloaded = Buffer.from(await sftp.get(remotePath))
if (!original.equals(downloaded)) {
throw new Error('Downloaded bytes differ from the uploaded bytes')
}
} finally {
await sftp.end()
}
console.log(`Verified ${original.length} bytes at ${remotePath}`)
}
main().catch(() => {
console.error(`SFTP transfer failed during ${stage}; check the settings and server logs`)
process.exitCode = 1
})
put() substitui um arquivo remoto existente no caminho escolhido. Use um destino que possa ser
sobrescrito com segurança. Esta é uma gravação direta: uma transferência interrompida pode deixar
um arquivo truncado ou parcial, e uma verificação com falha não o reverte. A cópia baixada fica em
memória; o script não cria nem sobrescreve um arquivo de download local. Buffer.from() normaliza o
resultado de download vazio, além de buffers binários comuns, então um arquivo válido de zero bytes
passa na verificação.
O pacote documenta put() e get().
Aqui eles são executados em sequência em uma única conexão. O bloco finally chama end() após
sucesso ou falha, e as falhas dão ao processo um status de saída diferente de zero. O readyTimeout da
conexão limita o handshake SSH, não a transferência inteira; use um prazo para a tarefa como um todo
se for executar isto sem supervisão.
Defina os detalhes da conexão no segundo terminal. Substitua 32768 pela porta que o Docker
imprimiu. O comando decodifica o campo base64 da chave pública confiável e calcula o hash desses
bytes, em vez de calcular o hash do texto do arquivo .pub:
export SFTP_HOST=127.0.0.1 SFTP_PORT=32768 SFTP_USERNAME=demo SFTP_KEY_FILE=client_key
SFTP_HOST_SHA256=$(node --input-type=module -e '
import { createHash } from "node:crypto"
import { readFileSync } from "node:fs"
const [, key] = readFileSync("server_host_key.pub", "utf8").trim().split(/\s+/)
console.log(createHash("sha256").update(Buffer.from(key, "base64")).digest("hex"))
') &&
export SFTP_HOST_SHA256 &&
yarn node transfer.ts example.txt /home/demo/incoming/example.txt
Para o arquivo fornecido, o sucesso imprime:
Verified 17 bytes at /home/demo/incoming/example.txt
Para transferir o seu próprio arquivo pequeno, substitua os dois argumentos de caminho, colocando entre aspas os caminhos que contêm espaços. Uma comparação bem-sucedida estabelece que o servidor retornou os mesmos bytes do seu upload naquele momento; ela não estabelece a durabilidade de backup nem que outro processo consumiu o arquivo.
Diagnosticar uma transferência com falha
| Etapa da falha | O que verificar |
|---|---|
configuration | Informe os dois caminhos, uma porta inteira de 1 a 65535 e a impressão digital em formato hexadecimal. |
reading local files | Verifique se o arquivo de origem e a chave privada existem e podem ser lidos. |
connect | Verifique a porta, a chave de host confiável, o nome de usuário e a chave de cliente autorizada. Uma mudança na chave de host precisa ser verificada com o administrador. |
upload | Verifique o diretório pai remoto e as permissões de gravação. O script não cria diretórios. |
download verification | Verifique as permissões de leitura e se outro processo moveu ou alterou o arquivo remoto. |
Uma desconexão do servidor pode fazer qualquer uma das etapas de transferência falhar. Inspecione o
terminal do servidor antes de tentar novamente e verifique se há um arquivo de destino parcial. Não
remova o verificador de host para contornar um erro de conexão. Para um servidor existente, use os
caminhos como essa conta SFTP os vê; uma conta em chroot pode ver /incoming/example.txt mesmo quando o
administrador dela vê um caminho mais longo no sistema de arquivos.
Parar o servidor local
Ao terminar, execute isto no segundo terminal:
docker stop node-sftp-demo
Como o servidor foi iniciado com --rm, pará-lo exclui o contêiner e os arquivos enviados a ele
por upload. O seu projeto local, o arquivo de exemplo e a chave de cliente descartável permanecem em
node-sftp-demo.
