This guide walks through a minimal Sftpd setup using the in-memory backend,
then shows how to switch to S3.
1. Add the dependency
This guide uses the current pinned development environment:
- Erlang/OTP 29.0
- Elixir 1.20.0-rc.5 on OTP 29
The package itself still declares an older minimum Elixir version in mix.exs.
The current verified minimum is Elixir 1.14.5 on OTP 26.
def deps do
[
{:sftpd, "~> 0.1.1"}
]
endThen fetch dependencies:
mix deps.get
2. Generate SSH host keys
SFTP clients expect the server to present SSH host keys. Create them once and keep them somewhere your application can read:
mkdir -p ssh_keys
ssh-keygen -t rsa -f ssh_keys/ssh_host_rsa_key -N ""
ssh-keygen -t ecdsa -f ssh_keys/ssh_host_ecdsa_key -N ""
ssh-keygen -t ed25519 -f ssh_keys/ssh_host_ed25519_key -N ""
Pass the containing directory as system_dir.
3. Start a server with the memory backend
The memory backend is the fastest way to get a working server without any external services:
{:ok, ref} =
Sftpd.start_server(
port: 2222,
backend: Sftpd.Backends.Memory,
backend_opts: [],
auth: {:passwords, [{"dev", "dev"}]},
system_dir: "ssh_keys"
)Important options:
:portcontrols the SSH listener port:backendselects the storage implementation:backend_optspasses backend-specific configuration:authconfigures authentication. Use{:passwords, [{"dev", "dev"}]}for local development, or{MyApp.SftpAuth, opts}for application callbacks:system_dirpoints at the SSH host key directory:max_sessionslimits concurrent client sessions:open_timeoutbounds file open setup time:close_timeoutbounds close-time finalization time
OTP 29 no longer enables the SFTP subsystem implicitly for SSH daemons.
Sftpd.start_server/1 supplies the required :subsystems option internally,
so the setup above works on both OTP 29 and older supported OTP releases.
OTP 29 also disables remote shell and exec services by default. Sftpd is an
SFTP-only wrapper and does not enable those services.
4. Connect with an SFTP client
From another terminal:
sftp -P 2222 dev@localhost
Then try a few operations:
put local.txt remote.txt
ls
get remote.txt
rm remote.txtBecause the memory backend is ephemeral, data disappears when the server stops.
5. Stop the server
:ok = Sftpd.stop_server(ref)6. Switch to the S3 backend
To persist files in S3-compatible storage, use Sftpd.Backends.S3:
The S3 backend is optional. The memory backend and custom backends work with
only {:sftpd, "~> 0.1.1"}. Add the S3 dependencies before using
Sftpd.Backends.S3:
def deps do
[
{:sftpd, "~> 0.1.1"},
{:ex_aws, "~> 2.0"},
{:ex_aws_s3, "~> 2.0"},
{:hackney, "~> 1.9"},
{:sweet_xml, "~> 0.7"},
{:jason, "~> 1.3"},
{:configparser_ex, "~> 4.0"}
]
endWithout those dependencies, Sftpd.Backends.S3.init/1 returns
{:error, :missing_s3_dependency}.
{:ok, ref} =
Sftpd.start_server(
port: 2222,
backend: Sftpd.Backends.S3,
backend_opts: [bucket: "my-bucket", prefix: "tenant-a/"],
auth: {:passwords, [{"dev", "dev"}]},
system_dir: "ssh_keys"
)S3 backend options:
:bucketis required:prefixscopes keys within a bucket. It can be a string or{:session, key}to read a per-user prefix from the auth session map:aws_clientlets you swap in a compatible client for tests or custom adapters
Example ExAws configuration:
config :ex_aws,
access_key_id: "your-key",
secret_access_key: "your-secret",
region: "us-east-1"
config :ex_aws, :s3,
scheme: "http://",
host: "localhost",
port: 90007. Add telemetry handlers if you want instrumentation
Sftpd depends on :telemetry directly. Applications only need to attach
handlers for the events they want to consume.
See Telemetry for the full event reference.
8. Build your own backend
If neither built-in backend fits your storage model:
- read Backends for backend architecture and tradeoffs
- read Custom Backends for implementation guidance
- implement the
Sftpd.Backendbehaviour
Notes and Caveats
Sftpdwraps Erlang's:ssh_sftpdimplementation and explicitly enables the SFTP subsystem required by OTP 29- OTP 29 disables SSH shell and exec services by default;
Sftpddoes not expose or enable those services - OTP's stock SFTP server always reports close success to the client, even if final close-time backend flushing fails
- backends should return POSIX-style error atoms such as
:enoentand:eio - the S3 backend models directories using
.keepmarker objects