Skip to content

Repository files navigation

FmiSrl.FtpServer

Build, Test and Publish

A lightweight, modern FTP server library for .NET.

Features

  • Fully asynchronous operation.
  • Supports both Dependency Injection and manual instantiation.
  • Extensible authentication and file system providers.
  • Built-in support for common FTP commands (USER, PASS, LIST, RETR, STOR, etc.).
  • PASV mode support.

Installation

dotnet add package FmiSrl.FtpServer.Server

Usage

With Dependency Injection (Recommended)

To use the FTP server in a modern .NET application (e.g., ASP.NET Core, Worker Service), register it in your Program.cs:

using Microsoft.Extensions.DependencyInjection;
using FmiSrl.FtpServer.Server;
using FmiSrl.FtpServer.Server.DependencyInjection;
using FmiSrl.FtpServer.Server.Services;

var builder = Host.CreateApplicationBuilder(args);

// Configure Options for Providers
builder.Services.Configure<PhysicalFileSystemProviderOptions>(opt =>
{
    opt.RootDirectory = "./my_ftp_root";
});

builder.Services.Configure<SimpleAuthenticationProviderOptions>(opt =>
{
    opt.Username = "admin";
    opt.Password = "secure_password";
});

// Add FTP Server and configure providers via generic DI
builder.Services.AddFtpServer(options =>
{
    options.FtpPort = 21;
    options.ServerName = "My Custom FTP Server";
})
.UseFileSystemProvider<PhysicalFileSystemProvider>()
.UseAuthenticationProvider<SimpleAuthenticationProvider>();

var host = builder.Build();

// Get the server instance and start it
var ftpServer = host.Services.GetRequiredService<FtpServer>();
await ftpServer.StartAsync();

await host.RunAsync();

The server is registered as a Singleton by default.

Without Dependency Injection

You can also use the library in simple console applications or legacy projects without a DI container:

using FmiSrl.FtpServer.Server;
using FmiSrl.FtpServer.Server.Services;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;

var serverOptions = Options.Create(new FtpServerConfigurationOptions
{
    FtpPort = 2121,
    ServerName = "Standalone FTP Server"
});

var fsOptions = Options.Create(new PhysicalFileSystemProviderOptions
{
    RootDirectory = "./ftp_root"
});

var authOptions = Options.Create(new SimpleAuthenticationProviderOptions
{
    Username = "user",
    Password = "password"
});

// Providers are strictly required
var ftpServer = new FtpServer(
    new PhysicalFileSystemProvider(fsOptions),
    new SimpleAuthenticationProvider(authOptions),
    Enumerable.Empty<IFtpCommandMiddleware>(),
    Enumerable.Empty<IFtpServerEventHandler>(),
    serverOptions,
    NullLogger<FtpServer>.Instance
);

await ftpServer.StartAsync();
Console.WriteLine("Server started. Press any key to stop.");
Console.ReadKey();
await ftpServer.StopAsync();

Configuration

Passive Data Ports

Every PASV command binds one listener from the configured range, and holds that port until the transfer ends, the client disconnects, or the idle timeout expires:

var serverOptions = Options.Create(new FtpServerConfigurationOptions
{
    PasvMinPort = 50000,
    PasvMaxPort = 50100,
    PasvIdleTimeout = TimeSpan.FromMinutes(2)
});

Size the range for the peak number of concurrent transfers, with headroom for ports still in TIME_WAIT after a download. Ports are handed out round-robin, so a port that was just released is the last one retried.

PasvIdleTimeout bounds how long a passive listener waits for the client to dial in. Clients that issue PASV and then never connect — aborted transfers, dropped networks — are the usual cause of a drained range; the timeout reclaims their ports. It is disarmed the moment the data connection is accepted, so it never cuts a slow transfer short. Set it to Timeout.InfiniteTimeSpan to wait forever.

Setting PasvMinPort and PasvMaxPort to 0 disables the range entirely and lets the operating system assign an ephemeral port per data connection, which is only appropriate when no firewall or NAT rule constrains the passive ports. When a configured range runs dry the server falls back to an ephemeral port and logs a warning — that port will usually be blocked by a firewall rule scoped to the range, so the warning is worth alerting on.

Logging

The library uses Microsoft.Extensions.Logging. When using DI, it will automatically use the configured logging providers (e.g., Serilog, Console, Debug).

When used manually, you can pass any ILogger<FtpServer> implementation to the constructor.

Authentication Provider

Implement the IAuthenticationProvider interface to customize how users are authenticated:

public interface IAuthenticationProvider
{
    Task<bool> AuthenticateAsync(string username, string password);
}

File System Provider

Implement the IFileSystemProvider interface to provide custom storage (e.g., Azure Blob Storage, Database, S3). It provides an FtpAuthenticationContext which includes the currently authenticated username:

using FmiSrl.FtpServer.Server.Abstractions;

public interface IFileSystemProvider
{
    Task<IEnumerable<FileSystemEntry>> GetEntriesAsync(FtpAuthenticationContext authContext, string path);
    Task<Stream> OpenReadAsync(FtpAuthenticationContext authContext, string path);
    Task<Stream> OpenWriteAsync(FtpAuthenticationContext authContext, string path);
    Task DeleteFileAsync(FtpAuthenticationContext authContext, string path);
    Task CreateDirectoryAsync(FtpAuthenticationContext authContext, string path);
    Task DeleteDirectoryAsync(FtpAuthenticationContext authContext, string path);
    Task<bool> FileExistsAsync(FtpAuthenticationContext authContext, string path);
    Task<bool> DirectoryExistsAsync(FtpAuthenticationContext authContext, string path);
    Task RenameAsync(FtpAuthenticationContext authContext, string oldPath, string newPath);
}

About

A lightweight, modern FTP server library for .NET.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages