ssh
Python
automation
remote access
programming

Perform commands over ssh with Python

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Running commands over SSH from Python is a standard automation task for deployment, backups, configuration checks, and incident response. The main design choice is whether you want a lightweight wrapper around the system ssh command or a native Python SSH client library. For most programmatic control, paramiko is the usual starting point because it supports authentication, command execution, and output capture without shelling out to external binaries.

The Simplest Approach: Call ssh with subprocess

If your machine already has SSH configured and you just need to reuse that setup, calling the system client can be the shortest path.

python
1import subprocess
2
3result = subprocess.run(
4    ["ssh", "[email protected]", "uname -a"],
5    capture_output=True,
6    text=True,
7    check=False,
8)
9
10print("return code:", result.returncode)
11print("stdout:", result.stdout.strip())
12print("stderr:", result.stderr.strip())

This approach benefits from your normal SSH config, agent forwarding, and known-hosts behavior. The tradeoff is that error handling and session management are less structured than with a Python library.

Using Paramiko for Native SSH Control

paramiko implements the SSH protocol in Python and gives you full access to authentication and output streams.

python
1import paramiko
2
3client = paramiko.SSHClient()
4client.load_system_host_keys()
5client.set_missing_host_key_policy(paramiko.RejectPolicy())
6
7client.connect(
8    hostname="example.com",
9    username="deploy",
10    key_filename="/home/user/.ssh/id_ed25519",
11    timeout=10,
12)
13
14stdin, stdout, stderr = client.exec_command("hostname && whoami")
15
16print(stdout.read().decode())
17print(stderr.read().decode())
18
19client.close()

This is the better option when you need reusable automation code rather than a one-off command wrapper.

Capture Exit Status Correctly

Reading standard output alone is not enough. Many SSH failures show up in the exit code or standard error stream.

python
1import paramiko
2
3client = paramiko.SSHClient()
4client.load_system_host_keys()
5client.set_missing_host_key_policy(paramiko.RejectPolicy())
6client.connect(hostname="example.com", username="deploy")
7
8stdin, stdout, stderr = client.exec_command("test -f /etc/passwd")
9exit_code = stdout.channel.recv_exit_status()
10
11print("exit code:", exit_code)
12print("stderr:", stderr.read().decode().strip())
13
14client.close()

Always wait for the channel exit status before treating the remote command as successful.

Authentication Choices

SSH automation usually uses one of two methods:

  • key-based authentication
  • password authentication

Key-based authentication is preferred because it is safer and easier to integrate with agent-based workflows.

Password example:

python
1client.connect(
2    hostname="example.com",
3    username="deploy",
4    password="secret-password",
5)

This is simple for demos but usually a bad production practice. Store secrets in a proper secret manager if passwords are unavoidable.

Running Commands That Need a Shell

Remote commands are not full local shell sessions unless you explicitly invoke one. If you rely on pipes, shell variables, or compound commands, write them accordingly.

python
1stdin, stdout, stderr = client.exec_command(
2    "bash -lc 'echo $HOME && ls -1 /tmp | head -n 3'"
3)
4print(stdout.read().decode())

Be careful with quoting. Once you start nesting shell syntax inside Python strings, subtle escaping bugs are easy to introduce.

Reusing Connections

For multiple commands on the same host, keep one SSH connection open instead of reconnecting each time. Repeated handshakes create unnecessary latency.

python
1commands = [
2    "uptime",
3    "df -h /",
4    "systemctl is-active nginx",
5]
6
7for command in commands:
8    stdin, stdout, stderr = client.exec_command(command)
9    exit_code = stdout.channel.recv_exit_status()
10    print(command, exit_code)
11    print(stdout.read().decode().strip())

If you need to work with dozens of servers, then you may want a higher-level tool such as Fabric, AsyncSSH, or your own thread or queue-based orchestration.

Security and Host Key Verification

One of the worst patterns in SSH examples is automatically accepting unknown host keys.

Bad habit:

python
# client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

That makes demos easy but weakens trust guarantees. In real automation, load known host keys and reject unexpected hosts unless you are deliberately bootstrapping new machines under a controlled process.

Timeouts and Reliability

Network automation fails in messy ways: DNS problems, authentication issues, transient packet loss, hanging commands, or partial output. Use connection timeouts, command timeouts where possible, and structured logging.

A good wrapper should capture:

  • target host
  • command
  • exit code
  • elapsed time
  • standard output and standard error

That turns SSH from an opaque remote action into something you can debug later.

Common Pitfalls

The most common problem is ignoring host key verification and building insecure automation around AutoAddPolicy. Another is assuming that a remote command runs in the same shell environment as an interactive login. Developers also often forget to check exit status and end up treating failed commands as success because some text appeared on standard output. Finally, reconnecting for every small command creates avoidable latency and load.

Summary

  • Use subprocess with the system ssh client for simple reuse of existing SSH configuration.
  • Use paramiko when you need native Python control over authentication and output.
  • Check exit codes, not just command output.
  • Prefer SSH keys over passwords.
  • Keep host key verification enabled and reuse connections when executing multiple commands.

Course illustration
Course illustration

All Rights Reserved.