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

  1. Verify the credentials out-of-band (e.g. curl ftp://host --user user:pass or an FTP client) to confirm they are valid.
  2. Set 'ssl' => true (or use the SSL flag appropriate for your server) when the server requires FTPS.
  3. Pull credentials from your secret manager at runtime and confirm the env-specific values are used.
  4. If the server limits concurrent sessions, lower worker parallelism or enable connection reuse (FtpConnectionProvider caching / single adapter instance).
  5. 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

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

Related errors


AI-assisted analysis of thephpleague/flysystem@b277b5dc3d (2026-08-17). Data as JSON: /api/errors/ec1b58efef0e0127. Report an issue: GitHub.