SSH Keys and SSH Basics
What SSH is
SSH is how you securely log into remote Linux machines over a network. It encrypts the connection so nothing is readable in transit. It is also how Git connects to remote repositories when using SSH remotes.
What an SSH key is
An SSH keypair has two parts:
- Private key — secret, lives on your machine only. Never share it.
- Public key — safe to copy to servers and Git hosting. Anyone can have it.
When you connect to a server, the server checks whether your public key is in its authorized_keys file. If it is, it lets you authenticate using your private key — no password needed.
Generate a key
Preferred — ED25519
ssh-keygen -t ed25519 -C "you@example.com"
Creates a modern ED25519 SSH keypair. Use this unless you specifically need RSA. -t ed25519 sets the key type. -C adds a comment label so you know what the key is for. Default output: ~/.ssh/id_ed25519 (private) and ~/.ssh/id_ed25519.pub (public).
Fallback — RSA
ssh-keygen -t rsa -b 4096 -C "you@example.com"
Use only if a system requires RSA specifically.
Permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
~/.ssh or the key files are too open (e.g. world-readable), SSH silently ignores them. Permission errors are a very common cause of "Permission denied" failures.
View your public key
cat ~/.ssh/id_ed25519.pub
Shows the public key so you can paste it into a server's authorized_keys or a Git hosting site's SSH key settings.
Start ssh-agent and load your key
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
ssh-add -l
ssh-agent -s starts the agent process and prints shell commands that set the SSH_AUTH_SOCK and SSH_AGENT_PID environment variables. eval executes those commands in the current shell — without eval the variables would only exist in the subshell and your ssh client would never find the agent. ssh-add -l lists loaded keys. The agent means you only type your key passphrase once per session.
Connect to a server
ssh user@host
ssh -i ~/.ssh/id_ed25519 user@host
The first form uses whatever key is loaded in the agent or the default key file. Use -i to specify a key explicitly.
Copy key to a host
Easy way
ssh-copy-id user@host
Installs your public key into the remote user's ~/.ssh/authorized_keys. This is the simplest method if you can already log in with a password.
Manual — pipe remotely
ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys" < ~/.ssh/id_ed25519.pub
ssh user@host "chmod 600 ~/.ssh/authorized_keys"
This pipes your local public key to the remote server's authorized_keys file over SSH. The remote server's file must have permissions 600 — not the local one.
SSH config file
Edit ~/.ssh/config to save connection details by name:
Host myserver
HostName 192.168.1.10
User richard
IdentityFile ~/.ssh/id_ed25519
Then connect with just:
ssh myserver
Host blocks for different servers. This saves typing the full command every time and is essential when you work with many hosts.
known_hosts
~/.ssh/known_hosts stores the fingerprint of every host you have connected to. On the next connection, SSH checks the fingerprint matches. If it does not, SSH refuses to connect and warns you.
Remove a stale entry when a host is rebuilt:
ssh-keygen -R hostname
Common errors
Permission denied (publickey)
Usually means one of:
- Your public key is not in
~/.ssh/authorized_keyson the remote host - The wrong private key is being used
- Wrong username
- Bad permissions on
~/.ssh/, the key file, orauthorized_keys - Key is not loaded in
ssh-agent
REMOTE HOST IDENTIFICATION HAS CHANGED
The stored fingerprint for this host does not match. This happens when:
- The server was rebuilt
- DNS points to a different machine now
- SSH host keys were regenerated
Fix it by removing the old entry:
ssh-keygen -R hostname
Debugging connections
When SSH refuses a connection and the error is not obvious, run with verbose output:
ssh -vvv user@host
-v adds one level of debug output. -vvv gives the most detail. In the output, look for:
- Which key files are being tried — look for lines like
Trying private key: /home/user/.ssh/id_ed25519 - Where auth fails — look for
Authentications that can continue: publickeyfollowed byPermission denied - Whether the key was accepted — look for
Server accepts key - Whether the agent offered the key — look for
Offering public key: ...
ssh -vvv is the first tool to reach for when you get a vague "Permission denied (publickey)" and can not tell why.
Jump hosts / bastion
A jump host (or bastion) is an intermediate server you must go through to reach machines on a private network. Instead of SSH-ing to the bastion and then SSH-ing again, you can chain the connection in one command:
ssh -J bastion.example.com user@internal-server
SSH connects to the bastion first, then tunnels onward to the internal server. You only need one command.
Better still, put this in your ~/.ssh/config so you never have to type it:
Host internal-server
HostName 10.0.0.5
User richard
ProxyJump bastion.example.com
Host bastion.example.com
User richard
IdentityFile ~/.ssh/id_ed25519
After this, ssh internal-server routes through the bastion automatically.
authorized_keys on both the bastion and the internal server, unless the bastion is configured for agent forwarding.
Configuring sshd (server-side)
Most SSH problems are client-side, but sometimes you need to harden or change the server daemon configuration. The main file is /etc/ssh/sshd_config.
# Key settings in /etc/ssh/sshd_config
PubkeyAuthentication yes # allow key-based login (usually default)
PasswordAuthentication no # disable password login once keys are set up
PermitRootLogin no # never allow direct root SSH in production
AllowUsers alice bob deploy # whitelist of users who can SSH in
AllowGroups sshusers # or restrict by group
# After changes, test config then reload — do NOT restart with active sessions
sshd -t # test config syntax
systemctl reload sshd # RHEL (sshd.service)
systemctl reload ssh # Debian (ssh.service)
sshd -t before reloading. A syntax error in sshd_config can lock you out of the server if you restart (rather than reload). Keep an existing session open until you have confirmed the new session works.
Port forwarding
SSH can forward network connections through an encrypted tunnel — useful for reaching services on private networks or for debugging without opening firewall rules.
# Local forwarding: access remote-host:5432 as localhost:5432 on YOUR machine
ssh -L 5432:localhost:5432 user@remote-host
# Now: psql -h localhost -p 5432 connects through the tunnel
# Forward through a jump host to a database on an internal server
ssh -L 5432:db-server:5432 user@bastion.example.com
# Remote forwarding: expose localhost:8080 on the remote server's port 9090
# Useful for sharing a local dev service with someone on the remote
ssh -R 9090:localhost:8080 user@remote-host
# Dynamic (SOCKS proxy): turn SSH into a SOCKS5 proxy on localhost:1080
# Configure your browser/tool to use SOCKS5 proxy 127.0.0.1:1080
ssh -D 1080 user@remote-host
# Add -N to not open a shell (just the tunnel), -f to background
ssh -N -f -L 5432:localhost:5432 user@remote-host
Local forwarding (-L) is by far the most common — it brings a remote service to your local machine. The format is -L local_port:target_host:target_port where target_host is resolved from the SSH server's perspective.
ControlMaster — connection reuse
Opening multiple SSH connections to the same host (common with Ansible or repeated scp) is slow due to repeated authentication. ControlMaster reuses an existing connection.
# Add to ~/.ssh/config
Host *
ControlMaster auto
ControlPath ~/.ssh/cm-%r@%h:%p
ControlPersist 10m
ControlMaster auto: use an existing multiplexed connection if available, or create one. ControlPath: the socket file path (one per user@host:port). ControlPersist 10m: keep the master connection alive for 10 minutes after the last client disconnects, so the next ssh or ansible run connects instantly without re-authenticating.
# Check active multiplexed connections
ssh -O check user@remote-host
# Close a multiplexed connection manually
ssh -O stop user@remote-host