Module 13: SSH and Remote Access
🎯 Goal: Connect to remote Linux machines securely and predictably using SSH keys, hosts, and scoped configuration.
By the end of this module, you should be able to generate and protect a key pair, verify a host's identity, use an SSH config alias, transfer files safely, and explain why private keys must never be copied into source control, chat, or a server you do not trust.
Why this matters
Almost nothing you manage professionally sits on the machine in front of you - servers, cloud VMs, Kubernetes nodes, and container hosts are all reached remotely, and SSH is the near-universal way to do that securely. Once you get to the Docker and Kubernetes parts of this curriculum, you'll be SSHing into hosts and copying files around constantly, so getting comfortable with key pairs, ssh, and scp now removes a huge amount of friction later.
Concepts
The first connection is a trust decision
SSH encryption protects a connection only if you verify the server you intended to reach. Read the host fingerprint on first connect and confirm it through a trusted channel for real systems. Keep private keys private, prefer passphrases and an agent, and use a separate key or restricted account when a project requires different access boundaries.
What SSH is and why it replaced telnet. SSH (Secure Shell) is a protocol for getting a remote command-line session on another machine over a network, the same way you've been getting a local shell on your own machine since module 01. Older tools like telnet did the same basic job but sent everything - including your password - as plain, unencrypted text over the network, so anyone watching the traffic could read it. SSH encrypts the entire session: your login, your keystrokes, and everything the remote machine sends back. This is why SSH is the default today and telnet is essentially never used for remote administration anymore.
Client and server. SSH has two sides: the client (the ssh command you run to connect out to somewhere) and the server (a background service, sshd, that listens for incoming connections and is what actually lets a machine be connected into). Ubuntu ships with the SSH client already installed, which is why you can ssh out to other machines immediately. The server is a separate package you install only if you want this machine to accept incoming SSH connections.
Password vs. key-based authentication. When you ssh into a machine, you need to prove who you are. The simplest way is a password, typed each time. The stronger and more convenient way, used almost everywhere professionally, is a key pair: a mathematically related pair of files, a private key (which never leaves your machine and must be kept secret) and a public key (which you can hand out freely, because it's only useful for verifying, not impersonating, you). You put your public key on the remote machine; when you connect, the remote machine challenges your client to prove it holds the matching private key, without the private key or a password ever crossing the network. This is why file permissions on your private key matter so much (see below) - anyone who obtains it can pretend to be you.
Your machine (client) Remote machine (server)
┌─────────────────────┐ ┌─────────────────────┐
│ ~/.ssh/id_ed25519 │ │ ~/.ssh/ │
│ (PRIVATE — never │ │ authorized_keys │
│ leaves this box) │ │ (holds your PUBLIC │
│ │ │ key, one per line) │
│ ~/.ssh/id_ed25519.pub │──copied───►│ │
│ (public — safe to once, via │ sshd challenges the │
│ hand out) ssh-copy-id │ client to prove it │
└─────────────────────┘ │ holds the matching │
│ private key — the │
ssh alice@host ───────────────►│ private key itself │
(connection attempt) │ never crosses the wire │
└─────────────────────┘
Why the permissions on ~/.ssh and your private key matter. You already learned in module 03 that Linux permissions control who can read/write/execute a file. SSH is unusually strict about this: if your private key file or your ~/.ssh directory are readable by anyone other than you, SSH will refuse to use them and print a warning, because a private key readable by other users defeats the entire point of it being private. The convention is 700 (owner: read/write/execute, nobody else: anything) for the ~/.ssh directory, and 600 (owner: read/write, nobody else: anything) for private key files.
authorized_keys and how key-based login actually gets granted. On the machine you're connecting to, there's a file at ~/.ssh/authorized_keys (one line per public key) listing which public keys are allowed to log in as that user. Adding your public key there is what grants you access - there's no separate "user database" for SSH keys beyond this file. ssh-copy-id automates appending your public key to that file over an existing (usually password-based) connection, so you don't have to do it by hand.
~/.ssh/config for host aliases. Typing full ssh user@long-hostname-or-ip -p 2222 -i /path/to/key every time gets tedious. The ~/.ssh/config file lets you define a short alias (like myserver) that bundles up the hostname, username, port, and key file, so you can just type ssh myserver. This is a client-side convenience file only - it doesn't affect the server at all.
WSL2's networking reality for SSH practice. WSL2 runs behind a NAT (network address translation) layer on your Windows host, meaning other machines on your network generally cannot initiate a connection into your WSL2 instance by default - this was touched on in module 10's networking notes. Practically, this means: (1) SSHing out from WSL2 to a real remote machine, VM, or cloud instance works completely normally and is the most realistic way to practice; (2) if you want to practice being on the "server" side too without a second machine, you can install openssh-server inside your own WSL2 instance and SSH to localhost from the same instance - this proves the mechanics (keys, permissions, config) but is not the same as genuine machine-to-machine access, and don't expect a Windows machine elsewhere on your network to reach your WSL2 sshd without extra port-forwarding setup that's out of scope here.
scp for copying files over SSH. scp (secure copy) reuses your existing SSH authentication (password or key) to copy files to or from a remote machine, so if ssh works, scp works with the same credentials. It's the SSH-secured equivalent of a local cp.
Command reference
| Command | What it does | Example |
|---|---|---|
ssh user@host |
Opens a remote shell session on host, logging in as user |
ssh alice@203.0.113.10 |
ssh -p <port> user@host |
Connects using a non-default SSH port (-p); default port is 22 |
ssh -p 2222 alice@203.0.113.10 |
ssh -i <keyfile> user@host |
Connects using a specific private key file (-i = identity file) instead of the default |
ssh -i ~/.ssh/id_ed25519 alice@203.0.113.10 |
ssh-keygen |
Generates a new public/private key pair, interactively prompting for a file location and optional passphrase | ssh-keygen -t ed25519 -C "my-comment" |
ssh-copy-id user@host |
Copies your public key to the remote machine's ~/.ssh/authorized_keys, prompting for the remote password once to do so |
ssh-copy-id alice@203.0.113.10 |
scp <src> <dest> |
Securely copies a file between local and remote machines over SSH | scp notes.txt alice@203.0.113.10:/home/alice/ |
scp -r <src> <dest> |
Copies a directory and its contents recursively (-r) |
scp -r myproject/ alice@203.0.113.10:~/backup/ |
sudo apt install openssh-server |
Installs the SSH server package so this machine can accept incoming connections (module 05 recap) | sudo apt install openssh-server |
sudo systemctl enable --now ssh |
Enables the SSH server to start on boot and starts it immediately (module 11 recap) | sudo systemctl enable --now ssh |
sudo systemctl status ssh |
Shows whether the SSH server is currently running | sudo systemctl status ssh |
chmod 700 ~/.ssh |
Restricts the .ssh directory to owner-only access, required for SSH to trust its contents |
chmod 700 ~/.ssh |
chmod 600 ~/.ssh/id_ed25519 |
Restricts a private key file to owner-only read/write, required for SSH to accept using it | chmod 600 ~/.ssh/id_ed25519 |
Hands-on exercises
Open your WSL2 Ubuntu terminal. Confirm the SSH client is already present by running
ssh -V. You should see a version string likeOpenSSH_8.xor similar - this confirms the client-side tooling from earlier modules' package management is already there.Generate a key pair for yourself: run
ssh-keygen -t ed25519 -C "practice-key". When prompted for a file location, press Enter to accept the default (~/.ssh/id_ed25519). When prompted for a passphrase, you can press Enter twice for no passphrase for this practice exercise (in real use, a passphrase adds another layer of protection on top of the key itself).List your
.sshdirectory withls -l ~/.ssh. You should seeid_ed25519(your private key) andid_ed25519.pub(your public key). Runcat ~/.ssh/id_ed25519.puband note that it's a single line of text starting withssh-ed25519- this is safe to share. Do notcator share the private key file.Check the permissions on your key files with
ls -l ~/.ssh/id_ed25519andls -ld ~/.ssh.ssh-keygensets these correctly by default (600on the private key,700on the directory), so confirm you see-rw-------anddrwx------respectively.Now practice the server side locally so you have something to actually connect to. Install and start the SSH server:
sudo apt update sudo apt install -y openssh-server sudo systemctl enable --now ssh sudo systemctl status sshConfirm the status shows
active (running).Add your own public key to your own
authorized_keysfile (simulating what you'd do on a remote machine):mkdir -p ~/.ssh cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keysThen run
ssh localhost(using your own Linux username). The first time, you'll be asked to confirm the host's fingerprint - typeyes. You should be logged in without being prompted for a password, proving key-based auth worked. Typeexitto return to your original shell.Break the permissions on purpose to see the failure mode. Run
chmod 777 ~/.ssh/id_ed25519(making the private key world-readable), then tryssh -i ~/.ssh/id_ed25519 localhost. You should see a warning likePermissions ... are too openand the connection either falls back to password auth or refuses the key. Read the error message carefully - this is exactly the situation the "why permissions matter" concept above described.Fix it: run
chmod 600 ~/.ssh/id_ed25519, then retryssh -i ~/.ssh/id_ed25519 localhostand confirm it works again cleanly. Typeexitwhen done.Practice
scp. Create a test file and copy it to yourself over SSH:echo "scp test" > ~/scp-test.txt scp ~/scp-test.txt localhost:~/scp-test-copy.txt ls -l ~/scp-test-copy.txt cat ~/scp-test-copy.txtConfirm the copy exists and has the same content.
Set up a host alias. Edit (or create)
~/.ssh/configwith a text editor and add:Host mylocal HostName localhost User <your-linux-username> IdentityFile ~/.ssh/id_ed25519Save it, then run
chmod 600 ~/.ssh/configand test withssh mylocal. You should connect without specifying the username or key file on the command line, confirming the alias is working.If you have access to a real remote machine or cloud VM (optional but recommended for realism), run
ssh-copy-id user@<that-machine's-address>to copy your public key there, thenssh user@<that-machine's-address>to confirm passwordless login works against an actual separate machine, not justlocalhost. This is the scenario the WSL2 NAT note above described as the realistic way to practice SSH.
Production practice: remote access with trust boundaries
SSH combines server authentication, encrypted transport, and client authentication. A successful password or key prompt does not by itself prove you reached the intended server—the host key does that job.
Your SSH private key is your unshareable signing device. The public key placed on a server tells that server what signatures it will accept; the host key tells your client which server it reached.
sequenceDiagram
participant C as SSH client
participant S as Server
C->>S: Connect to host and port
S->>C: Present host key
C->>C: Verify known_hosts fingerprint
C->>S: Prove possession of private key
S->>C: Encrypted session
The handshake sequence explains a connection. This key-placement model clarifies what may and may not leave your client.
flowchart LR
K["Private key\nclient only"] -->|proves possession| C["SSH client"]
P["Public key\nserver ~/.ssh/authorized_keys"] --> S["SSH server"]
H["Server host key"] --> N["Client known_hosts"]
A ~/.ssh/config entry can give a server a memorable alias, a specific user, and a dedicated key. ssh production-api is then clearer and less error-prone than repeatedly typing a long command, while the private key remains only on your client.
Never “fix” an unexpected host-key warning by deleting the entry blindly. It can be a routine rebuild, but it can also indicate DNS or network interception. Verify the new fingerprint through a trusted channel before updating known_hosts.
Generate a dedicated practice key pair with a passphrase. Inspect its public-key fingerprint, add a harmless host alias in ~/.ssh/config, and use ssh -G <alias> to see the resolved configuration. Do not paste either key into this repository.
Explain the difference between a user key and a host key. Why does disabling host-key verification make an encrypted session less trustworthy?
- Can you locate your public and private key files and state which may be shared?
- Can you explain what
known_hostsprotects? - Can you use a config alias without embedding a password or key in a command?
Independent challenge
No commands given here — figure it out yourself using what you know from this module and earlier ones.
Task: Set up passwordless SSH from your WSL2 instance to itself (over localhost) using a brand-new key pair kept in its own file, separate from your default id_ed25519. Wire it up so a single short command — a host alias — connects you without you naming the user, key file, or host on the command line. Once it works, deliberately loosen the private key's permissions so that other users could read it, observe exactly how SSH reacts, and then restore the correct permissions (connect this to the permission model from module 03). Explain why SSH treats a too-readable private key as unusable.
Stuck? One hint
ssh-keygen -f lets you name a non-default key file; its public half gets appended to ~/.ssh/authorized_keys, and a Host block in ~/.ssh/config bundles the hostname/user/IdentityFile — while a private key must be 600 (owner read/write only), not group- or world-readable.
Common mistakes & troubleshooting
- "Permission denied (publickey)" when connecting. This almost always means either the public key was never added to the remote's
authorized_keys, or the permissions on the remote's~/.sshorauthorized_keysare too loose (SSH refuses to trust them) or too strict for the wrong owner. Re-check both the key placement and permissions. - "WARNING: UNPROTECTED PRIVATE KEY FILE!" or similar. This means your private key file's permissions are too open (readable by group/others). Fix with
chmod 600 <keyfile>. - Confusing which key goes where. The public key (
.pubfile) goes on the machine you're connecting to (in itsauthorized_keys). The private key stays on the machine you're connecting from and is never copied anywhere else. - Expecting another device on your home network to SSH into your WSL2 instance directly. Because of WSL2's NAT networking, this typically doesn't work without additional port-forwarding configuration on the Windows side, which is out of scope here - use
localhostself-connections or a real remote host to practice instead. - Host key verification warnings after reinstalling a remote machine or WSL distro. If a remote machine's identity changes (reinstall, new key), SSH will refuse to connect with a loud warning about a changed host key, as a security measure against impersonation. This is resolved by removing the stale entry from
~/.ssh/known_hostsfor that host, only after you've confirmed the change is expected and legitimate. ssh: connect to host ... port 22: Connection refused. This usually means no SSH server is listening on the target - check thatopenssh-serveris installed and thesshservice is running (sudo systemctl status ssh) on the target machine, not the machine you're connecting from.- Forgetting
sudo systemctl enable --now sshafter installing the server. Installing the package doesn't automatically start or enable the service; you must do that explicitly, as covered in module 11.
Checkpoint quiz
Write down your answer to each question before expanding it — checking without attempting first is the single easiest way to fool yourself into thinking you've learned this.
- What specific security problem does SSH solve that
telnetdoes not? - In a key pair, which key goes on the remote machine you're connecting to, and which never leaves your own machine?
- Why does SSH refuse to use a private key file or
.sshdirectory with overly permissive permissions? - What is
~/.ssh/authorized_keysand how does a public key end up there in normal practice? - What does
~/.ssh/configdo, and does it affect the remote machine in any way? - Why can't a random machine on your home Wi-Fi normally SSH directly into your WSL2 Ubuntu instance?
- What's the difference between what
sshdoes and whatscpdoes, given they use the same underlying authentication?
Show answers
- SSH encrypts the entire session (login credentials and all traffic), while telnet sends everything in plain text, so anyone monitoring the network could read passwords and data. SSH prevents eavesdropping and credential theft in transit.
- The public key goes on the remote machine (in its
~/.ssh/authorized_keys). The private key stays only on your own machine and must never be shared or copied elsewhere. - Because a private key (or an authorized_keys/config file) readable by other users on the system could let another local user read or use it to impersonate you; strict permissions (
600/700) ensure only the owner can access it, preserving the "private" guarantee the whole scheme depends on. - It's a file on the remote machine listing public keys that are permitted to log in as that user, one per line. In normal practice it's populated by running
ssh-copy-id(which appends your public key to it over an initial password-authenticated connection) or by manually appending your.pubkey's contents to it. - It's a client-side configuration file that lets you define shortcuts/aliases bundling hostname, username, port, and identity file, so you can type a short alias instead of the full connection command. It has no effect on the remote machine - it's purely local convenience.
- Because WSL2 sits behind NAT on the Windows host, so incoming connections from other devices on the network aren't routed to it by default - only outbound connections from WSL2 work without extra configuration.
sshopens an interactive remote shell session for running commands;scpuses the same SSH authentication to copy files to or from a remote machine instead of giving you a shell. They share credentials/keys but serve different purposes (interactive session vs. file transfer).
Further reading & sources
man7.org: ssh(1), ssh-keygen(1), and ssh_config(5) - full references for the client, key generation, and~/.ssh/configsyntax.- DigitalOcean: SSH Essentials - Working with SSH Servers, Clients, and Keys - a well-regarded, thorough walkthrough covering the same ground with additional worked scenarios.
- Microsoft: Get started using OpenSSH in WSL - the official doc that expands on this module's WSL2/NAT limitation, including options for accepting inbound connections if you need that later.
- Mozilla OpenSSH security guidelines - a widely-cited reference for hardening
sshd_configbeyond this module's beginner scope, worth bookmarking for when you administer a real internet-facing server.
Next
Continue to 14-logging-and-journald to learn how to read system logs and dig deep into journald, the log system behind the services you started managing in module 11.