Start here
Make a key
ssh-keygen -t ed25519Install it on a server
ssh-copy-id user@hostConnect on another port
ssh -p 2222 user@hostCopy a file down
scp user@host:/path .Reach a port through the server
ssh -L 8080:localhost:80 user@hostHost key changed warning
ssh-keygen -R host
This works from Windows too. Windows has its own OpenSSH client, so
ssh, scp, sftp and ssh-keygen run in PowerShell with the same syntax and nothing here needs PuTTY. Confirm it with ssh -V; if the command is not recognised, add it with Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0. The walk-through is in Connect to a Linux server from Windows.
Connecting
| Task | Command |
|---|---|
| Basic connection | ssh user@host |
| Non-standard port | ssh -p 2222 user@host |
| Pick a specific key | ssh -i ~/.ssh/id_ed25519 user@host |
| Run one command and exit | ssh user@host "df -h" |
| Through a jump host | ssh -J jump@bastion user@target |
| Why is it refusing me | ssh -v user@host, more detail with -vvv |
| Accept a new host key without prompting | ssh -o StrictHostKeyChecking=accept-new user@host |
| Ignore the agent and any config, for testing | ssh -o IdentitiesOnly=yes -i key user@host |
| Forget a changed host key | ssh-keygen -R host |
| Look up what is stored for a host | ssh-keygen -F host |
Keys
| Task | Command |
|---|---|
| Create a modern key | ssh-keygen -t ed25519 -C "zaur@workstation" |
| Create an RSA key, for old systems | ssh-keygen -t rsa -b 4096 -C "zaur@workstation" |
| Key in a specific file | ssh-keygen -t ed25519 -f ~/.ssh/id_srv01 |
| Install the public key on a server | ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host |
| See what it would do first | ssh-copy-id -n user@host |
| Fingerprint of a key | ssh-keygen -lf ~/.ssh/id_ed25519.pub |
| Recover the public key from a private one | ssh-keygen -y -f ~/.ssh/id_ed25519 |
| Change or add a passphrase | ssh-keygen -p -f ~/.ssh/id_ed25519 |
| Load a key into the agent | ssh-add ~/.ssh/id_ed25519 |
| What is loaded right now | ssh-add -l |
| File | Required permissions on Linux |
|---|---|
~/.ssh | 700 |
~/.ssh/id_ed25519 | 600 |
~/.ssh/id_ed25519.pub | 644 |
~/.ssh/authorized_keys | 600 |
~/.ssh/config | 600 |
Wrong permissions look like a wrong key. OpenSSH silently refuses a private key that others can read, and refuses key authentication when the home directory or
~/.ssh is group writable. The error says permission denied, never “fix your permissions”. ssh -v shows which key was offered and rejected.
The config file
Stop retyping ports, users and keys. This lives in ~/.ssh/config, and on Windows in C:\Users\you\.ssh\config.
Host srv01
HostName 10.0.0.21
User zaur
Port 2222
IdentityFile ~/.ssh/id_srv01
Host *.dmz
ProxyJump bastion
ServerAliveInterval 60
Host bastion
HostName bastion.example.com
User jump
IdentitiesOnly yes
| Directive | What it does |
|---|---|
HostName | The real address. Host is just the nickname you type. |
IdentityFile | Which key to offer |
IdentitiesOnly yes | Offer only that key, not everything in the agent |
ProxyJump | Go through a bastion, same as -J |
ServerAliveInterval 60 | Keep idle sessions from being dropped by a firewall |
ForwardAgent yes | Let the next hop use your local keys. Use sparingly. |
StrictHostKeyChecking | yes, no or accept-new |
The config applies to scp and rsync too. Once
srv01 is defined, scp file srv01:/tmp/ and rsync -av dir/ srv01:/backup/ both inherit the port, user and key. That is the real payoff.
Moving files
| Task | Command |
|---|---|
| Upload a file | scp report.csv user@host:/tmp/ |
| Download a file | scp user@host:/var/log/messages . |
| A whole directory | scp -r dist/ user@host:/opt/app/ |
| Non-standard port, note the capital P | scp -P 2222 file user@host:/tmp/ |
| Interactive transfer | sftp user@host |
| Sync a tree, resumable and incremental | rsync -avh --progress dir/ user@host:/backup/ |
| Mirror, deleting what is gone | rsync -avh --delete dir/ user@host:/backup/ |
| See what it would do first | rsync -avhn --delete dir/ user@host:/backup/ |
| rsync over a non-standard port | rsync -avh -e "ssh -p 2222" dir/ user@host:/backup/ |
The trailing slash decides everything in rsync.
dir/ copies the contents of dir, dir copies the directory itself into the target. Combined with --delete, getting it wrong nests or wipes a tree. Always dry run with -n first.
Tunnels
| Goal | Command |
|---|---|
| Reach a service that only the server can reach | ssh -L 8080:10.0.0.50:80 user@host |
| Reach something on the server itself | ssh -L 5432:localhost:5432 user@dbhost |
| Expose your local service to the server | ssh -R 9000:localhost:3000 user@host |
| SOCKS proxy for a browser | ssh -D 1080 user@host |
| Tunnel without a shell session | ssh -N -L 8080:localhost:80 user@host |
| Send it to the background | ssh -f -N -L 8080:localhost:80 user@host |
Read -L left to right. The first port is on your machine, the rest is what the server will connect to on your behalf. So
-L 8080:10.0.0.50:80 means your localhost:8080 arrives at port 80 on 10.0.0.50, as seen from the server. This is how you reach an iLO or a vCenter that is not routable from your desk.
The server side
| Task | Command or setting |
|---|---|
| Config file | /etc/ssh/sshd_config, plus drop-ins in sshd_config.d/ |
| Check the config before restarting | sshd -t |
| Apply changes, RHEL family and Photon OS | systemctl restart sshd |
| Apply changes, Debian and Ubuntu | systemctl restart ssh |
| Turn off password logins | PasswordAuthentication no |
| Keys only, explicitly | PubkeyAuthentication yes |
| Root login policy | PermitRootLogin no or prohibit-password |
| Limit who may connect | AllowUsers zaur svc-backup |
| Change the port | Port 2222, and open it in the firewall first |
| Who is logged in now | who, or ss -tnp state established '( dport = :22 or sport = :22 )' |
| Failed attempts, RHEL family | journalctl -u sshd -p err or /var/log/secure |
| Failed attempts, Debian and Ubuntu | journalctl -u ssh or /var/log/auth.log |
Keep the session open. Test a changed sshd config from a second terminal before closing the one you are in.
sshd -t catches syntax errors, but it will not tell you that AllowUsers just excluded you, and on a remote server that mistake needs console access to undo.
Windows and VMware specifics
| Situation | What to do |
|---|---|
| Where the client keys live on Windows | C:\Users\you\.ssh\ |
| Agent on Windows | Set-Service ssh-agent -StartupType Automatic, then Start-Service ssh-agent and ssh-add |
| Key file rejected as too open on Windows | Remove inherited rights: icacls key /inheritance:r /grant:r "$env:USERNAME:R" |
| Install the OpenSSH server on Windows | Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 |
| Keys for a normal user on Windows server | C:\Users\user\.ssh\authorized_keys |
| Keys for an administrator on Windows server | C:\ProgramData\ssh\administrators_authorized_keys |
| Enable SSH on an ESXi host | vim-cmd hostsvc/enable_ssh, or from the host UI. Disable it again afterwards. |
| Root key on ESXi | /etc/ssh/keys-root/authorized_keys |
| Shell on the vCenter appliance | Connect, then type shell to leave the appliance shell |
The administrators_authorized_keys trap. On Windows Server, a key for any account in the Administrators group is read from that single ProgramData file, not from the user’s profile, and it must be owned by Administrators or SYSTEM with inheritance removed. A key that works for a standard user and fails for an admin is almost always this.
FAQ
Permission denied (publickey) and the key is definitely right.
Run
ssh -v and read which keys were offered. The usual causes are file permissions on the client, a home directory that is group writable on the server, SELinux context on ~/.ssh after the file was copied in, or the server simply not listing that user in AllowUsers. On the RHEL family, restorecon -Rv ~/.ssh fixes the SELinux case.
Warning about a changed host key. Is it an attack?
Usually the server was rebuilt or you are reaching a different node behind the same address. Verify the fingerprint out of band if the machine matters, then clear the old entry with
ssh-keygen -R host. Never disable host key checking permanently to make the message go away.
The session dies after a few minutes of inactivity.
A firewall or NAT device is timing out the idle connection. Set
ServerAliveInterval 60 in your client config, which is friendlier than changing the server. For long jobs, run them under tmux or screen so a dropped session does not kill the work.
ed25519 or RSA?
ed25519 by default: shorter, faster and supported everywhere modern. Keep a 4096-bit RSA key for old appliances and network gear that predate it. Both can sit in the agent, and
IdentityFile in the config picks the right one per host.
Can I avoid typing the passphrase every time?
That is what the agent is for:
ssh-add once per session on Linux, or the ssh-agent service on Windows. A key with no passphrase at all is a file that grants access to anyone who copies it, which is a reasonable trade only for a tightly scoped automation account.