files-community/Files · error · IOException

Failed to connect to FTP server.

Error message

Failed to connect to FTP server.

What it means

Thrown by FtpStorageFile.MoveAsync when EnsureConnectedAsync returns false - the FTP client could not establish or restore a connection (credentials rejected, host unreachable, TLS handshake failure). The operation runs inside SafetyExtensions.WrapAsync with a RetryWithCredentialsAsync hook, so a credentials retry is attempted before this surfaces.

Source

Thrown at src/Files.App/Utils/Storage/StorageItems/FtpStorageFile.cs:195

				{
					BaseStorageFile file = await destFolder.CreateFileAsync(desiredNewName, option.Convert());
					await using var stream = await file.OpenStreamForWriteAsync();
					return await ftpClient.DownloadStream(stream, FtpPath, token: cancellationToken) ? file : null;
				}
			}, ((IPasswordProtectedItem)this).RetryWithCredentialsAsync));
		}

		public override IAsyncAction MoveAsync(IStorageFolder destinationFolder)
			=> MoveAsync(destinationFolder, Name, NameCollisionOption.FailIfExists);
		public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName)
			=> MoveAsync(destinationFolder, desiredNewName, NameCollisionOption.FailIfExists);
		public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option)
		{
			return AsyncInfo.Run((cancellationToken) => SafetyExtensions.WrapAsync(async () =>
			{
				using var ftpClient = GetFtpClient();
				if (!await ftpClient.EnsureConnectedAsync())
					throw new IOException($"Failed to connect to FTP server.");

				BaseStorageFolder destFolder = destinationFolder.AsBaseStorageFolder();

				if (destFolder is FtpStorageFolder ftpFolder)
				{
					string destName = $"{ftpFolder.FtpPath}/{Name}";
					FtpRemoteExists ftpRemoteExists = option is NameCollisionOption.ReplaceExisting ? FtpRemoteExists.Overwrite : FtpRemoteExists.Skip;

					bool isSuccessful = await ftpClient.MoveFile(FtpPath, destName, ftpRemoteExists, cancellationToken);
					if (!isSuccessful)
						throw new IOException($"Failed to move file from {Path} to {destFolder}.");
				}
				else
					throw new NotSupportedException();
			}, ((IPasswordProtectedItem)this).RetryWithCredentialsAsync));
		}

View on GitHub (pinned to 68c68a58d4)

Solutions

  1. Prompt the user to re-enter credentials (the RetryWithCredentialsAsync path) and retry the move.
  2. Verify host, port, and encryption settings match the server (explicit vs implicit TLS, correct port).
  3. Check network connectivity and firewall rules for both the control channel and passive data ports.
  4. Wrap MoveAsync in a try/catch for IOException and report a connection-level message rather than a move-level one.
  5. Recreate the FtpStorageFile from a fresh FtpStorageFolder if the underlying client is stale.
Defensive patterns

Strategy: retry

Validate before calling

// Confirm connectivity before issuing the move.
using var c = GetFtpClient();
if (!await c.EnsureConnectedAsync())
    throw new IOException("Cannot reach FTP server; check credentials and network.");

Try / catch

try { await file.MoveAsync(dest, name, option); }
catch (IOException ex) when (ex.Message.Contains("connect to FTP"))
{ /* re-prompt credentials via IPasswordProtectedItem, then retry */ }

Prevention

When it happens

Trigger: MoveAsync on an FtpStorageFile when the underlying AsyncFtpClient fails to connect before issuing the RNFR/RNTO sequence. Typically credentials are wrong/expired, the host is unreachable, the port is blocked, or the TLS negotiation failed.

Common situations: Saved FTP bookmark with an expired password; server moved or is temporarily down; corporate firewall blocking the control port (21) or PASV data ports; certificate changes breaking TLS; connection pool handing back a dead client.

Related errors


AI-assisted analysis of files-community/Files@68c68a58d4 (2026-08-13). Data as JSON: /api/errors/0d7794673ce4f024. Report an issue: GitHub.