{"record":{"id":"ec1b58efef0e0127","repo":"thephpleague/flysystem","slug":"unable-to-login-authenticate-with-ftp","errorCode":null,"errorMessage":"Unable to login/authenticate with FTP","messagePattern":"Unable to login/authenticate with FTP","errorType":"exception","errorClass":"UnableToAuthenticate","httpStatus":null,"severity":"critical","filePath":"src/Ftp/FtpConnectionProvider.php","lineNumber":61,"sourceCode":"    private function createConnectionResource(string $host, int $port, int $timeout, bool $ssl)\n    {\n        error_clear_last();\n        $connection = $ssl ? @ftp_ssl_connect($host, $port, $timeout) : @ftp_connect($host, $port, $timeout);\n\n        if ($connection === false) {\n            throw UnableToConnectToFtpHost::forHost($host, $port, $ssl, error_get_last()['message'] ?? '');\n        }\n\n        return $connection;\n    }\n\n    /**\n     * @param resource $connection\n     */\n    private function authenticate(FtpConnectionOptions $options, $connection): void\n    {\n        if ( ! @ftp_login($connection, $options->username(), $options->password())) {\n            throw new UnableToAuthenticate();\n        }\n    }\n\n    /**\n     * @param resource $connection\n     */\n    private function enableUtf8Mode(FtpConnectionOptions $options, $connection): void\n    {\n        if ( ! $options->utf8()) {\n            return;\n        }\n\n        $response = @ftp_raw($connection, \"OPTS UTF8 ON\");\n\n        if ( ! in_array(substr($response[0], 0, 3), ['200', '202'])) {\n            throw new UnableToEnableUtf8Mode(\n                'Could not set UTF-8 mode for connection: ' . $options->host() . '::' . $options->port()\n            );","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/thephpleague/flysystem/blob/b277b5dc3d56650b68904117124e79c851e12376/src/Ftp/FtpConnectionProvider.php#L43-L79","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n$options = FtpConnectionOptions::lazy('ftp.example.com', '/', FTP_NATIVE, null, 'user', 'wrong-password');\nnew Filesystem(new FtpAdapter($options)); // -> UnableToAuthenticate\n\n// after (FTPS + correct credentials from env)\n$options = FtpConnectionOptions::lazy(\n    'ftp.example.com', '/', FTP_NATIVE, null,\n    getenv('FTP_USERNAME'),\n    getenv('FTP_PASSWORD'),\n    null, true // ssl: true\n);","handlingStrategy":"validation","validationCode":"// Validate credentials and TLS requirement before constructing the adapter\nif ($user === '' || $password === '') {\n    throw new RuntimeException('FTP credentials missing: check secret store.');\n}\n// Confirm server expects FTPS when you set ssl: true (and vice versa)\n// Many 530 rejections on plain FTP are 'TLS required' in disguise — check server policy.","typeGuard":null,"tryCatchPattern":"use League\\Flysystem\\Ftp\\UnableToAuthenticate;\n\ntry {\n    $filesystem->write('probe.txt', 'ok'); // triggers connection + login\n} catch (UnableToAuthenticate $e) {\n    // credentials/protocol problem: do NOT retry blindly (can lock the account)\n    $this->alerting->page('FTP credentials rejected for ' . $host);\n    throw $e;\n}","preventionTips":["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."],"tags":["php","flysystem","ftp","authentication","credentials","ftps"],"backgroundTag":"ftp-authentication-failed","analyzedSha":"b277b5dc3d56650b68904117124e79c851e12376","analyzedAt":"2026-08-17T04:28:35.741Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}