thephpleague/flysystem · critical · UnableToAuthenticate
Unable to login/authenticate with FTP
Error message
Unable to login/authenticate with FTP
What it means
FtpConnectionProvider::authenticate() calls ftp_login() with the username and password from FtpConnectionOptions; a false return (warnings suppressed with @) results in UnableToAuthenticate with the fixed message 'Unable to login/authenticate with FTP'. This is a pure credentials/protocol rejection from the FTP LOGIN command, not a connection failure (that would be UnableToConnectToFtpHost).
Source
Thrown at src/Ftp/FtpConnectionProvider.php:61
private function createConnectionResource(string $host, int $port, int $timeout, bool $ssl)
{
error_clear_last();
$connection = $ssl ? @ftp_ssl_connect($host, $port, $timeout) : @ftp_connect($host, $port, $timeout);
if ($connection === false) {
throw UnableToConnectToFtpHost::forHost($host, $port, $ssl, error_get_last()['message'] ?? '');
}
return $connection;
}
/**
* @param resource $connection
*/
private function authenticate(FtpConnectionOptions $options, $connection): void
{
if ( ! @ftp_login($connection, $options->username(), $options->password())) {
throw new UnableToAuthenticate();
}
}
/**
* @param resource $connection
*/
private function enableUtf8Mode(FtpConnectionOptions $options, $connection): void
{
if ( ! $options->utf8()) {
return;
}
$response = @ftp_raw($connection, "OPTS UTF8 ON");
if ( ! in_array(substr($response[0], 0, 3), ['200', '202'])) {
throw new UnableToEnableUtf8Mode(
'Could not set UTF-8 mode for connection: ' . $options->host() . '::' . $options->port()
);View on GitHub (pinned to b277b5dc3d)
Solutions
- Verify the credentials out-of-band (e.g. curl ftp://host --user user:pass or an FTP client) to confirm they are valid.
- Set 'ssl' => true (or use the SSL flag appropriate for your server) when the server requires FTPS.
- Pull credentials from your secret manager at runtime and confirm the env-specific values are used.
- If the server limits concurrent sessions, lower worker parallelism or enable connection reuse (FtpConnectionProvider caching / single adapter instance).
- Check the FTP server's logs for the exact 530 response reason.
Example fix
// before
$options = FtpConnectionOptions::lazy('ftp.example.com', '/', FTP_NATIVE, null, 'user', 'wrong-password');
new Filesystem(new FtpAdapter($options)); // -> UnableToAuthenticate
// after (FTPS + correct credentials from env)
$options = FtpConnectionOptions::lazy(
'ftp.example.com', '/', FTP_NATIVE, null,
getenv('FTP_USERNAME'),
getenv('FTP_PASSWORD'),
null, true // ssl: true
); Defensive patterns
Strategy: validation
Validate before calling
// Validate credentials and TLS requirement before constructing the adapter
if ($user === '' || $password === '') {
throw new RuntimeException('FTP credentials missing: check secret store.');
}
// Confirm server expects FTPS when you set ssl: true (and vice versa)
// Many 530 rejections on plain FTP are 'TLS required' in disguise — check server policy. Try / catch
use League\Flysystem\Ftp\UnableToAuthenticate;
try {
$filesystem->write('probe.txt', 'ok'); // triggers connection + login
} catch (UnableToAuthenticate $e) {
// credentials/protocol problem: do NOT retry blindly (can lock the account)
$this->alerting->page('FTP credentials rejected for ' . $host);
throw $e;
} Prevention
- Load FTP credentials from a secret manager with env-specific keys and rotate them before expiry.
- Match the 'ssl' option to the server's TLS policy; verify with an FTP client beforehand.
- Limit concurrent sessions per user when the provider enforces session caps.
- Alert (don't retry) on UnableToAuthenticate — repeated failures can trigger account lockout.
When it happens
Trigger: Wrong username or password in FtpConnectionOptions::lazy(...); server requires implicit FTPS/explicit TLS but a plain connection was made, so login is refused; account locked, IP allowlist/firewall blocking, or the server rejecting the login because max connections per user is exceeded.
Common situations: Rotated/expired FTP credentials not updated in the secret store; environment drift (dev creds in prod or vice versa); host requires FTPS but 'ssl' => false; hosting providers that lock accounts after failed attempts; concurrent workers exceeding per-user session limits.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Metadata can't be parsed from item '$item' , not enough part
- Could not set UTF-8 mode for connection: {host}::{port}
- Could not set passive mode for connection: {host}::{port}
- Unable to get checksum for $path: $reason
- ETag header not available.
AI-assisted analysis of thephpleague/flysystem@b277b5dc3d (2026-08-17).
Data as JSON: /api/errors/ec1b58efef0e0127.
Report an issue: GitHub.