A lightweight, modern FTP server library for .NET.
- 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.
dotnet add package FmiSrl.FtpServer.ServerTo use the FTP server in a modern .NET application (e.g., ASP.NET Core, Worker Service), register it in your Program.cs:
usingMicrosoft.Extensions.DependencyInjection;usingFmiSrl.FtpServer.Server;usingFmiSrl.FtpServer.Server.DependencyInjection;usingFmiSrl.FtpServer.Server.Services;varbuilder=Host.CreateApplicationBuilder(args);// Configure Options for Providersbuilder.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 DIbuilder.Services.AddFtpServer(options =>{options.FtpPort=21;options.ServerName="My Custom FTP Server";}).UseFileSystemProvider<PhysicalFileSystemProvider>().UseAuthenticationProvider<SimpleAuthenticationProvider>();varhost=builder.Build();// Get the server instance and start itvarftpServer=host.Services.GetRequiredService<FtpServer>();awaitftpServer.StartAsync();awaithost.RunAsync();The server is registered as a Singleton by default.
You can also use the library in simple console applications or legacy projects without a DI container:
usingFmiSrl.FtpServer.Server;usingFmiSrl.FtpServer.Server.Services;usingMicrosoft.Extensions.Logging.Abstractions;usingMicrosoft.Extensions.Options;varserverOptions=Options.Create(newFtpServerConfigurationOptions{FtpPort=2121,ServerName="Standalone FTP Server"});varfsOptions=Options.Create(newPhysicalFileSystemProviderOptions{RootDirectory="./ftp_root"});varauthOptions=Options.Create(newSimpleAuthenticationProviderOptions{Username="user",Password="password"});// Providers are strictly requiredvarftpServer=newFtpServer(newPhysicalFileSystemProvider(fsOptions),newSimpleAuthenticationProvider(authOptions),Enumerable.Empty<IFtpCommandMiddleware>(),Enumerable.Empty<IFtpServerEventHandler>(),serverOptions,NullLogger<FtpServer>.Instance);awaitftpServer.StartAsync();Console.WriteLine("Server started. Press any key to stop.");Console.ReadKey();awaitftpServer.StopAsync();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:
varserverOptions=Options.Create(newFtpServerConfigurationOptions{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.
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.
Implement the IAuthenticationProvider interface to customize how users are authenticated:
publicinterfaceIAuthenticationProvider{Task<bool>AuthenticateAsync(stringusername,stringpassword);}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:
usingFmiSrl.FtpServer.Server.Abstractions;publicinterfaceIFileSystemProvider{Task<IEnumerable<FileSystemEntry>>GetEntriesAsync(FtpAuthenticationContextauthContext,stringpath);Task<Stream>OpenReadAsync(FtpAuthenticationContextauthContext,stringpath);Task<Stream>OpenWriteAsync(FtpAuthenticationContextauthContext,stringpath);TaskDeleteFileAsync(FtpAuthenticationContextauthContext,stringpath);TaskCreateDirectoryAsync(FtpAuthenticationContextauthContext,stringpath);TaskDeleteDirectoryAsync(FtpAuthenticationContextauthContext,stringpath);Task<bool>FileExistsAsync(FtpAuthenticationContextauthContext,stringpath);Task<bool>DirectoryExistsAsync(FtpAuthenticationContextauthContext,stringpath);TaskRenameAsync(FtpAuthenticationContextauthContext,stringoldPath,stringnewPath);}