Exporter des fichiers vers SFTP en Node.js avec ssh2-sftp-client
Envoyez un fichier local avec put(), téléchargez-le avec get() et comparez les octets avant de signaler
la réussite. Ce tutoriel utilise ssh2-sftp-client avec une authentification par clé SSH et un serveur
OpenSSH local temporaire : vous pouvez ainsi essayer le transfert complet sans compte SFTP existant.
Authentifier le serveur aussi bien que l’utilisateur
SFTP transfère des fichiers via SSH. Votre clé client vous identifie auprès du serveur ; la clé d’hôte
du serveur identifie le serveur auprès de vous. Le client ssh2
sous-jacent accepte automatiquement les clés d’hôte, sauf si vous fournissez un hostVerifier ; l’exemple en vérifie donc une explicitement.
Pour un serveur existant, obtenez l’empreinte de sa clé d’hôte auprès de son administrateur par un canal
de confiance. Avec hostHash: 'sha256', ssh2 transmet au vérificateur un condensé hexadécimal de 64 caractères, en minuscules,
des octets bruts de la clé publique SSH. Cet encodage diffère de l’empreinte base64 SHA256:
affichée par les outils OpenSSH. Ne déduisez pas la valeur attendue d’une première connexion non vérifiée.
Créer le projet client
Les commandes ci-dessous utilisent Bash, Node.js 26.8.1, Yarn 4.12.0, ssh-keygen et Docker Engine 28 ou plus récent
sous Linux. Le client est épinglé à ssh2-sftp-client 12.1.1.
Node exécute directement le fichier TypeScript ; aucune étape de compilation
n’est nécessaire. L’exemple conserve les deux copies du fichier en mémoire : utilisez donc un petit
fichier qui tient largement en RAM.
Exécutez ceci dans un répertoire où node-sftp-demo n’existe pas encore. La chaîne && interrompt la configuration
si la création du répertoire ou l’entrée dans celui-ci échoue. Le fichier de verrouillage vide fait de
ce projet un projet Yarn distinct, y compris lorsque son répertoire parent est un autre projet.
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
Restez dans ce répertoire pour les commandes suivantes. client_key est une clé privée jetable et non
chiffrée destinée à cet exercice local. Seule sa partie publique est copiée dans le conteneur. Tenez les
vraies clés privées à l’écart du contrôle de version et utilisez la configuration d’authentification
approuvée pour votre serveur.
Démarrer un serveur SFTP local
Exécutez ce qui suit dans le premier terminal. Cette commande installe OpenSSH dans un conteneur Ubuntu
24.04, crée l’utilisateur demo et lui attribue un répertoire /home/demo/incoming privé et accessible en écriture.
ForceCommand internal-sftp limite les sessions
à SFTP ; la connexion par mot de passe et le transfert de ports sont désactivés.
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
'
Laissez-la s’exécuter une fois qu’elle a affiché une ligne commençant par Server listening on. Si elle se
termine avant, corrigez l’erreur affichée avant de continuer. Docker choisit un port hôte disponible et
le lie à l’interface de bouclage. Les fichiers du serveur
et sa clé d’hôte n’existent que dans ce conteneur et disparaissent lorsqu’il est supprimé.
Ouvrez un second terminal dans node-sftp-demo. Lisez le port attribué et copiez la clé publique de l’hôte
via votre connexion Docker locale, qui constitue le canal d’administration de confiance pour cet
exemple :
docker port node-sftp-demo 22/tcp &&
docker cp node-sftp-demo:/etc/ssh/demo_host_key.pub server_host_key.pub
La première commande affiche une adresse telle que 127.0.0.1:32768. Utilisez son port réel ci-dessous. La
copie de la clé publique remplace tout fichier server_host_key.pub existant dans ce répertoire de démonstration.
Chaque nouveau conteneur possède une nouvelle clé d’hôte : répétez donc cette étape lorsque vous le
recréez.
Envoyer et vérifier un fichier
Enregistrez ceci sous transfer.ts. Le chemin local et le chemin complet du fichier distant sont des
arguments de ligne de commande. Le répertoire parent distant doit déjà exister et permettre à votre
compte d’écrire et de lire des fichiers.
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() remplace un fichier distant existant au chemin choisi.
Utilisez une destination qui peut être écrasée sans risque. Il s’agit d’une écriture directe : un
transfert interrompu peut laisser un fichier tronqué ou partiel, et un échec de vérification ne
l’annule pas. La copie téléchargée reste en mémoire ; le script ne crée ni n’écrase aucun fichier local
de téléchargement. Buffer.from() normalise le résultat d’un téléchargement vide ainsi que les buffers
binaires ordinaires : un fichier valide de zéro octet passe donc la vérification.
Le paquet documente put() et get().
Ici, ces méthodes s’exécutent successivement sur une seule connexion. Le bloc finally appelle
end() après une réussite ou un échec, et les échecs donnent au processus un code de sortie non
nul. Le readyTimeout de la connexion limite la négociation SSH, pas l’ensemble du transfert ; définissez
un délai limite au niveau de la tâche si vous l’exécutez sans surveillance.
Définissez les paramètres de connexion dans le second terminal. Remplacez 32768 par le port
affiché par Docker. La commande décode le champ base64 de la clé publique de confiance et hache ces
octets, plutôt que de hacher le texte du fichier .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
Pour le fichier fourni, une réussite affiche :
Verified 17 bytes at /home/demo/incoming/example.txt
Pour transférer votre propre petit fichier, remplacez les deux arguments de chemin, en mettant entre guillemets les chemins qui contiennent des espaces. Une comparaison réussie établit que le serveur a renvoyé, à ce moment-là, les octets que vous aviez envoyés ; elle n’établit ni la durabilité des sauvegardes ni qu’un autre processus a consommé le fichier.
Diagnostiquer l’échec d’un transfert
| Étape de l’échec | Points à vérifier |
|---|---|
configuration | Fournissez les deux chemins, un port entier compris entre 1 et 65535, et l’empreinte au format hexadécimal. |
reading local files | Vérifiez que le fichier source et la clé privée existent et sont lisibles. |
connect | Vérifiez le port, la clé d’hôte de confiance, le nom d’utilisateur et la clé client autorisée. Un changement de clé d’hôte doit être vérifié auprès de l’administrateur. |
upload | Vérifiez le répertoire parent distant et les droits d’écriture. Le script ne crée pas de répertoires. |
download verification | Vérifiez les droits de lecture et si un autre processus a déplacé ou modifié le fichier distant. |
Une déconnexion du serveur peut faire échouer l’une ou l’autre étape de transfert. Examinez le terminal
du serveur avant de réessayer, et vérifiez la présence d’un fichier de destination partiel. Ne supprimez
pas le vérificateur d’hôte pour contourner une erreur de connexion. Pour un serveur existant, utilisez
les chemins tels que les voit ce compte SFTP ; un compte chrooté peut voir /incoming/example.txt alors que son
administrateur voit un chemin plus long dans le système de fichiers.
Arrêter le serveur local
Lorsque vous avez terminé, exécutez ceci dans le second terminal :
docker stop node-sftp-demo
Comme le serveur a été démarré avec --rm, son arrêt supprime le conteneur et les fichiers
envoyés. Votre projet local, le fichier d’exemple et la clé client jetable restent dans node-sftp-demo.
