Secure Shell (SSH) gives you an authenticated, encrypted way to run commands, copy files, forward ports, and use Git over untrusted networks. You will use it to deploy, debug, and automate. This guide gives you a practical baseline: install a client, create and protect keys, shape your ~/.ssh/config
, forward ports, and harden the server—then tie it to your daily web workflow with Git and file transfer. By the end, you should be able to connect quickly and safely, and know where to tighten the bolts further.
Install a modern SSH client
Use OpenSSH everywhere. macOS and most Linux distros ship it. Windows 10/11 include an official OpenSSH client and server you can add via “Optional Features” or PowerShell; Microsoft documents installation and Windows-specific notes.
Verify the version. Aim for OpenSSH ≥ 8.2 to use FIDO2 (hardware) keys and newer defaults. Check with ssh -V
; consult OpenSSH release notes for feature changes.
Once OpenSSH is available, your next step is identity—keys.
Generate a key you can trust
SSH uses asymmetric keys. You keep the private key; you place the public key on servers or developer services (e.g., GitHub/GitLab). On any platform with OpenSSH:
# Recommended software key
ssh-keygen -t ed25519 -C "you@example.com"
# Optional: hardware-backed key (FIDO2 security key required)
ssh-keygen -t ed25519-sk -C "you@example.com"
Ed25519 is compact and fast; the -sk
variants produce keys tied to a FIDO2 authenticator and require a physical touch per use, which mitigates key theft on a compromised workstation. See ssh-keygen(1)
and vendor guidance for FIDO2 details.
Protect the private key with a passphrase and load it into an agent:
# start the agent and add your key
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
Use agent forwarding sparingly; a compromised jump host can request signatures from your agent while your session is active. Favor per-host enabling, and disable by default.
With a key in hand, you need it on the remote side.
Install your public key on servers
The simplest path is ssh-copy-id
:
ssh-copy-id user@host
It logs in once with your password and appends your public key to ~/.ssh/authorized_keys
on the server. Man pages explain the exact behavior and flags.
Permissions matter. OpenSSH refuses to use keys if directories/files are too open:
# client side (your machine)
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
# server side (the account you log into)
chmod go-w ~/
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
On Windows, fix private-key ACLs via the file’s Security → Advanced dialog so only your user has access. Common “too open” errors and minimum modes are well-documented across platforms.
Next, you will want to stop typing long commands.
Tame connections with ~/.ssh/config
Create ~/.ssh/config
(600) and describe hosts with clear aliases. The OpenSSH client reads options in order and applies the first match; the manual lists every directive.
Host web-prod
HostName prod.example.com
User deploy
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
ServerAliveInterval 30
ServerAliveCountMax 3
# Jump through a bastion (ProxyJump)
Host api-*
User ubuntu
ProxyJump bastion.example.com
# Reuse TCP connections (multiplexing) for speed
Host *
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 10m
ProxyJump
simplifies traversing bastions; ControlMaster/ControlPersist
multiplex multiple sessions over one TCP connection to reduce latency.
With these basics, connecting becomes a short alias: ssh web-prod
. Now make the tunnel your web stack needs.
Forward ports for local development and admin
SSH can tunnel TCP safely. Three patterns matter:
- Local forwarding exposes a remote service on your laptop:
ssh -L 5432:127.0.0.1:5432 db@db.internal
then connect tolocalhost:5432
. - Remote forwarding exposes a local service to a remote host:
ssh -R 9000:127.0.0.1:3000 user@host
then accesshost:9000
remotely. - Dynamic forwarding (SOCKS) creates a local proxy for arbitrary connections:
ssh -D 1080 user@jumphost
, then point your app/browser atsocks5://localhost:1080
.
Authoritative guides explain each mode and when to use them; many teams use local forwarding for databases and dynamic forwarding to reach private subnets during incident response.
With connectivity solved, you need to move code and artifacts, and you likely use Git.
Use SSH with Git and CI
Register your public key with your forge, then set remotes to ssh://
or the short git@host:org/repo.git
. GitHub and GitLab document setup, key testing, and commit signing over SSH. Hardware-backed keys also work once your OpenSSH and token support ed25519-sk/ecdsa-sk
.
For CI/CD runners, prefer deploy keys or per-job ephemeral keys instead of reusing a personal key, and rotate regularly in line with NIST IR 7966’s key-lifecycle guidance.
Treating keys as credentials with a lifecycle reduces blast radius in compromised pipelines.
Copy files the right way
Use sftp
or rsync
over SSH for reliable transfers:
# one-off copy
scp ./build.tgz deploy@web-prod:/var/www/
# efficient sync with permissions and deletion
rsync -avz --delete ./public/ deploy@web-prod:/var/www/public/
Both ride the same SSH authentication you already configured; rsync
minimizes bandwidth by sending deltas.
Next, harden the server side so your habits align with production controls.
Harden the SSH server baseline
On Linux or Windows Server with OpenSSH, make these defaults explicit in sshd_config
:
- Disable root logins:
PermitRootLogin no
(orprohibit-password
where appropriate). - Disable password auth once keys are working for all admins:
PasswordAuthentication no
and, if used,ChallengeResponseAuthentication no
. - Ensure
PubkeyAuthentication yes
.
These measures reduce credential-stuffing risk and enforce key-based access.
For Windows Server, Microsoft provides sshd
configuration specifics; apply equivalent controls and test with a break-glass account before cutting over.
With policy tightened, consider operational hygiene and troubleshooting.
Operate safely day-to-day
Prefer least-privilege accounts plus sudo
. Keep known_hosts current to detect host key changes. For agent use, forward only to trusted hosts and only when required; Teleport’s guidance and security community consensus highlight the risk of forwarding through untrusted intermediaries.
If your org manages many keys, adopt lifecycle controls—provision, inventory, rotate, and remove keys systematically—as urged by NIST IR 7966 and its bulletin. Treat unused keys as liabilities.
Next, here are the fastest fixes for common errors.
Troubleshoot quickly
- “Permissions are too open” on private keys: set
~/.ssh
to700
and private keys to600
; on Windows, remove inherited ACLs and grant only your user. - “Permission denied (publickey)” on the server: tighten
~/
(go-w
),~/.ssh
(700
), andauthorized_keys
(600
); confirm you copied the public key and are pointing the client at the private key. Usessh -vvv
to see which identities are tried. - Slow connects or repeated prompts: enable multiplexing (
ControlMaster/ControlPersist
) andIdentitiesOnly yes
to avoid trying every key in your agent.
With reliability and security in hand, add two upgrades when your risk model demands them.
Two valuable upgrades
- Hardware-backed keys (FIDO2). They require a physical touch, can enforce a PIN, and keep the private key in hardware—ideal for admins and high-value deployers. Generate with
ssh-keygen -t ed25519-sk
and enroll on GitHub/GitLab and servers that support it. - Jump hosts done right. Replace ad-hoc agent forwarding with
ProxyJump
and per-host policies in~/.ssh/config
. This keeps credentials local while still traversing private networks.
Quick Windows notes for web teams
Windows’ built-in OpenSSH behaves like the Unix client. Install via Features or PowerShell, then manage keys and ~/.ssh/config
in your user profile (typically C:\Users\<you>\.ssh
). Microsoft’s docs cover installation, key management, and server configuration details and gotchas.
A minimal starter ~/.ssh/config
# ~/.ssh/config (chmod 600)
Host bastion
HostName bastion.example.com
User ops
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
Host web-*
User deploy
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 30
ServerAliveCountMax 3
Host *
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 10m
Usage: ssh web-01
, rsync -avz ./public/ web-01:/var/www/public/
, ssh -L 5432:127.0.0.1:5432 web-01
.
Key takeaways
Start with OpenSSH and an Ed25519 key; prefer hardware-backed keys for privileged access. Store public keys on servers with ssh-copy-id
, keep permissions strict, and codify hosts in ~/.ssh/config
with ProxyJump
and multiplexing. Use SSH for port forwarding, Git, and file transfer; then harden sshd
to enforce keys and restrict root logins. Finally, manage key lifecycle as a first-class security control, not an afterthought. These practices reduce toil today and cut incident risk tomorrow.