Paramiko Master Hub + Production Cookbook





Paramiko Master Hub + Production Cookbook | Pythoneo






Paramiko Master Hub + Production Cookbook

Secure SSH/SFTP automation in Python: copy‑paste snippets, key auth, port forwarding, concurrency, bulk ops, logging, security hardening, and troubleshooting.

Updated: 2025‑08‑20
Covers: Paramiko 2.x
License: CC BY 4.0
Start

Quickstart: SSH and SFTP (Safe Defaults)

import paramiko, socket

def ssh_client(host, user, password=None, pkey_path=None, timeout=8):
    ssh = paramiko.SSHClient()
    ssh.load_system_host_keys()
    ssh.set_missing_host_key_policy(paramiko.RejectPolicy())  # safer default
    try:
        if pkey_path:
            key = paramiko.RSAKey.from_private_key_file(pkey_path)
            ssh.connect(hostname=host, username=user, pkey=key,
                        look_for_keys=False, allow_agent=False, timeout=timeout)
        else:
            ssh.connect(hostname=host, username=user, password=password,
                        look_for_keys=False, allow_agent=False, timeout=timeout)
        return ssh
    except (paramiko.SSHException, socket.error) as e:
        raise RuntimeError(f"SSH connect failed: {host}") from e

def run(host, user, cmd, **kw):
    with ssh_client(host, user, **kw) as ssh:
        stdin, stdout, stderr = ssh.exec_command(cmd, timeout=15)
        rc = stdout.channel.recv_exit_status()
        return rc, stdout.read().decode(), stderr.read().decode()
Use RejectPolicy() in production and preload known_hosts. Prefer key authentication. Always set timeouts.
Auth

Key Authentication & Host Keys

# Load key types
from paramiko import RSAKey, ECDSAKey, Ed25519Key
rsa = RSAKey.from_private_key_file("~/.ssh/id_rsa")
ed  = Ed25519Key.from_private_key_file("~/.ssh/id_ed25519")

# Known hosts & strict host key checking
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()  # ~/.ssh/known_hosts
ssh.set_missing_host_key_policy(paramiko.RejectPolicy())
Rotate keys periodically, restrict key permissions (chmod 600), and disable password auth on servers when possible.
Exec

Command Execution Patterns

Run with return code and timeout

def run_checked(ssh, cmd, timeout=20):
    stdin, stdout, stderr = ssh.exec_command(cmd, timeout=timeout)
    rc = stdout.channel.recv_exit_status()
    out, err = stdout.read().decode(), stderr.read().decode()
    if rc != 0:
        raise RuntimeError(f"cmd failed rc={rc}: {cmd}\n{err}")
    return out

Environment and working directory

cmd = "cd /var/log && env FOO=1 BAR=2 ./script.sh"
run_checked(ssh, cmd)

sudo & TTY prompts

# Use a PTY to handle sudo prompts
chan = ssh.get_transport().open_session()
chan.get_pty()
chan.exec_command("sudo -S ls /root")
chan.send("your_sudo_password\\n")
out = chan.makefile("r").read()

Interactive shell

shell = ssh.invoke_shell()
shell.send("uname -a\\n")
resp = shell.recv(65535).decode()

Files

SFTP: Upload, Download, Permissions

def sftp_session(ssh):
    return ssh.open_sftp()

with ssh_client("server", "user", pkey_path="~/.ssh/id_ed25519") as ssh:
    with sftp_session(ssh) as sftp:
        sftp.put("local.bin", "/remote/path/app.bin")   # upload
        sftp.get("/remote/path/log.txt", "log.txt")     # download
        sftp.chdir("/remote/path")
        sftp.mkdir("archive", mode=0o750)
        sftp.chmod("/remote/path/app.bin", 0o750)
        # Atomic upload: write temp then rename
        tmp = "/remote/path/.app.bin.tmp"
        sftp.put("local.bin", tmp)
        sftp.rename(tmp, "/remote/path/app.bin")
Prefer atomic uploads and explicit permissions. For large files, consider checksums (sha256) to verify integrity post‑transfer.
Tunnels

Port Forwarding & Tunnels

# Local port forward: localhost:9000 -> remote:5432
from paramiko.forward import forward_tunnel
import threading

def forward(db_host="127.0.0.1", db_port=5432, local_port=9000):
    ssh = ssh_client("server", "user", pkey_path="~/.ssh/id_ed25519")
    t = threading.Thread(target=forward_tunnel,
                         args=(local_port, db_host, db_port, ssh.get_transport()),
                         daemon=True)
    t.start()
    return ssh, t  # keep ssh open while forwarding
Keep the SSH connection open while forwarding. Close tunnels gracefully on shutdown to avoid orphaned threads.
Scale

Bulk Operations & Concurrency

from concurrent.futures import ThreadPoolExecutor, as_completed

def run_remote(host, user, cmd, pkey_path):
    ssh = ssh_client(host, user, pkey_path=pkey_path)
    try:
        out = run_checked(ssh, cmd)
        return host, 0, out
    except Exception as e:
        return host, 1, str(e)
    finally:
        ssh.close()

hosts = ["srv1","srv2","srv3","srv4"]
with ThreadPoolExecutor(max_workers=8) as ex:
    futures = [ex.submit(run_remote, h, "ops", " ~/.ssh/id_ed25519", "uptime") for h in hosts]
    for fut in as_completed(futures):
        host, rc, msg = fut.result()
        print(host, rc, msg[:120])
Use threads for network I/O. Apply per‑host timeouts and circuit breakers. Rate‑limit to avoid hammering jump hosts.
Audit

Logging & Auditing

import logging, json, sys, time
logging.basicConfig(level=logging.INFO, format="%(message)s")
log = logging.getLogger("paramiko_ops")

def jlog(event, **kw):
    log.info(json.dumps({"ts":time.time(),"event":event,**kw}))

jlog("ssh_connect", host="srv1")
# ...
jlog("cmd", host="srv1", cmd="uptime", rc=0)
Prefer structured JSON logs for audit trails. Mask secrets and consider a unique run_id to correlate events across hosts.
Security

Security Hardening Checklist

  • Use key authentication; disable password auth on servers where possible.
  • Strict host key checking with RejectPolicy; manage known_hosts centrally.
  • Always set timeouts (connect, exec, SFTP). Fail fast and retry with backoff.
  • Least privilege: dedicate users, limit sudoers, restrict file permissions.
  • Rotate keys; protect private keys (chmod 600) and use passphrases or agent.
  • Log commands, exit codes, and transfer checksums; retain for audits.
  • For jump hosts/bastions, prefer ProxyJump or SSH tunnels rather than exposing ports.
Fix

Troubleshooting & Common Errors

Authentication failed

Check key path, permissions (600), username, and server AllowedUsers.

# Test manually with OpenSSH for clarity
# ssh -i ~/.ssh/id_ed25519 user@host -vvv

Host key verification failed

Update known_hosts; avoid AutoAddPolicy in production.

ssh-keyscan -H host.example.com >> ~/.ssh/known_hosts

Hangs / timeouts

Set connect and command timeouts; handle network partitions.

ssh.connect(..., timeout=8)
ssh.exec_command("cmd", timeout=20)

Broken pipe / keepalive

Enable keepalives on Transport.

t = ssh.get_transport()
t.set_keepalive(30)  # seconds

Beware of reading stdout without draining stderr (or vice versa) on long‑running commands; it can deadlock. Read both or use channels.
FAQ

Frequently Asked Questions

How do I run commands that require a TTY (sudo)?

Use get_pty() on a SessionChannel, send the password to stdin, and read both stdout and stderr. Consider NOPASSWD for non‑interactive automation when policy allows.

What’s the best way to deploy files safely?

Upload to a temporary path, verify checksum, set permissions, then rename to the final path (atomic move). Roll back by keeping a backup and swapping names.

How do I go through a bastion/jump host?

Create an SSH connection to the bastion, open a direct-tcpip channel to the target, then create a second Paramiko Transport over that channel; or use local port forwarding and connect the inner session to localhost.

If this page helped, consider linking it in “DevOps,” “Automation,” or “Secure File Transfer” resources. Sharing supports more free content like this.

© 2025 Pythoneo · Designed to be highly linkable: comprehensive, evergreen, copy‑paste friendly, security‑first, and production‑ready.