Python SFTP: Paramiko, pysftp, and a Way That Needs Neither
Python has no SFTP in its standard library, so every Python SFTP script reaches for something. Here is working code for paramiko, the library almost every one of them is built on, the reasons to skip pysftp, and a third way where Files.com makes the SFTP connection on the cloud side and your Python only talks HTTPS, plus the parts the snippets skip: keys, host verification, credentials in a scheduler, and failing loudly.
Python SFTP code has a habit of becoming infrastructure. It starts as a small cron job that sends a nightly CSV to a vendor. Then it becomes an Airflow task that collects a partner's files. Before long, the "temporary" script someone wrote in 2019 is part of the finance close and nobody wants to touch it.
Python has no SFTP in its standard library, so every one of those scripts reaches for something, and there are three real choices: paramiko, the library behind almost all Python SFTP code; pysftp, the wrapper people find first and regret later; and moving the SFTP connection out of Python entirely by letting Files.com make it.
Here is working code for each, along with the details that short samples leave out: SSH keys, host verification, credentials on a scheduler, and making a failed transfer fail loudly.
Paramiko, the library underneath everything
Paramiko is a Python implementation of SSH. Its SFTPClient class provides the upload, download, directory listing, and file-management operations most scripts need, and it is what most other Python SFTP tools wrap. Install it with pip:
pip install paramiko
Here is a complete upload with a username and password. The password comes from an environment variable rather than the source code:
import os
import paramiko
client = paramiko.SSHClient()
client.load_system_host_keys()
try:
client.connect(
hostname="sftp.example.com",
port=22,
username="alice",
password=os.environ["SFTP_PASSWORD"],
)
with client.open_sftp() as sftp:
sftp.put("/data/exports/orders.csv", "/inbound/orders.csv")
finally:
client.close()
Four calls do the work. SSHClient() creates the SSH client. load_system_host_keys() loads the server keys the account already trusts, normally from ~/.ssh/known_hosts. connect() opens the connection and authenticates. open_sftp() starts the SFTP session, and put(local_path, remote_path) uploads the file. The with block closes the SFTP session even if the upload fails, and the finally block closes the SSH connection underneath it, so a failed transfer never leaks a session on the server.
Downloads and directory listings use the same SFTPClient:
with client.open_sftp() as sftp:
for name in sftp.listdir("/outbound"):
print(name)
sftp.get("/outbound/report.csv", "/data/imports/report.csv")
Authenticate with an SSH key
An unattended job wants a key, not a password. Pass the private key's path as key_filename, and turn off paramiko's habit of hunting through ~/.ssh for any key it can find:
client.connect(
hostname="sftp.example.com",
port=22,
username="alice",
key_filename="/etc/sftp-keys/brightway_ed25519",
passphrase=os.environ.get("SFTP_KEY_PASSPHRASE"),
look_for_keys=False,
allow_agent=False,
)
look_for_keys=False stops paramiko from trying unrelated keys in the account's ~/.ssh, and allow_agent=False stops it from using whatever is loaded into an SSH agent. Together they make a scheduled job predictable: it authenticates with the key you configured, not whichever key happens to be on the machine.
If the key is encrypted, the passphrase comes from a secret store or an environment variable, never from the script. If you have not set up a key pair before, the walkthrough on setting up SSH keys covers generating one and handing the public half to the server's administrator.
Do not disable host key verification
When paramiko meets a server it has never seen, it refuses to connect, because its default policy is RejectPolicy. That is the correct behavior, and almost every tutorial on the internet overrides it with one line:
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
Do not ship that line. AutoAddPolicy trusts any host key the first time it sees it, which means a DNS change, redirected traffic, or the wrong server answering at that address hands your file to a machine you did not mean to talk to. Host keys exist to catch exactly that.
Keep the default RejectPolicy and get the key into known_hosts the way SSH itself does it. Sign in as the operating-system account that will run the job, connect once with an SSH or SFTP client, compare the fingerprint the server presents with one the server's administrator gave you over a separate channel, and save it.
From then on load_system_host_keys() finds the trusted key. If the server's key ever changes, the job fails instead of quietly connecting to a different machine, and an administrator verifies the new fingerprint before anyone accepts it. That failure is the feature.
pysftp, the wrapper to skip
Search "python sftp" and pysftp comes up first. It wraps paramiko in a smaller API, and its examples are three lines long. It is still the wrong choice for new code, for two reasons.
The most recent pysftp release is 0.2.9, from July 2016. It has not kept pace with paramiko, and pairing old pysftp code with a current paramiko release produces compatibility errors that have nothing to do with your application.
The three-line examples also work by disabling host key checking. The idiom you will see everywhere is:
cnopts = pysftp.CnOpts()
cnopts.hostkeys = None
That is AutoAddPolicy with extra steps, and it carries the same risk. A slightly shorter script is not worth an unmaintained dependency and a disabled security control. Use paramiko directly, and if you want a smaller interface, put the four or five operations your application needs behind a short helper function you understand.
Let Files.com make the SFTP connection
A paramiko script does more than move a file. It creates an operational checklist: keep paramiko patched, store and rotate the partner's key or password, maintain the partner's host key, allow outbound port 22, write retries and transfer recovery, collect logs and build alerts, and change the script when the partner changes servers or credentials.
For one integration that is reasonable. For ten or fifty partners it is infrastructure hidden inside application code.
The third option takes the SFTP connection out of Python. Files.com makes it on the cloud side, and your Python only talks to Files.com over HTTPS.
Connect the partner's server with a Remote Server Mount
A Remote Server Mount maps an empty folder on your Files.com site to another SFTP server. Mount a partner's server at /partners/brightway and that folder becomes a live window onto the partner's machine. Uploading a file writes it to the partner's server as it arrives. Downloading reads from it, listing shows the remote directory, and a rename, delete, or new subfolder happens on the partner's side. Files.com keeps no copy.
The partner's hostname, port, username, and authentication method are configured once in Files.com rather than in every Python job. Files.com authenticates with a password, a private key, or both, and it can generate the key pair itself (RSA, 4096-bit): you send the public half to the partner's administrator and the private half never leaves Files.com. An existing key can be supplied in OpenSSH, PuTTY, or SSH2 format, and every credential is stored encrypted.
Host verification is handled at the mount, the way the paramiko section said to handle it, without the script. Files.com detects and stores the partner's host key on the first connection, or an administrator enters the fingerprint the partner supplied. If that key ever changes, Files.com disables the connection until an administrator confirms the new one. The partner's only task is to allowlist the Files.com IP addresses in its firewall.
Your Python talks HTTPS
With the mount in place, the script needs no SFTP library, no private key, and no access to port 22. It uses the Files.com Python SDK over HTTPS with an API key:
pip3 install Files.com
import os
import files_sdk
files_sdk.set_api_key(os.environ["FILES_API_KEY"])
files_sdk.file.upload_file(
"/data/exports/orders.csv",
"/partners/brightway/inbound/orders.csv",
)
The upload travels to Files.com over HTTPS, and Files.com writes it through the mount to the partner's SFTP server. Listings and downloads use the same mounted path:
for item in files_sdk.folder.list_for("/partners/brightway/outbound"):
print(item.path)
files_sdk.file.download_file(
"/partners/brightway/outbound/report.csv",
"/data/imports/report.csv",
)
The machine running Python no longer knows SFTP. The partner's private key is not on it, the host key is managed centrally, the SDK retries and resumes interrupted transfers, and every operation lands in the Files.com audit log with a user, a file, and a timestamp. If your site runs on a custom domain, set files_sdk.base_url once.
When a partner rotates its credentials or moves to a new server, an administrator updates the mount, and the Python code and the mounted path stay exactly as they are.
You may not need Python at all
Once the partner's server is a folder on Files.com, some scheduled scripts turn into configuration. A Files.com Sync pushes files to the remote or pulls them from it on a schedule you set, and an Automation acts on what arrives.
The documented pattern: a partner uploads invoices to its own SFTP server, Files.com pulls them every ten minutes, and a Move Files automation routes each one to the right processing folder, with the transfer and every file operation recorded in the audit log.
That removes the cron job, its credentials, and its hand-written retry loop. Python stays for the workflow steps that carry your business logic, and the platform handles the transfer.
Your paramiko script keeps working too
You do not have to replace an existing paramiko script to use this model. Files.com also accepts inbound SFTP on port 22 with SSH key authentication, like any hosted SFTP server.
Point the script from the first section at yourcompany.files.com, put into /partners/brightway/inbound, and Files.com proxies the operation out to the partner over SFTP. The script keeps one stable credential for one server that never changes, while the partner's credentials, host key, hostname, and firewall arrangements live on Files.com.
Ten partners are ten mounts and one code path. A partner whose SFTP server sits inside a private network is reachable through the Files.com Agent, which connects outbound so nobody opens an inbound firewall port.
Make scheduled transfers fail loudly
Most Python SFTP scripts end up under cron, Airflow, a container scheduler, or Windows Task Scheduler, and that environment differs from the shell where the script was tested. The job may run as another user. known_hosts is per user, key permissions may block the scheduler's account, environment variables from your terminal may not exist, and the working directory may be somewhere else. Test the script as the exact account that will run it.
Then let failures surface. Do not catch an exception unless you can do something useful with it: an uncaught exception exits Python with a nonzero status, which is how the scheduler learns something went wrong. If you need a log entry, log and re-raise:
import logging
try:
run_transfer()
except Exception:
logging.exception("SFTP transfer failed")
raise
A failed transfer produces three things: a nonzero exit status, a useful log line with a timestamp, and an alert to someone who can act on it. A transfer that fails silently is worse than one that never ran.
Which one to use
Paramiko is the general answer for any SFTP server when the transfer belongs inside Python's own logic and the team is prepared to manage keys, host verification, retries, and monitoring.
Skip pysftp.
The Files.com mount is the answer when you would rather not run SFTP in Python at all, when the far end is already Files.com, when the same job has to reach several partners, or when the transfer could become a scheduled Sync with no code.
If Python is not the language for the job, the same three ways are covered for PowerShell, Java, C#, and Node.js, and the SFTP automation guide covers bash and cron.
When scripts stop being the right answer
A few Python transfer jobs are easy to understand. Dozens, spread across servers, schedulers, repositories, and owners, are not. The signs are familiar: nobody knows which machine runs a transfer, a key rotation needs a code change, the same retry logic has been copied into several scripts, and the partner discovers failures before monitoring does.
At that point the question is no longer how to upload a file with Python. It is how to operate file transfers as a reliable service.
That is the point where teams move the transfers onto a platform that runs them. Files.com is the cloud-native File Orchestration Platform: one platform that replaces the stack of legacy tools IT teams run to move files, including the SFTP servers, the scripts that feed them, and the schedulers holding it all together. It speaks every protocol, connects 50+ cloud and on-prem systems, automates every transfer, and keeps a complete audit trail.
For the job in this post, that means the nightly upload becomes a Files.com automation with retries and a delivery record, and Python stays for the logic that is genuinely yours, calling the SDK. If the SFTP server has to stay in your own data center, the ExaVault appliance is a free SFTP server for up to 50 users that every example above can target.
Start a free Files.com trial and point the SDK at it. No credit card required, and an SFTP endpoint with SSH key support is live in minutes.
Frequently asked questions
Does Python have built-in SFTP support?
No. Python's standard library includes ftplib for FTP, but SFTP is a different protocol built on SSH and nothing in the standard library speaks it. Install paramiko for a native Python SFTP client, or use the Files.com SDK over HTTPS and let Files.com make the SFTP connection.
How do I upload a file over SFTP in Python?
With paramiko, create an SSHClient, call load_system_host_keys(), connect() with a key file, open_sftp(), then put(local_path, remote_path), and close the client in a finally block. With Files.com, call files_sdk.file.upload_file(local_path, remote_path) over HTTPS, and a Remote Server Mount carries the file on to the partner's SFTP server.
Is pysftp still maintained?
No. The latest pysftp release, 0.2.9, was published in July 2016, and it has not kept pace with paramiko since. Use paramiko directly for new Python SFTP code, or the Files.com SDK.
How do I use an SSH key with paramiko?
Pass the private key's path as key_filename to SSHClient.connect(), set look_for_keys=False and allow_agent=False so paramiko uses only that key, and supply the passphrase through the passphrase argument from a secret store or environment variable if the key is encrypted.
Is AutoAddPolicy safe?
Not for anything unattended. AutoAddPolicy trusts an unknown host key on first contact, which defeats the point of host keys. Connect once interactively to verify the fingerprint and populate known_hosts, then leave paramiko's default RejectPolicy in place.
Can Files.com connect to my partner's SFTP server for me?
Yes. A Remote Server Mount connects a folder on your Files.com site to any SFTP server in real time. Files.com holds the credentials, can generate the key pair, pins the partner's host key, and passes every operation through. Your Python uploads to Files.com over HTTPS with the SDK, or a scheduled Sync moves the files with no code at all.