SSH & Remote Work
SSH keys, tmux, tunnels, and bastion jumps - how Python developers reach staging APIs, tail journald logs, and port-forward databases without leaving sensitive keys on shared machines.
Recipe
ssh -A -J bastion@jump.acme.com deploy@staging-api.internal
tmux new -s triage
sudo journalctl -u billing-api -fWhen to reach for this:
- Incident triage on staging/production VMs
- Port-forward Postgres only reachable in VPC
- Long-running
uv runmigration with disconnect-safe tmux - SCP wheel or config to air-gapped deploy host
Working Example
# ~/.ssh/config
Host bastion
HostName jump.acme.com
User ubuntu
IdentityFile ~/.ssh/id_ed25519
Host staging-api
HostName 10.0.2.15
User deploy
ProxyJump bastion
LocalForward 15432 staging-db.internal:5432
# Connect and forward DB for local psql
ssh staging-api
# other terminal:
psql "postgresql://user@localhost:15432/billing"
# tmux session survives laptop sleep
tmux new -s migrate
uv run alembic upgrade head
# detach: Ctrl-b dWhat this demonstrates:
ProxyJumpvia bastion without manual double sshLocalForwardmaps remote DB to localhost- tmux named session for long migrations
- Identity file per host block in config
Deep Dive
How It Works
- SSH auth - Key-based preferred; password auth disabled on hardened servers.
- Agent forwarding -
-Aforwards agent for git pull on remote - use cautiously. - Tunnels - LocalForward (remote to local), RemoteForward (local to remote), DynamicForward SOCKS.
- tmux - Multiplexes terminals; sessions outlive SSH connection.
Remote Work Checklist
| Step | Command |
|---|---|
| Test connect | ssh -G staging-api dry config |
| Start session | tmux new -s work |
| Follow logs | journalctl -f |
| Copy artifact | scp -J bastion file deploy@host:/tmp/ |
Python Notes
# Remote one-liner health check
ssh staging-api 'curl -sf http://localhost:8000/health'
# Sync env file securely (avoid if secrets in file - use vault)
scp -J bastion .env.example deploy@staging-api:/opt/billing-api/Gotchas
- Agent forwarding to untrusted host - Key abuse if remote compromised. Fix: disable
-Aon prod unless required. - Leaving tmux as root - Confusing shared sessions. Fix: personal tmux on service user account.
- Exposed LocalForward on 0.0.0.0 - DB reachable on cafe WiFi. Fix: bind
127.0.0.1:15432:.... - Prod keys on laptop - Stolen laptop becomes prod access. Fix: short-lived SSO + bastion session manager.
- SSH StrictHostKeyChecking off globally - MITM risk. Fix: known_hosts per environment.
- SCP deprecated quirks - Prefer
rsync -e sshfor large artifact sync.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| AWS SSM Session Manager | No SSH keys on VMs | On-prem only SSH |
| Teleport / Boundary | Audited privileged access | Tiny team bastion OK |
| GitHub Codespaces | Cloud dev environment | Prod data access forbidden |
| VPN | Whole subnet access | Just one DB forward needed |
FAQs
Ed25519 or RSA key?
Ed25519 default for new keys - shorter, secure; RSA for legacy systems only.
ssh-agent on macOS?
Keychain integration --apple-use-keychain stores passphrase securely.
tmux or screen?
tmux modern default; same persistence goal.
Mosh for flaky WiFi?
mosh UDP roaming - nice for travel; requires server package.
Reverse tunnel for demo?
ssh -R 8080:localhost:8000 exposes local FastAPI temporarily - security risk, time-box it.
VS Code Remote SSH?
Uses same SSH config; ensure remote has uv and project cloned.
SFTP vs scp?
sftp interactive; scp one-shot - rsync better for deploy dirs.
Multiple GitHub keys?
Host github.com-work with IdentityFile in ssh config per org.
Recording sessions?
Some compliance requires script or teleport session recording on prod.
IPv6 jump hosts?
Explicit AddressFamily inet if v6 routing broken in corp network.
Related
- Sysadmin Essentials - once connected
- Process & Resource Management - service control
- SSH and Remote Execution - Python fabric/paramiko
- Deploying FastAPI - server layout
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.