Getting Started with ssh2-sftp-client
While building a WebShell, I needed more than sz/rz uploads. I wanted a GUI that could list files and handle uploads/downloads. After some research I chose ssh2-sftp-client, which wraps SFTP/SSH. Here are the issues I ran into and how I solved them.
Enabling or Disabling the Service
Most Linux distributions ship with SFTP enabled because it’s part of the SSH stack. Administrators can disable it, though, so your code must handle unavailable services.
# vi /etc/ssh/sshd_config
# override default of no subsystems
Subsystem sftp /usr/libexec/openssh/sftp-server
After
# override default of no subsystems
# Subsystem sftp /usr/libexec/openssh/sftp-server
service sshd restart
If SFTP is disabled, ssh2-sftp-client throws ERR_GENERIC_CLIENT
.
list
Use list()
to retrieve directory contents, but keep these in mind:
~
is not supported. Paths must be absolute or relative—not aliases.async function main() { try { await sftp.connect(config); const fileList = await sftp.list(remotePath); console.log(fileList); await sftp.end(); } catch (err) { console.error(err); } }
The
type
field describes the entry:d
: directory-
: regular filel
: symlink
For symlinks, you must query again (e.g., via
stat
) to determine whether the target is a file or directory—isDirectory
/isFile
clarify the real type.size
is in bytes, similar to standard shell output. Directories also have a size.rights
expose permissions. The response also includes the owning UID (owner
) and GID (group
). To determine the current user’s access, you need that user’s UID/GID. Runid <username>
over SSH (with thessh2
client, not the SFTP wrapper) to retrieve it:const { Client: SSH2Client } = require('ssh2'); const ssh2Client = new SSH2Client(); await new Promise((resolve) => ssh2Client.connect(config).on('ready', () => ssh2Client.exec(`id ${config.username}`, (err, stream) => { stream.on('data', (buf) => { const idRes = buf.toString(); console.log(idRes); // uid=0(root) gid=0(root) groups=0(root) const matches = idRes.match(/\d+/g); console.log({ uid: +matches[0], gid: +matches[1], }); resolve(idRes); }); }) ) );
Combine that with the
list()
response to decide whether the user can read/write.
filter
list(path, filter)
accepts a regex-like filter. ^[^.]
hides dotfiles. Note: Windows hidden files don’t follow the dot convention, so this method won’t hide them—you’d need to query attributes separately.
Response Payload
{
"type": "-",
"name": "admin1.pem",
"size": 418,
"modifyTime": 1651807713000,
"accessTime": 1651807715000,
"rights": {
"user": "rw",
"group": "r",
"other": "r"
},
"owner": 0,
"group": 0
}
put
vs. fastPut
- Both upload files.
fastPut
always reads from disk—meaning temporary files if you’re proxying uploads through a Node server. That hurts speed and makes progress reporting tricky. For streaming scenarios (like a WebSocket upload piped through Node to the remote host), useput
. put
accepts any readable stream.- Example: Convert chunks received over WebSocket into a readable stream and pipe directly to
put
. The Node server doesn’t persist anything locally. - Set permissions explicitly—for example,
0o644
, matching FileZilla’s SFTP defaults (rw-r--r--
).
get
vs. fastGet
Similar to uploads: get
is better when you need to stream data to the frontend; fastGet
writes files directly to disk.
Throttling
Use a throttling stream to limit bandwidth:
const Throttle = require('throttle');
const throttleStream = new Throttle(1024 * 1024); // 1 MB/s
this.conn.put(throttleStream.pipe(stream), data.path, {
writeStreamOptions: {
autoClose: false,
mode: 0o644,
},
readStreamOptions: {
autoClose: false,
},
pipeOptions: {
end: false,
},
});
Cancel Transfers
To abort an upload/download, close the SFTP connection and reconnect.
Resumable Transfers
Download: set
readStreamOptions.start
to the byte offset.sftp.get(remoteFile, fileWtr, { readStreamOptions: { start: 10, }, });
Upload: use
append()
to resume.
downloadDir
/ uploadDir
These helpers work but can’t stream, so the server must store intermediate files and progress reporting is inaccurate. Instead, recursively iterate directories and use get
/put
. Create folders with mkdir
as needed.
SFTP Primer
https://www.ssh.com/academy/ssh/sftp
Final Thoughts
That’s it—hope it saves you some time.