Back to Blog
Python

Python Paramiko SSH Key and Password Authentication

python paramiko ssh key and password authentication: Learn how to authenticate with Paramiko using SSH keys and passwords, including passphrase-protected keys, fallbac...

paramikosshauthenticationpythonremote-commandssecurity
Illustration of a Python Paramiko SSH connection using both key and password authentication methods

Python Paramiko SSH key and password authentication covers the two main ways to authenticate to a remote server from an automation script. Paramiko provides a full SSHv2 implementation, and the connect() method on SSHClient accepts both authentication paths. You can even provide both so Paramiko falls back from key to password when the key is rejected.

The connect() Method and Its Authentication Parameters

The SSHClient.connect() method is the entry point for authentication. The parameters that matter for authentication are:

ParameterPurpose
hostnameTarget server address
usernameRemote user to authenticate as
passwordPassword for password auth, or passphrase for a protected key
pkeyA loaded PKey object (RSA, Ed25519, ECDSA, DSA)
key_filenamePath or list of paths to private key files
passphrasePassphrase for the private key, if separate from password
allow_agentWhether to try the local SSH agent
look_for_keysWhether to look for keys in ~/.ssh

When you pass both pkey and password, Paramiko tries the key first. If the server rejects it, Paramiko falls back to the password. This is the basis for a resilient authentication strategy in scripts that must work across servers with different auth configurations.

Password Authentication

Password authentication is the simplest path. You pass the password directly to connect():

import paramiko client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect( hostname="192.168.1.50", username="deploy", password="s3cret-value" ) stdin, stdout, stderr = client.exec_command("hostname") print(stdout.read().decode()) client.close()

The set_missing_host_key_policy(paramiko.AutoAddPolicy()) line is required for this example to work on a server whose host key has not been seen before. Without it, Paramiko raises SSHException and refuses to connect. In a production script, you should replace AutoAddPolicy with a pinned host key, which is covered later in this article.

Password auth is straightforward, but it has operational drawbacks. Passwords are frequently rotated, stored in secret managers, and subject to lockout policies. For long-running automation, key-based auth is usually the better choice.

SSH Key Authentication

Key authentication requires a private key that the server has in its authorized_keys file for the target user. You load the key with one of Paramiko's key classes and pass it to connect() via the pkey parameter:

import paramiko key = paramiko.RSAKey.from_private_key_file("/home/user/.ssh/id_rsa") client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect( hostname="192.168.1.50", username="deploy", pkey=key ) client.close()

Paramiko supports several key types, and the correct class depends on the key format:

Key typeParamiko class
RSAparamiko.RSAKey
Ed25519paramiko.Ed25519Key
ECDSAparamiko.ECDSAKey
DSAparamiko.DSSKey

If you are unsure which type a key file is, you can inspect the first line of the file. An OpenSSH private key begins with a header such as -----BEGIN OPENSSH PRIVATE KEY-----, and the key type is embedded in the base64 body. In practice, RSA and Ed25519 dominate modern deployments; DSA is deprecated in OpenSSH and rarely encountered.

Alternatively, you can skip loading the key yourself and pass key_filename:

client.connect( hostname="192.168.1.50", username="deploy", key_filename="/home/user/.ssh/id_ed25519" )

Paramiko reads the file, detects the key type, and loads it internally. This is convenient when the key format is not known in advance.

Passphrase-Protected Private Keys

A private key protected by a passphrase cannot be loaded without that passphrase. If you try, Paramiko raises paramiko.ssh_exception.PasswordRequiredException. You supply the passphrase when loading the key:

import paramiko key = paramiko.RSAKey.from_private_key_file( "/home/user/.ssh/id_rsa", password="key-passphrase" ) client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect( hostname="192.168.1.50", username="deploy", pkey=key )

Note that the password parameter on from_private_key_file is the key's passphrase, not the remote account password. These are two different secrets, and confusing them is a common source of authentication failures.

You can also pass the passphrase through connect() itself. If pkey is provided and passphrase is set, Paramiko uses it to decrypt the key:

client.connect( hostname="192.168.1.50", username="deploy", pkey=key, passphrase="key-passphrase" )

However, loading the key explicitly with from_private_key_file is clearer because it separates key loading from connection setup.

Combining Key and Password Authentication

A practical pattern for automation scripts is to try key authentication first and fall back to password. Paramiko supports this directly: pass both pkey and password to connect(). The client attempts the key first; if the server rejects it, it tries the password.

import paramiko key = paramiko.Ed25519Key.from_private_key_file( "/home/user/.ssh/id_ed25519", password="key-passphrase" ) client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect( hostname="192.168.1.50", username="deploy", pkey=key, password="remote-password" )

This is useful when you manage a fleet of servers where some have the user's public key installed and others still rely on password auth. The script works against both without branching logic.

There is a subtlety worth knowing. The password parameter serves double duty in connect(): it is both the remote account password and the passphrase for pkey when passphrase is not given. If your key has a passphrase and you also want a password fallback, pass the passphrase explicitly via passphrase and keep password for the fallback. Otherwise Paramiko may try the remote password as the key passphrase first, fail, and then use it as the password, which still works but produces a confusing authentication sequence.

Host Key Verification

The examples so far use AutoAddPolicy, which accepts any host key it has not seen before and adds it to the known-hosts store. This is convenient for scripts and test environments, but it is not safe against man-in-the-middle attacks. An attacker who can intercept the connection can present their own host key, and AutoAddPolicy will accept it.

For production automation, pin the expected host key. The safest approach is to load the known-hosts file and use RejectPolicy (the default) so that unknown or changed host keys cause a connection failure:

import paramiko client = paramiko.SSHClient() client.load_system_host_keys() # reads ~/.ssh/known_hosts # No set_missing_host_key_policy call: RejectPolicy is the default client.connect( hostname="192.168.1.50", username="deploy", pkey=key )

If the server's host key is not in the known-hosts file, connect() raises SSHException with a message about the missing host key. You can also pin a single host key explicitly by loading it and comparing it, but load_system_host_keys() is the most practical approach for scripts that run on a controlled machine.

The tradeoff is clear: AutoAddPolicy makes first-time connections seamless but weakens security; RejectPolicy with a known-hosts file is stricter and fails loudly when something is wrong. For unattended automation, failing loudly is the correct behavior.

Common Authentication Failures

Authentication failures in Paramiko surface as exceptions, and knowing which one you are dealing with speeds up debugging.

paramiko.ssh_exception.AuthenticationException is raised when the server rejects the credentials. This covers both a wrong password and a key that the server does not accept. The message is usually generic, so you need to check the server's auth log to see which method failed.

paramiko.ssh_exception.PasswordRequiredException is raised when you try to load a passphrase-protected key without supplying the passphrase. It is a subclass of SSHException and is easy to miss because it happens at key-loading time, before the connection is attempted.

paramiko.ssh_exception.BadHostKeyException is raised when the server presents a host key that differs from the one recorded in the known-hosts file. This indicates either a legitimate host key rotation or a man-in-the-middle attempt. You should investigate before updating the known-hosts entry.

A common mistake is mixing up the key passphrase and the remote password. If you load a key with the wrong passphrase, Paramiko raises SSHException with a message about the key file, not an AuthenticationException. The error occurs before any network traffic, which is a useful signal when reading stack traces.

Here is a minimal error-handling wrapper that distinguishes the two failure classes:

import paramiko from paramiko.ssh_exception import ( AuthenticationException, PasswordRequiredException, SSHException, ) def connect_with_fallback(hostname, username, key_path, key_passphrase, password): client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: key = paramiko.RSAKey.from_private_key_file(key_path, password=key_passphrase) client.connect(hostname=hostname, username=username, pkey=key, password=password) except PasswordRequiredException: raise RuntimeError("Key passphrase is incorrect or missing") except AuthenticationException: raise RuntimeError("Server rejected the key or password") except SSHException as exc: raise RuntimeError(f"SSH connection failed: {exc}") return client

This wrapper loads the key first, so passphrase errors are caught before the network attempt. If the server rejects both the key and the password, you get an AuthenticationException and can report it as a credential problem rather than a connection problem.

Choosing Between Key and Password Authentication in Production

For scheduled jobs, CI pipelines, and configuration-management tooling, key-based authentication is the standard choice. Keys do not expire the way passwords do, they can be revoked per host by removing the entry from authorized_keys, and they avoid the risk of a password being logged in plaintext by a misconfigured process.

Password authentication remains useful in specific situations. When you are bootstrapping a new server and have not yet installed your public key, a one-time password login is the practical way to get the key into place. The same applies to interactive scripts where a human enters the password at a prompt rather than storing it in a file.

The combined key-and-password approach is best reserved for transitional periods, such as migrating a fleet from password auth to key auth. Once all servers accept the key, remove the password fallback from the script so that a server that silently lost its authorized_keys entry fails loudly instead of quietly falling back to a password that may be stored in a secret manager.

A final operational note: never hardcode passwords or private keys in source code. Read them from environment variables, a secrets manager, or a configuration file with restricted permissions. Paramiko itself does not enforce this, but the authentication method you choose is only as secure as the way you store its credentials.

python paramiko ssh key and password authentication: Practic | RYUSLOG DEV