Module 5: Remote Machines, Networking, and Scripting
1. Introduction: leaving your machine and automating the work
Description
You finished the previous module knowing who you are to the system (whoami, permissions), what runs on your computer (processes, signals), and what your shell knows about its own environment (PATH, variables). Notice one detail: all of that happened inside a single machine, yours. This module breaks that boundary twice. First you are going to extend your reach toward another computer: diagnose it over the network, get into it with a key instead of a password, move files to it with precision, and leave processes running there even after you close your laptop. Second, you are going to stop typing commands one by one and start saving them in a file that someone else — or you yourself, a month from now — can run without you being there to explain it.
By the end of this lesson you will have the full map of the eight lessons that follow, you will be able to explain why the module respects a very specific order — network before SSH, scripting last — and you will have running, without spending a cent or creating any account, at least one of the three ways to set up your own remote lab.
The reason this matters in real work is concrete: the market research that built this guide's catalog flagged exactly this as a frequent gap — people who deploy applications to a cloud without being able to explain what a port is, how a domain name resolves, or what SSH guarantees. When a platform's automatic deployment fails with a message that is not "everything went fine," that person has nothing to diagnose with. All that is left is to press the button again and hope. This module is what lives underneath the button.
Connection to the module: in module 4 you learned to read your own machine — permissions, processes, environment. The next lesson, How machines talk: IP, ports, and DNS, starts building from scratch, and calmly, the model you will use to diagnose any machine, including one that is not yours. No networking term gets defined in this lesson: it gets defined there.
From your house to the neighborhood: what changes in this module
Think of the previous four modules as learning to move comfortably inside your own house: you know what is in every room (the file tree), you know how to pass things from one room to another without going outside (pipes and redirection), you know who can come in through each door (permissions), and you know which lights stay on and which appliances keep running even when you are not watching them (processes and environment variables). That is real mastery, and it has already gotten you out of trouble.
This module is stepping outside. First you learn to understand how the houses in the neighborhood are identified and found — addresses, numbered doors, a directory that translates names into addresses — because you cannot ring a doorbell at a place whose address you cannot read. Then you learn to get into a house that is not yours with a key that only opens that door, instead of shouting a password so someone inside can hear you and decide whether to believe you. Once inside, you learn to move whole boxes from one house to the other without carrying them one at a time, and to leave the stove on when you leave without it turning itself off. And finally, instead of repeating the same steps from memory every time you visit a house, you learn to leave behind a note with exact instructions that anyone — including you, six months later — can follow without you being there to explain it.
That note with instructions is a script. And that is, in one sentence, the real distance between module 4 and this one: you stop being someone who operates their own machine and become someone who extends their work toward other machines and toward the future.
Why the order is network, then SSH, and scripting last
This module could start straight with SSH — it is, after all, the protagonist of much of what follows. It does not, and there is a concrete reason behind that, not an organizational whim.
When an ssh something@something fails, the message you get almost always belongs to one of these layers: the name could not be resolved (Could not resolve hostname), there is no reachability to that address (No route to host), the port does not respond (Connection refused, or the command just hangs without saying anything), or authentication failed (Permission denied). These are four completely different causes, with four completely different solutions, and all four sound equally confusing if nobody ever explained what an IP address is, what a port is, or what DNS does. That is why lessons 2 and 3 — the client-server model, addresses, ports, DNS, and the tools to diagnose each layer separately — come before SSH. You cannot meaningfully diagnose a connection you do not understand; you can only try things at random until something works, and that is not a skill that transfers to the next problem.
Scripting goes last for the opposite reason: not because it is hard, but because it needs everything that came before to make sense. A shell script is, at its core, the same sequence of commands you already know how to type by hand — with the same paths from module 1, the same redirection and the same exit codes from module 3, the same execute permissions from module 4 — saved into a file with a little logic wrapped around it. If this module started with scripting, you would have to learn chmod +x without having seen permissions, or check $? without having seen exit codes. Instead, by the time you reach lesson 6, you will already recognize most of the pieces.
The full map:
1. Introduction: leaving your machine and automating the work ← you are here
2. How machines talk: IP, ports, and DNS
3. Troubleshooting the network from the terminal: ping, dig, curl, and ss
4. Connecting with SSH and key authentication
5. Moving files and sessions that survive: scp, rsync, and tmux
6. Your first shell script
7. Logic and safety in scripts: conditionals, loops, and set -euo pipefail
8. Final project: remote diagnostics toolkit
Two model lessons (2 and 3) before touching ssh in lesson 4, three lessons of remote mechanics (4 through 6), one lesson of hardening for scripts (7), and a project (8) that pulls it all together against a real case: a domain that needs diagnosing and a remote machine you need to connect to in order to do it.
Worked example: set up your lab at zero cost
Everything that follows in this module needs something on the other end of a connection: a machine you can connect to over SSH, move files to, and run commands on. You do not need a cloud account, a credit card, or a rented server. There are three ways to get exactly that without spending anything, and with at least one working you can follow the rest of the module.
You only need one of the three. If you have never touched a server terminal, Option A has the least friction. If you have a second computer lying around, Option B is the closest to a real work situation. If you already use Docker from another course, Option C is the fastest to tear down.
In all three we are going to use the ssh command in its simplest form, just to confirm there is a live server on the other end. What SSH actually is and what it guarantees you will see calmly in lesson 4; for now treat it as nothing more than "a way to ask another computer to run a command and hand you back the result."
Option A — SSH against your own machine
This is the option with the fewest steps, because the SSH client already comes installed on macOS and on any Linux distribution, and the server only needs to be turned on.
On macOS:
- Open the menu → System Settings → General → Sharing.
- Turn on Remote Login.
There is also a command-line route (sudo systemsetup -setremotelogin on), but macOS requires that the application running it have Full Disk Access for that specific action; if you run it from Terminal without that permission, you will get a privileges error. That is why, for this first contact, the menu route is the one with no surprises.
On Linux (Ubuntu/Debian):
sudo apt update && sudo apt install -y openssh-server
sudo systemctl enable --now ssh
What to expect (may vary slightly depending on your distribution):
Created symlink /etc/systemd/system/multi-user.target.wants/ssh.service → /usr/lib/systemd/system/ssh.service.
If you use Windows with WSL2 (module 1, lesson 3), this runs exactly the same as on Linux: your Ubuntu distribution is no different from a real Ubuntu for this purpose.
Test it, on either system:
whoami
What to expect:
mikenieva
With that username, test the connection:
ssh mikenieva@localhost echo "the SSH server on this machine responds"
What to expect (only the first time you connect):
The authenticity of host 'localhost (127.0.0.1)' can't be established.
ED25519 key fingerprint is SHA256:g4K9x...truncated...q2Zw.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'localhost' (ED25519) to the list of known hosts.
mikenieva@localhost's password:
the SSH server on this machine responds
That "fingerprint" notice and that password prompt will get a full explanation in lesson 4. For now, type yes to confirm and then your user account's password. The last line — your own text, handed back from "another" machine that in this case happens to be the same one — is the confirmation that the server is alive and responding.
Option B — a second computer on your same network
If you have an old laptop, a Raspberry Pi, or any other computer connected to the same Wi-Fi, repeat exactly the steps from Option A, but on that other machine. The difference is in how you connect: instead of localhost, you are going to use that computer's IP address within your local network — something shaped like 192.168.1.34. How to find that address is, literally, content from lesson 2; for now keep this option in your pocket and come back to it once you finish that lesson. It is the option that most resembles a real work situation, because there are two distinct physical machines involved.
Option C — a local container with Docker
If you already have Docker installed — maybe from another course — this is the option that touches your operating system the least: the entire server lives inside a container you can delete with a single command once you finish the module.
docker run -d \
--name ssh-lab \
-p 2222:2222 \
-e PUID=1000 \
-e PGID=1000 \
-e USER_NAME=student \
-e USER_PASSWORD=lab1234 \
-e PASSWORD_ACCESS=true \
lscr.io/linuxserver/openssh-server:latest
One row per flag, so nothing is left unexplained:
| Flag | What it does |
|---|---|
-d | runs the container in the background ("detached"), without taking over your terminal |
--name ssh-lab | gives the container a readable name, so you can manage it without memorizing its identifier |
-p 2222:2222 | connects port 2222 on your machine to port 2222 inside the container. The order is always host:container; if you write it backwards, the container starts up fine, but you will never be able to connect to it from outside |
-e VARIABLE=value | defines an environment variable inside the container — the same environment variable idea from module 4, applied to a process that runs inside another process |
What to expect:
Unable to find image 'lscr.io/linuxserver/openssh-server:latest' locally
latest: Pulling from linuxserver/openssh-server
(... download progress ...)
Status: Downloaded newer image for lscr.io/linuxserver/openssh-server:latest
3f1a9c2e8d0b4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f
That long identifier is the ID of the container you just created. Test it just like the other two options:
ssh -p 2222 student@localhost echo "the container responds"
Same fingerprint warning, and as the password you use the value you set in USER_PASSWORD (lab1234 in the example). Once you finish the module, docker rm -f ssh-lab deletes the whole container without leaving a trace on your system.
Going deeper: comparing the three options and a preview of the final project
| Option | New software you install | How realistic the network experience is | How you tear it down when done |
|---|---|---|---|
| A — your own machine | None on macOS/Linux | Partial: it is the same machine talking to itself | Turn off Remote Login by hand |
| B — second computer | None | Complete: two distinct physical machines | Turn off the service on the other machine |
| C — Docker container | Docker, if you do not have it | Partial: same machine, but isolated in a container | docker rm -f ssh-lab, a single command |
If you have never done this before, start with Option A: it introduces the fewest new pieces. You can add B or C later on if you want to practice against something that feels more like a real server.
The project that closes the module, in lesson 8, is a small toolkit you are going to call ops-toolkit: a script that audits a machine's environment, another that diagnoses a domain layer by layer, and a third that connects over SSH to a remote machine, runs the first two there, and brings the results back. You do not need to understand that paragraph yet — it is just a preview of where this module is heading, and by the time you reach lesson 8 every piece of that sentence will feel familiar.
Common mistakes
Thinking this module is "learning the ssh command." It is the most common conceptual trap when starting out, and it sounds reasonable: SSH is, at a glance, a single command with two words. But each connection symptom — Could not resolve hostname, Connection refused, Permission denied — belongs to a different layer of the client-server model, and really diagnosing it means knowing which layer to look at, not memorizing the command's syntax. You can spot it this way: if, facing a connection error, your first instinct is to search for the exact message online instead of asking yourself "is it the name, is it the network, is it the port, or is it authentication?", you are still in memorization mode. It gets fixed by going through lessons 2 and 3 before lesson 4, in the order they are laid out.
Believing you need a cloud account to practice SSH for real. It is a common and avoidable practical mistake: requesting a VPS, activating a free tier with an expiration date, or straight up postponing the module for "not having a server." None of the three options in this lab need that, and all three give you exactly the same protocol, the same tools, and the same error messages as a real server.
In Option C, flipping Docker's port mapping. Writing -p 22:2222 instead of -p 2222:2222 is an easy typo to make and a hard one to notice, because the container starts up without complaint. The symptom only shows up when you try to connect: ssh -p 2222 student@localhost responds Connection refused, even though the container is running. It gets fixed by checking that the first number in the pair is always your machine's port (the one you use to connect) and the second is the port inside the container.
Exercises
Exercise 1 — Choose the right option
For each situation, decide which of the three lab options fits best and justify it with what you saw in this lesson.
- Someone who has never opened a server terminal and only has their everyday laptop.
- Someone who has, besides their laptop, an old computer connected to the same Wi-Fi that they no longer use for anything.
- Someone who installed Docker a month ago for another course and wants to be able to tear the lab down without leaving anything permanently installed.
See solution
- Option A. It has the fewest new pieces: it does not install any additional software on macOS or Linux, and the only step is turning on something the operating system already ships with. For a first exposure, less friction is better.
- Option B. Having a second physical machine available is exactly the scenario Option B takes advantage of, and it gives the experience closest to connecting to a real server — two distinct computers, not the same one talking to itself.
- Option C. It already has the one extra piece that option requires (Docker), and the container gets destroyed completely with
docker rm -f ssh-lab, without leaving any persistent configuration on the operating system — exactly what the scenario asks for.
Why it works: the criterion in this lesson is not "one option is superior to the others," but rather what software you already have available and how realistic you want the practice to feel. All three options give you exactly the same protocol and the same error messages on the other end.
Exercise 2 — How many causes can you rule out without the model yet
Imagine you skip lessons 2 and 3 and go straight to trying SSH against Option B. You type ssh student@192.168.1.50 and get ssh: connect to host 192.168.1.50 port 22: Connection refused. Without the layered model taught in lessons 2 and 3: how many different causes can you think of for that message, and how many of them can you rule out with what you know so far?
See solution
Without lessons 2 and 3, it is hard to rule out any of them: the message could be due to, among other things, the SSH server on that machine not running, a firewall blocking the port, the IP address you typed no longer belonging to that computer (addresses within a local network can change), or the two machines not even being on the same network. That is at least four plausible explanations, and without a method to isolate each one separately, the only available strategy is to try random changes until something works.
Why it works: that is exactly the problem lessons 2 and 3 solve — giving you a layer-by-layer elimination method: does the name resolve?, is there network reachability?, is the port listening?, is the response the expected one? Connection refused in particular is valuable information once you know the model: it means there was network reachability all the way to that machine, because something actively responded by rejecting the connection, unlike an attempt that just hangs with no response at all. That nuance is impossible to take advantage of without the vocabulary the next two lessons give you.
Exercise 3 — Confirm you already have what you need
On your own machine, without touching any of the options above, run:
ssh -V
Interpret the output: what does it tell you about whether or not you already have the SSH client installed, and what would you do if the result were command not found instead of a version?
See solution
A typical output looks like this (the exact version varies by system):
OpenSSH_9.6p1, LibreSSL 3.3.6
That line confirms that the SSH client — the program you use to initiate connections, distinct from the server you turned on in Option A — already comes installed by default on macOS, on any common Linux distribution, and inside a WSL2 distribution. You did not have to install anything to get here.
If instead the result were zsh: command not found: ssh (or the bash equivalent), the correct diagnosis is not "reinstall the operating system": it is exactly the same command not found you already know how to resolve from module 4 — check whether the program exists on disk and whether the directory it lives in is in your PATH. In practice, this almost never happens on macOS, Linux, or WSL2 with a standard install; it is more of a case for minimal distributions built on purpose without networking tools.
Why it works: confirming the client is already available is part of this lesson's "zero cost" promise — you not only spend no money, you also spend no time installing the base software. And connecting the rare failure case back to module 4 reinforces that command not found always has the same structural explanation, no matter which command is missing.
Summary and next step
This module extends everything you learned in the previous four beyond your own machine: you are going to diagnose networks, connect to other computers with SSH, move files between them, keep processes alive even after closing your laptop, and finally turn loose commands into reusable scripts. You saw why the order matters — you cannot diagnose a connection you do not understand, and a script only makes sense once you already know the pieces it is going to chain together — and you set up, at zero cost, at least one of the three ways to have an SSH server waiting for you on the other end.
Before moving on you should be able to:
- Name the module's eight lessons in order and say in one sentence what each one covers.
- Explain why the network comes before SSH, and why scripting comes last, without looking at the map.
- Have at least one of the three lab options working, confirmed with an
ssh ...@... echo "..."that handed you back your own message. - Recognize that a connection error (
Connection refused,Could not resolve hostname,Permission denied) is information about a specific layer, even if you cannot yet name which one.
What comes next is not SSH yet. It is the model that will let you read any network error without guessing: what an IP address is, what localhost and 127.0.0.1 are, what it means for a port to be listening, and what DNS does step by step when you type a domain name. With that model in place, lesson 4 — actually connecting over SSH, with a key — is going to feel like the natural next step, not new magic.
Resources
- Apple Support — Allow a remote computer to access your Mac — the official steps to turn on Remote Login from System Settings, the route you used in Option A on macOS.
- Apple Support — Use the systemsetup command-line utility — documents why
systemsetup -setremoteloginrequires the application running it to have Full Disk Access, the warning you saw about the command-line route. - Ubuntu Server documentation — OpenSSH Server — official installation and administration of the SSH server on Ubuntu/Debian, the route you used in Option A on Linux.
- Docker Hub — linuxserver/openssh-server — full documentation for the image used in Option C, with all available environment variables besides the four you saw here.
- Docker Docs — Networking overview, published ports — official reference for the
-p/--publishflag and thehost:containerorder that avoids the reversed-mapping error from this lesson.