Introduction
In the context of modern DevOps and SecOps practices, programmatic access to remote servers has become a necessity. One of the most reliable and powerful ways to interact with remote hosts via Python is the Paramiko library. It provides an implementation of the SSH2 protocol, allowing connections to servers, command execution, file transfers, and building automated infrastructure management systems.
This article examines every aspect of working with Paramiko: from basic concepts to concrete implementations. We will thoroughly explore classes, methods, functions, and practical use cases in real‑world projects.
What Is Paramiko
Paramiko is a Python implementation of the SSH2 protocol, created by Robbie Walker. The name comes from “parallel” + “miko” (miko – a shrine maiden in Japanese culture). The library’s primary goal is to provide a secure data‑transfer channel between hosts with authentication via passwords or keys.
The library is written in pure Python, making it cross‑platform and easy to integrate into any project. Paramiko is widely used in tools such as Ansible, Fabric, and other automation systems.
Key Features and Benefits
Full SSH2 Protocol Implementation
Paramiko supports all core SSH2 capabilities, including:
- Password and key authentication
- Remote command execution
- File transfer via SFTP
- Creation of secure tunnels
Support for Modern Encryption Algorithms
- RSA, DSS, ECDSA, Ed25519 keys
- AES, 3DES, Blowfish ciphers
- SHA‑1, SHA‑256, SHA‑512 hashing
Additional Capabilities
- Compatibility with Python 3.6+
- Support for jump servers (bastion hosts)
- Ability to create custom SSH servers
- Integration with SSH agents
- High reliability and security
Installation and Setup
Standard Installation
pip install paramiko
Installation with Optional Dependencies
pip install paramiko[gssapi] # For Kerberos/GSSAPI
pip install paramiko[invoke] # For integration with Invoke
Verify Installation
import paramiko
print(paramiko.__version__)
Library Architecture
Paramiko is built on a modular architecture with a clear separation of responsibilities among its components:
Main Components
- Transport: Low‑level SSH transport layer
- Channel: Abstraction for data streams
- SSHClient: High‑level interface for SSH connections
- SFTPClient: Client for file operations via SFTP
- ServerInterface: Interface for building SSH servers
Core Classes and Their Methods
SSHClient – Primary Interface
SSHClient is the main class for handling SSH connections. It offers a high‑level API for connecting to servers and executing commands.
Key SSHClient Methods
| Method | Description | Parameters |
|---|---|---|
connect() |
Establish a connection to a server | hostname, port, username, password, pkey, timeout |
exec_command() |
Execute a command | command, bufsize, timeout, get_pty |
invoke_shell() |
Create an interactive shell | term, width, height, width_pixels, height_pixels |
open_sftp() |
Initialize an SFTP session | - |
load_system_host_keys() |
Load system host keys | filename |
load_host_keys() |
Load user‑defined host keys | filename |
save_host_keys() |
Save host keys | filename |
set_missing_host_key_policy() |
Define policy for unknown hosts | policy |
get_transport() |
Retrieve the Transport object | - |
close() |
Close the connection | - |
Basic SSHClient Usage Example
import paramiko
# Create a client
client = paramiko.SSHClient()
# Set policy for unknown hosts
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Connect to the server
client.connect(
hostname='192.168.0.1',
username='user',
password='password',
port=22,
timeout=10
)
# Execute a command
stdin, stdout, stderr = client.exec_command('ls -la')
# Retrieve results
output = stdout.read().decode('utf-8')
errors = stderr.read().decode('utf-8')
print("Command output:")
print(output)
if errors:
print("Errors:")
print(errors)
# Close the connection
client.close()
SFTPClient – File Operations
SFTPClient provides functionality for working with files and directories on a remote server via the SFTP protocol.
Key SFTPClient Methods
| Method | Description | Parameters |
|---|---|---|
put() |
Upload a file to the server | localpath, remotepath, callback, confirm |
get() |
Download a file from the server | remotepath, localpath, callback |
putfo() |
Upload from a file‑like object | fl, remotepath, file_size, callback, confirm |
getfo() |
Download into a file‑like object | remotepath, fl, callback |
listdir() |
List files in a directory | path |
listdir_attr() |
List files with attributes | path |
mkdir() |
Create a directory | path, mode |
rmdir() |
Remove a directory | path |
remove() |
Delete a file | path |
rename() |
Rename a file | oldpath, newpath |
stat() |
Get file information | path |
lstat() |
Get symbolic‑link information | path |
chmod() |
Change file permissions | path, mode |
chown() |
Change file ownership | path, uid, gid |
utime() |
Update access and modification times | path, times |
truncate() |
Truncate a file | path, size |
readlink() |
Read a symbolic link | path |
symlink() |
Create a symbolic link | source, dest |
normalize() |
Normalize a path | path |
getcwd() |
Get current working directory | - |
chdir() |
Change current directory | path |
close() |
Close the SFTP session | - |
SFTP Usage Example
import paramiko
import os
# Create an SSH client
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('hostname', username='user', password='password')
# Open an SFTP client
sftp = ssh.open_sftp()
# Upload a file
sftp.put('local_file.txt', '/remote/path/file.txt')
# Download a file
sftp.get('/remote/path/file.txt', 'downloaded_file.txt')
# Work with directories
try:
sftp.mkdir('/remote/new_directory')
print("Directory created")
except IOError:
print("Directory already exists")
# List files
files = sftp.listdir('/remote/path')
print("Files in directory:", files)
# Detailed file info
for file_attr in sftp.listdir_attr('/remote/path'):
print(f"File: {file_attr.filename}, Size: {file_attr.st_size}")
# Close sessions
sftp.close()
ssh.close()
Transport – Low‑Level Control
Transport offers low‑level access to an SSH connection, allowing fine‑grained session management.
Key Transport Methods
| Method | Description | Parameters |
|---|---|---|
connect() |
Establish the transport connection | hostkey, username, password, pkey, gss_host, gss_auth, gss_kex, gss_deleg_creds |
open_session() |
Create an SSH session | window_size, max_packet_size |
open_sftp_client() |
Create an SFTP client | window_size, max_packet_size |
open_channel() |
Create a channel | kind, dest_addr, src_addr, window_size, max_packet_size |
is_active() |
Check if the transport is active | - |
is_authenticated() |
Check authentication status | - |
get_username() |
Retrieve the username | - |
get_server_key() |
Retrieve the server's host key | - |
close() |
Close the transport | - |
set_keepalive() |
Set a keep‑alive interval | interval |
send_ignore() |
Send an ignore packet | bytes |
Key Classes
Paramiko supports several cryptographic key types:
RSAKey – RSA Keys
| Method | Description | Parameters |
|---|---|---|
generate() |
Generate a new key | bits, progress_func |
from_private_key_file() |
Load a key from a file | filename, password |
from_private_key() |
Load a key from a file‑like object | file_obj, password |
write_private_key_file() |
Save a key to a file | filename, password |
write_private_key() |
Save a key to a file‑like object | file_obj, password |
get_fingerprint() |
Get the key fingerprint | - |
DSAKey, ECDSAKey, Ed25519Key
These classes share the same interface as RSAKey but work with their respective algorithms.
Authentication Methods
Password Authentication
client.connect(
hostname='server.example.com',
username='user',
password='secure_password'
)
Key‑Based Authentication
# Load a private key from a file
key = paramiko.RSAKey.from_private_key_file('/path/to/private_key')
# Connect using the key
client.connect(
hostname='server.example.com',
username='user',
pkey=key
)
Encrypted Key Authentication
# Encrypted key with a passphrase
key = paramiko.RSAKey.from_private_key_file(
'/path/to/encrypted_key',
password='key_password'
)
client.connect(
hostname='server.example.com',
username='user',
pkey=key
)
Using an SSH Agent
# Retrieve keys from the SSH agent
agent = paramiko.Agent()
agent_keys = agent.get_keys()
if agent_keys:
client.connect(
hostname='server.example.com',
username='user',
pkey=agent_keys[0]
)
Security Policies
AutoAddPolicy – Automatically Add Unknown Hosts
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
RejectPolicy – Reject Unknown Hosts (default)
client.set_missing_host_key_policy(paramiko.RejectPolicy())
WarningPolicy – Warn About Unknown Hosts
client.set_missing_host_key_policy(paramiko.WarningPolicy())
Custom Policy Example
class CustomPolicy(paramiko.MissingHostKeyPolicy):
def missing_host_key(self, client, hostname, key):
# Custom host verification logic
print(f"New host: {hostname}")
return True # Allow the connection
client.set_missing_host_key_policy(CustomPolicy())
Working with Channels
Creating an Interactive Shell
# Open a shell session
shell = client.invoke_shell()
# Send a command
shell.send('ls -la\n')
# Receive output
import time
time.sleep(1)
output = shell.recv(1024).decode()
print(output)
# Close the channel
shell.close()
Using a Pseudo‑Terminal
stdin, stdout, stderr = client.exec_command('sudo apt update', get_pty=True)
Error Handling and Exceptions
Common Exceptions
| Exception | Description |
|---|---|
AuthenticationException |
Authentication failure |
SSHException |
General SSH error |
BadHostKeyException |
Invalid host key |
ChannelException |
Channel error |
PasswordRequiredException |
Key requires a passphrase |
ProxyCommandFailure |
Proxy command error |
Exception Handling Example
import paramiko
try:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('hostname', username='user', password='wrong_password')
except paramiko.AuthenticationException:
print("Authentication error")
except paramiko.SSHException as e:
print(f"SSH error: {e}")
except Exception as e:
print(f"General error: {e}")
finally:
client.close()
Logging and Debugging
Enabling Logging
import paramiko
import logging
# Basic logging configuration
logging.basicConfig(level=logging.DEBUG)
paramiko.util.log_to_file('paramiko.log')
# Advanced configuration
logger = logging.getLogger('paramiko')
logger.setLevel(logging.DEBUG)
handler = logging.FileHandler('debug.log')
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
logger.addHandler(handler)
Practical Use Cases
Bulk Command Execution
import paramiko
import threading
def execute_command(host, username, password, command):
try:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(host, username=username, password=password)
stdin, stdout, stderr = client.exec_command(command)
output = stdout.read().decode()
errors = stderr.read().decode()
print(f"Host {host}:")
print(f"Output: {output}")
if errors:
print(f"Errors: {errors}")
except Exception as e:
print(f"Error on host {host}: {e}")
finally:
client.close()
# List of servers
servers = ['192.168.1.10', '192.168.1.11', '192.168.1.12']
# Run in parallel
threads = []
for host in servers:
t = threading.Thread(target=execute_command, args=(host, 'admin', 'password', 'uptime'))
threads.append(t)
t.start()
for t in threads:
t.join()
File Synchronization
import paramiko
import os
def sync_directory(local_dir, remote_dir, hostname, username, password):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname, username=username, password=password)
sftp = ssh.open_sftp()
try:
# Create remote directory
sftp.mkdir(remote_dir)
except IOError:
pass # Directory already exists
# Upload files
for root, dirs, files in os.walk(local_dir):
for file in files:
local_path = os.path.join(root, file)
relative_path = os.path.relpath(local_path, local_dir)
remote_path = os.path.join(remote_dir, relative_path).replace('\\', '/')
# Ensure remote directories exist
remote_dir_path = os.path.dirname(remote_path)
try:
sftp.mkdir(remote_dir_path)
except IOError:
pass
# Transfer file
print(f"Uploading {local_path} -> {remote_path}")
sftp.put(local_path, remote_path)
sftp.close()
ssh.close()
Backup System Example
import paramiko
import datetime
import os
def create_backup(servers, backup_paths, local_backup_dir):
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
for server in servers:
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(server['host'], username=server['username'], password=server['password'])
sftp = ssh.open_sftp()
server_backup_dir = os.path.join(local_backup_dir, server['host'], timestamp)
os.makedirs(server_backup_dir, exist_ok=True)
for remote_path in backup_paths:
filename = os.path.basename(remote_path)
local_path = os.path.join(server_backup_dir, filename)
print(f"Backing up: {server['host']}:{remote_path} -> {local_path}")
sftp.get(remote_path, local_path)
sftp.close()
ssh.close()
except Exception as e:
print(f"Backup error for {server['host']}: {e}")
# Example usage
servers = [
{'host': '192.168.1.10', 'username': 'admin', 'password': 'password'},
{'host': '192.168.1.11', 'username': 'admin', 'password': 'password'},
]
backup_paths = ['/etc/nginx/nginx.conf', '/etc/hosts', '/etc/passwd']
create_backup(servers, backup_paths, '/local/backup/dir')
Working with Jump (Bastion) Hosts
Connecting Through an Intermediate Server
import paramiko
def connect_through_bastion(bastion_host, bastion_user, bastion_pass,
target_host, target_user, target_pass):
# Connect to the bastion host
bastion = paramiko.SSHClient()
bastion.set_missing_host_key_policy(paramiko.AutoAddPolicy())
bastion.connect(bastion_host, username=bastion_user, password=bastion_pass)
# Create a tunnel
transport = bastion.get_transport()
dest_addr = (target_host, 22)
local_addr = ('127.0.0.1', 0)
channel = transport.open_channel('direct-tcpip', dest_addr, local_addr)
# Connect to the target host through the tunnel
target = paramiko.SSHClient()
target.set_missing_host_key_policy(paramiko.AutoAddPolicy())
target.connect(target_host, username=target_user, password=target_pass, sock=channel)
return bastion, target
# Usage
bastion, target = connect_through_bastion(
'bastion.example.com', 'bastion_user', 'bastion_pass',
'internal.server.com', 'target_user', 'target_pass'
)
# Execute a command on the target server
stdin, stdout, stderr = target.exec_command('hostname')
print(stdout.read().decode())
# Close connections
target.close()
bastion.close()
Creating an SSH Server
Basic SSH Server Example
import paramiko
import socket
import threading
class SSHServer(paramiko.ServerInterface):
def __init__(self):
self.event = threading.Event()
def check_channel_request(self, kind, chanid):
if kind == 'session':
return paramiko.OPEN_SUCCEEDED
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def check_auth_password(self, username, password):
if username == 'admin' and password == 'password':
return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
def get_allowed_auths(self, username):
return 'password'
def check_channel_shell_request(self, channel):
self.event.set()
return True
def start_ssh_server():
# Generate a host key
host_key = paramiko.RSAKey.generate(2048)
# Create a listening socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('localhost', 2222))
sock.listen(1)
print('SSH server listening on port 2222')
while True:
client, addr = sock.accept()
print(f'Connection from {addr}')
transport = paramiko.Transport(client)
transport.add_server_key(host_key)
server = SSHServer()
transport.start_server(server=server)
# Wait for a shell request
channel = transport.accept(20)
if channel is not None:
server.event.wait(10)
if server.event.is_set():
channel.send('Welcome to the SSH server!\r\n')
channel.send('$ ')
while True:
try:
data = channel.recv(1024)
if not data:
break
command = data.decode().strip()
if command == 'exit':
break
# Simple command handling
if command == 'date':
import datetime
response = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
elif command == 'whoami':
response = 'admin'
else:
response = f'Unknown command: {command}'
channel.send(f'{response}\r\n$ ')
except Exception as e:
print(f'Error: {e}')
break
channel.close()
transport.close()
if __name__ == '__main__':
start_ssh_server()
Comprehensive Method and Function Reference
Key Functions in the Paramiko Module
| Function | Description |
|---|---|
Transport(sock) |
Create a Transport object |
SSHClient() |
Create an SSH client |
AutoAddPolicy() |
Policy that automatically adds unknown hosts |
RejectPolicy() |
Policy that rejects unknown hosts |
WarningPolicy() |
Policy that warns about unknown hosts |
util.log_to_file() |
Enable logging to a file |
Agent() |
Create an SSH agent |
RSAKey.generate() |
Generate an RSA key |
common.o600() |
Set file permissions to 600 |
Channel‑Related Methods
| Method | Description |
|---|---|
channel.send() |
Send data through the channel |
channel.recv() |
Receive data from the channel |
channel.sendall() |
Send all data |
channel.recv_ready() |
Check if data is ready to be read |
channel.send_ready() |
Check if the channel can accept data |
channel.close() |
Close the channel |
channel.get_pty() |
Request a pseudo‑terminal |
channel.invoke_shell() |
Start a shell session |
channel.exec_command() |
Execute a command |
channel.exit_status_ready() |
Check if the exit status is available |
channel.recv_exit_status() |
Retrieve the exit status |
Advanced Features
Working with SSH Config Files
import paramiko
import os
# Load SSH configuration
config = paramiko.SSHConfig()
config.parse(open(os.path.expanduser('~/.ssh/config')))
# Retrieve host settings
host_config = config.lookup('myserver')
# Connect using the retrieved settings
client = paramiko.SSHClient()
client.connect(
hostname=host_config.get('hostname'),
username=host_config.get('user'),
port=int(host_config.get('port', 22)),
key_filename=host_config.get('identityfile')
)
Using a SOCKS Proxy
import paramiko
import socks
import socket
# Configure SOCKS5 proxy
socks.set_default_proxy(socks.SOCKS5, "127.0.0.1", 1080)
socket.socket = socks.socksocket
# Connect via the proxy
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('target-server.com', username='user', password='password')
Monitoring File Transfer Progress
def progress_callback(transferred, total):
percent = (transferred / total) * 100
print(f'Transferred: {transferred}/{total} bytes ({percent:.1f}%)')
# Use the callback during file upload
sftp.put('large_file.bin', '/remote/path/large_file.bin', callback=progress_callback)
Comparison with Alternative Libraries
| Library | Language | SSH Support | Complexity | Performance |
|---|---|---|---|---|
| Paramiko | Python | Full | Medium | High |
| Netmiko | Python | Network devices | Low | Medium |
| Fabric | Python | High‑level | Low | High |
| AsyncSSH | Python | Asynchronous | High | Very high |
| OpenSSH | CLI | Full | Low | Very high |
Performance Optimization
Reusing Connections
class SSHConnectionPool:
def __init__(self):
self.connections = {}
def get_connection(self, host, username, password):
key = f"{host}:{username}"
if key not in self.connections:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(host, username=username, password=password)
self.connections[key] = client
return self.connections[key]
def close_all(self):
for client in self.connections.values():
client.close()
self.connections.clear()
Parallel Processing
import concurrent.futures
import paramiko
def execute_on_server(server_info):
host, username, password, command = server_info
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(host, username=username, password=password)
stdin, stdout, stderr = client.exec_command(command)
result = stdout.read().decode()
client.close()
return host, result
# Parallel execution across many servers
servers = [
('server1.com', 'user', 'pass', 'uptime'),
('server2.com', 'user', 'pass', 'uptime'),
('server3.com', 'user', 'pass', 'uptime'),
]
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
results = list(executor.map(execute_on_server, servers))
for host, result in results:
print(f"{host}: {result}")
Frequently Asked Questions
How do I connect to a server using a key without a password?
key = paramiko.RSAKey.from_private_key_file('/path/to/private_key')
client.connect(hostname, username=username, pkey=key)
How can I run a command with sudo?
stdin, stdout, stderr = client.exec_command('sudo command', get_pty=True)
stdin.write('password\n')
stdin.flush()
How do I transfer a large file with progress feedback?
def progress(transferred, total):
print(f'Transferred: {transferred}/{total} bytes')
sftp.put('large_file.bin', '/remote/path/file.bin', callback=progress)
How can I handle a connection timeout?
try:
client.connect(hostname, timeout=10)
except socket.timeout:
print("Connection timed out")
How do I check if a file exists on a remote server?
try:
sftp.stat('/path/to/file')
print("File exists")
except IOError:
print("File not found")
How do I create an SSH tunnel?
import select
import socketserver
class ForwardServer(socketserver.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
# Create a tunnel to forward a local port
def forward_tunnel(local_port, remote_host, remote_port, transport):
class SubHandler(socketserver.BaseRequestHandler):
def handle(self):
try:
chan = transport.open_channel('direct-tcpip',
(remote_host, remote_port),
self.request.getpeername())
except Exception:
return
while True:
r, w, x = select.select([self.request, chan], [], [])
if self.request in r:
data = self.request.recv(1024)
if len(data) == 0:
break
chan.send(data)
if chan in r:
data = chan.recv(1024)
if len(data) == 0:
break
self.request.send(data)
chan.close()
self.request.close()
server = ForwardServer(('', local_port), SubHandler)
server.serve_forever()
Best Security Practices
Using Context Managers
from contextlib import contextmanager
@contextmanager
def ssh_connection(hostname, username, password):
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
client.connect(hostname, username=username, password=password)
yield client
finally:
client.close()
# Usage
with ssh_connection('server.com', 'user', 'pass') as ssh:
stdin, stdout, stderr = ssh.exec_command('ls -la')
print(stdout.read().decode())
Validating Host Keys
import binascii
import os
def validate_host_key(hostname, key):
# Load known hosts
known_hosts = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
if hostname in known_hosts:
host_keys = known_hosts[hostname]
if key.get_name() in host_keys:
expected_key = host_keys[key.get_name()]
if key.asbytes() == expected_key.asbytes():
return True
return False
class ValidatingPolicy(paramiko.MissingHostKeyPolicy):
def missing_host_key(self, client, hostname, key):
if validate_host_key(hostname, key):
return
raise paramiko.SSHException(f'Unknown host key for {hostname}')
Secure Credential Storage
import keyring
import getpass
def get_credentials(hostname):
username = input('Username: ')
password = keyring.get_password('ssh', f'{username}@{hostname}')
if not password:
password = getpass.getpass('Password: ')
keyring.set_password('ssh', f'{username}@{hostname}', password)
return username, password
username, password = get_credentials('server.com')
Conclusion
Paramiko is a powerful and flexible tool for SSH operations in Python. The library offers complete control over SSH connections, from simple command execution to complex automation scenarios. Thanks to its reliability, security, and rich feature set, Paramiko remains the de‑facto standard for Python‑based SSH interactions.
Proper use of Paramiko, combined with security best practices, enables the creation of robust automation, monitoring, and infrastructure‑management systems. The library continues to evolve with active community support, ensuring relevance and compatibility with modern requirements.
The Future of AI in Mathematics and Everyday Life: How Intelligent Agents Are Already Changing the Game
Experts warned about the risks of fake charity with AI
In Russia, universal AI-agent for robots and industrial processes was developed