To learn Linux on a VPS, deploy a small Ubuntu 24.04 server for each two-hour lab, work through one topic until a written success check passes, then delete the server so billing stops. The eight labs below cover the shell, users, packages, systemd, networking with SSH and a firewall, a web server, Bash scripting and Docker in about 16 server-hours, which costs $0.16 on a 1 GB plan billed by the hour.
Every command follows the official Ubuntu 24.04 LTS documentation and manual pages, and every lab ends with a check you can run, a mistake to make on purpose and a reboot test. You will also find a fix-it table for the errors beginners hit most, the free courses to pair with each lab, and an honest note on when a local VM or student cloud credits fit better.
Key takeaways
- To learn Linux on a VPS, deploy a fresh Ubuntu 24.04 server for each two-hour lab, pass a written success check, then delete the server so billing stops.
- Eight labs (shell, users, packages, systemd, networking with SSH and UFW, nginx, Bash and Docker) take about 16 server-hours, and a 1 GB plan meets Ubuntu's 1 GB minimum for 24.04 cloud images.
- End every lab with a reboot test: Red Hat's RHCSA requires configurations to persist after a reboot, and the LFCS gives you 2 hours of hands-on command-line tasks.
- Three traps worth hitting on purpose: Restart=on-failure does not restart after a plain SIGTERM, sudo echo > file fails because your shell does the redirect, and Docker-published ports bypass UFW.
- Free routes exist: Ubuntu's own terminal tutorials, the Linux Foundation's free LFS101 course with 60 hours of material, and student cloud credits with eligibility limits.
Why learn Linux on a VPS instead of a local VM?
A local virtual machine teaches commands; a VPS teaches servers. You log in over SSH from another computer, your web server answers on a public IP address, and your firewall decides what the internet can reach. Two well-known entry-level admin exams test the same way: LFCS and RHCSA are hands-on, performance-based exams solved at a Linux command line.
You do not need to pay to start, though. Here is how the common options compare:
| Option | Cost | Public IP and outside SSH | Catch | Better fit when |
|---|---|---|---|---|
| Local VM with Multipass (what Ubuntu’s own tutorials use) | Free | No, it lives on your computer | Needs 5 GB of disk and 1 GB of memory free on your computer | You are offline, on a zero budget, or practicing commands only |
| Azure for Students | $100 credit for 12 months, no credit card | Yes | Full-time university students only; renew each year while you study | You are eligible and also want to learn Azure itself |
| AWS Free plan | Up to $200 in credits over 6 months | Yes | The account closes after 6 months or when credits run out unless you move to a paid plan | You want to learn AWS alongside Linux |
| Oracle Cloud Free Tier | Always Free AMD and Arm compute | Yes | A card is required for identity checks; accounts idle for 30 days or more may be treated as abandoned and suspended | You want one long-lived free server and accept the sign-up checks |
| HourlyVPS Quartz Q1, billed hourly | $0.01/hour, deleted after each lab | Yes, IPv4 and IPv6 | Prepaid: each server you order starts with an initial credit that pays for its hours; minimum top-up $5 | You want plain Linux, a fresh server per lab and no eligibility rules |
Honest split: if your laptop can run a VM, do Labs 1 to 4 locally for free with Multipass and move to a VPS for Labs 5 to 8, where SSH from outside, a real firewall and a public web server are the point of the exercise.
How to learn Linux on a VPS: one fresh server per lab
Every lab starts on a brand-new server and ends with deleting it. You never debug leftovers from last week, you can break anything without consequences, and the first-login routine becomes muscle memory because you repeat it eight times. It is the same habit that makes servers reproducible at work: if a server matters, you can rebuild it from your notes.
- Deploy. Pick Ubuntu 24.04 LTS on a Quartz Q1 in the location closest to you (the guide to choosing a VPS location explains why latency matters when you type over SSH), and add your SSH public key in the order form if it offers a key field; otherwise log in once with the initial password you received at deploy and add your key first.
- Connect. Log in with
ssh [email protected], or with the image’s default user (such asubuntu) if your server uses one, as shown in how to connect to a VPS over SSH. If a new server reuses an old address and SSH warns that the host key changed, that guide shows how to verify and clear it. - Set up. Labs 1 to 3 run as the first account your server gives you (root or the image’s default user), so the two hours go to the topic; the commands keep
sudo, so they work unchanged for root or a normal user. From Lab 4 on, spend the first 10 minutes on the routine from our VPS security checklist: update packages, create a sudo user and give it your key. From Lab 6 on, the routine also includes what Lab 5 teaches: the SSH drop-in that turns off root and password logins, and UFW with OpenSSH allowed. - Work. Type every command yourself. Paste anything that surprised you, with its output, into a notes file on your own computer.
- Prove it. Run the success check, then the reboot test:
sudo reboot, reconnect, and confirm your change survived. Red Hat requires exactly this on the RHCSA: configurations must persist after a reboot without intervention. - Delete. Copy anything you want to keep with
scp, then delete the server in the portal.
Billing note: Billing is by the hour: every hour a server exists is charged at the plan’s hourly rate. The price tapes and cost tables on this site count every started hour as a full hour, so they show the most a duration can cost; the cost calculator charges a partial hour to the nearest cent, as the bill does. Charges are deducted in whole cents: a full hour costs exactly the hourly rate, and a partial first or last hour is rounded to the nearest cent, never more than a full hour. Delete rather than stop the server when you are done.
Safety rules for a Linux lab server
A lab server is disposable, but it is still a real machine on the internet. Seven rules keep it from becoming someone else’s machine:
- Keys, not passwords. Log in with an SSH key from the first session; Lab 5 turns password logins off, and every later lab keeps them off. A guessed password on a lab server is still a compromised server.
- Firewall before services. From Lab 6 on, enable UFW with OpenSSH allowed before you start nginx, Docker or a test web server. Docker-published ports are the exception: Docker’s documentation says they bypass UFW, which Lab 8 lets you see for yourself.
- No real data or secrets. No personal files, API keys or work passwords on a lab server; the server will be deleted anyway.
- Test only what you own. Port scans, load tests and brute-force drills go against your own servers or ones you have written permission to test, as our acceptable use policy requires.
- Read before you paste. Docker’s install page puts it plainly: “Always examine scripts downloaded from the internet before running them locally.” The same goes for one-liners from forums.
- Keep a way back in. The VNC console in the portal is a local login that works when SSH or the firewall locks you out; the SSH guide covers the console.
- Delete, do not stop. A stopped server is still billed, because its vCPU, memory, disk and IP addresses stay reserved for you; only deleting the server stops billing. Before you delete anything you care about, run through the checklist for deleting a VPS.
What does a Linux lab on a VPS cost?
Labs need little hardware. Ubuntu lists 1 GB of RAM and 4 GB of disk as the minimum for 24.04 LTS cloud images, and suggests 3 GB of RAM and 25 GB of disk or more for more complex setups. A Quartz Q1 has 1 vCPU, 1 GB of RAM and 25 GB of NVMe storage. Our reasoning: each lab adds only small packages (sysstat, nginx, ShellCheck) or one small container, and Docker’s Ubuntu install page lists supported releases, not a RAM minimum, so Q1 covers the whole plan. A Quartz Q2 with 2 GB gives Lab 8 headroom if you pull larger images.
| What you run | Server-hours | Cost on Quartz Q1, billed hourly |
|---|---|---|
| One lab | 2 | $0.02 |
| All eight labs once | 16 | $0.16 |
| All eight labs twice (the second time from memory) | 32 | $0.32 |
| A 15-week semester with two 2-hour labs a week | 60 | $0.60 |
| For comparison: one Q1 left on for 4 billing periods | about 2,900, capped at the monthly price each billing period | $20.00 |
Cost: A Quartz Q1 costs $0.01/hour, so one two-hour lab costs $0.02; the same lab on a Q2 costs $0.04. Each server you order starts with an initial credit, prepaid and used for that server’s hours; usage is deducted from it every hour, and the minimum top-up for your account is $5. The hourly billing guide covers the rules, and the VPS cost calculator prices your own schedule. Deploy a Quartz Q1 by the hour
If you prefer one long-lived server, for example to follow a month-long course, simply leave it on: the same hourly billing stops at the plan’s monthly price in each billing period (one month from your order date), so an always-on Q1 never costs more than $5.00 per billing period. The hourly vs monthly guide shows when the cap kicks in.
The 8-lab plan at a glance
Each lab is about two hours, including the setup routine. Do them in order: later labs assume the earlier skills, not the earlier server.
| Lab | Topic | Core commands | You are done when |
|---|---|---|---|
| 1 | Shell and files | ls, cd, find, grep, pipes, man | You answer three questions about the server using only the terminal |
| 2 | Users and permissions | adduser, chmod, chgrp, sudo | Bob cannot open Alice’s home, but both share a team folder |
| 3 | Packages | apt, dpkg --listfiles, apt remove --purge | You can show where a package’s files went and remove it without leftovers |
| 4 | systemd and logs | systemctl, journalctl, unit files | Your own service survives a crash and a reboot |
| 5 | Networking, SSH and the firewall | ip, ss, ssh-keygen, ufw | Key login works, password login fails, and a port opens only when you allow it |
| 6 | A web server | nginx, nginx -t, access and error logs | Your page loads on your phone and a broken config never reaches it |
| 7 | Shell scripting | bash, exit codes, shellcheck, a systemd timer | Your script passes ShellCheck, fails loudly and runs every 15 minutes |
| 8 | Docker | docker run, docker compose, volumes, port binding | A container serves your page on localhost only, and you have seen why that matters |
Labs 1 to 3: the shell, users and packages
Lab 1: Shell and files
Goal: move around the filesystem, create and copy files, search with find and grep, and chain commands with pipes. Ubuntu’s Welcome to the terminal and The command line in depth tutorials cover the theory; run these on the server:
pwd
ls -la ~
cd /etc && ls -l | head
mkdir -p ~/lab1/notes && cd ~/lab1
echo "first line" > notes/a.txt
cp -a notes notes-backup
grep -c "/bin/bash" /etc/passwd
find /etc -name "*.conf" | wc -l
du -sh /var/log/* 2>/dev/null | sort -h | tail -5
man -k copy
Success check: answer three questions using only the terminal. How many accounts on this server use /bin/bash as their shell? Which five entries under /var/log are largest? What does the -a flag of cp preserve (man cp, then press / to search)? If man says the system has been minimized, read the same pages on Ubuntu Manpages, or install the unminimize package and run sudo unminimize.
Break it on purpose: run rm -r ~/lab1/notes and try to get it back. There is no trash can on a server; only the copy you made with cp -a saves you. That is the first lesson about backups.
Lab 2: Users, groups and permissions
Goal: create users and a group, read the nine permission characters in ls -l, and build a folder two people can share. Ubuntu encourages the adduser tools for account management, per its user management guide:
sudo adduser alice
sudo adduser bob
sudo addgroup team
sudo adduser alice team
sudo adduser bob team
sudo mkdir /srv/team
sudo chgrp team /srv/team
sudo chmod 2770 /srv/team
ls -ld /home/alice /srv/team
The 2 in 2770 is the set-group-ID bit: on a directory, new files inherit the directory’s group instead of their creator’s. Now switch users and test, starting with sudo -iu alice and touch /srv/team/plan.txt, then exit.
Success check: as Bob (sudo -iu bob), ls /home/alice fails with “Permission denied”, because Ubuntu 21.10 and later create home directories with mode 0750. ls -l /srv/team shows plan.txt with group team, and Bob can read it. Finally, sudo journalctl _COMM=sudo --since today lists every sudo command run today, including yours.
Break it on purpose: run sudo chmod 0755 /home/alice and confirm Bob can now browse Alice’s files. Put it back with sudo chmod 0750 /home/alice. Older Ubuntu releases used 0755 by default, which is why the user management guide tells you to check.
Lab 3: Packages with apt and dpkg
Goal: update the system, find and inspect a package, see where its files land, and learn the difference between removing and purging. The apt and dpkg commands follow Ubuntu’s Managing your software tutorial:
sudo apt update
apt list --upgradable
sudo apt upgrade
cat /etc/apt/sources.list.d/ubuntu.sources
apt show sysstat
apt policy sysstat
sudo apt install sysstat
dpkg --listfiles sysstat | head -20
dpkg --search /usr/bin/iostat
iostat
Then remove it in two stages and watch what stays behind:
sudo apt remove sysstat
ls /etc/sysstat
dpkg -l sysstat
sudo apt remove --purge sysstat
ls /etc/sysstat
grep sysstat /var/log/dpkg.log | tail -5
Success check: after the plain remove, /etc/sysstat still exists and dpkg -l shows the package as removed with its configuration kept; after the purge, the directory is gone, and /var/log/dpkg.log records each step. On a fresh 24.04 server the repository list lives in /etc/apt/sources.list.d/ubuntu.sources (the deb822 format), not in the old /etc/apt/sources.list.
“Packages have been kept back”? That is usually a phased update: Ubuntu rolls some updates out to a growing share of machines. Its phased-updates explainer says you can safely ignore the message, and security updates are never phased.
Labs 4 and 5: services, logs, networking and the firewall
Lab 4: systemd services and logs
Goal: write your own service, make systemd restart it after a crash, and read its logs with journalctl. Start with the setup routine (update, sudo user, key), then create a tiny program:
sudo tee /usr/local/bin/heartbeat.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
while true; do
echo "heartbeat from $(hostname)"
sleep 10
done
EOF
sudo chmod +x /usr/local/bin/heartbeat.sh
Then the unit file. DynamicUser=yes makes systemd run it as a throwaway unprivileged user, and Restart=on-failure with RestartSec=5 restarts it five seconds after a crash:
sudo tee /etc/systemd/system/heartbeat.service > /dev/null <<'EOF'
[Unit]
Description=Lab 4 heartbeat
[Service]
ExecStart=/usr/local/bin/heartbeat.sh
Restart=on-failure
RestartSec=5
DynamicUser=yes
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now heartbeat.service
systemctl status heartbeat
sudo journalctl -u heartbeat -f
Press Ctrl+C to leave the log. Now crash it two ways:
sudo systemctl kill --signal=SIGKILL heartbeat
systemctl status heartbeat
sudo systemctl kill heartbeat
systemctl status heartbeat
Success check: after SIGKILL the service comes back within about five seconds. After the plain systemctl kill, which sends SIGTERM by default, it stays down: systemd.service(5) counts SIGTERM as a clean exit, and on-failure only restarts unclean ones. Start it again, reboot, and confirm systemctl is-enabled heartbeat and systemctl is-active heartbeat both pass. sudo journalctl --list-boots shows whether the journal kept the previous boot.
Break it on purpose: run sudo chmod -x /usr/local/bin/heartbeat.sh and restart the service. systemctl status now shows status=203/EXEC, which systemd documents as a failed execve call, “most likely” a missing or non-accessible executable. Read the reason with sudo journalctl -u heartbeat -n 20, then fix it with sudo chmod +x /usr/local/bin/heartbeat.sh.
Lab 5: Networking, SSH keys and the UFW firewall
Goal: read the server’s network setup, switch SSH to keys only, and control which ports the internet can reach. Look first, on the server:
ip -br address
ip route show
resolvectl status
sudo ss -tulpn
Your public key reached your sudo user during the setup routine. Prove it is the same key by comparing fingerprints, first on your own computer, then on the server (no key yet? ssh-keygen -t ed25519 creates one, and the SSH guide shows how to copy it, Windows included):
ssh-keygen -lf ~/.ssh/id_ed25519.pub
ssh-keygen -lf ~/.ssh/authorized_keys
ls -la ~/.ssh
Then turn off root and password logins in a drop-in file. The 00- prefix matters, because sshd keeps the first value it reads; the security checklist explains why. Test the configuration, reload, and keep this session open until a second login works:
sudo tee /etc/ssh/sshd_config.d/00-hardening.conf > /dev/null <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
EOF
sudo sshd -t && sudo systemctl reload ssh
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication) '
Then the firewall. Allow SSH through its OpenSSH application profile before you enable UFW, or your next SSH login is blocked; Ubuntu’s firewall guide covers both commands and application profiles:
sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status verbose
Now prove the firewall works with Python’s built-in test web server, which Python’s documentation says is not for production:
- On the server:
mkdir -p ~/pub && cd ~/pub && echo hello > index.html && python3 -m http.server 8000. - From your computer:
curl -m 5 http://203.0.113.10:8000/times out. - In a second SSH session:
sudo ufw allow 8000/tcp. The samecurlnow printshello. - Clean up:
sudo ufw delete allow 8000/tcp, then Ctrl+C to stop the test server.
Success check: a key login as your user works; ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password [email protected] is refused; port 8000 answers only while the rule exists; and after a reboot sudo ufw status still says active. For a reality check, run sudo journalctl -u ssh --since today | grep -c "Invalid user" to count login attempts for user names that do not exist on your server.
Warning: do not experiment with /etc/netplan on a server you only reach over SSH. Ubuntu’s networking docs apply changes with sudo netplan apply; on a lab server use sudo netplan try instead, which reverts after 120 seconds unless you confirm, and keep the VNC console open.
Labs 6 to 8: a web server, a Bash script and Docker
Lab 6: A web server with nginx
Goal: install nginx, serve your own page from your own server block, read the logs and catch a broken configuration before it goes live. After the setup routine (now with the SSH drop-in and UFW), install nginx and open port 80 with the application profile the Ubuntu package ships:
sudo apt install nginx
sudo ufw allow 'Nginx HTTP'
curl -I http://localhost
Create a site folder and page. Note the sudo tee: sudo echo "..." > file fails with “Permission denied”, because your own shell performs the > redirection before sudo runs.
sudo mkdir -p /srv/lab/html
echo '<h1>Lab 6 works</h1>' | sudo tee /srv/lab/html/index.html
Then a server block based on the one in Ubuntu’s nginx configuration guide, enabled through a symlink:
sudo tee /etc/nginx/sites-available/lab > /dev/null <<'EOF'
server {
listen 80 default_server;
listen [::]:80 default_server;
root /srv/lab/html;
index index.html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
}
EOF
sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-available/lab /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo tail -f /var/log/nginx/access.log
Success check: http://203.0.113.10 shows “Lab 6 works” on your phone, and the request appears in access.log with your phone’s public address. After a reboot the page still loads without you touching anything.
Break it on purpose: delete one semicolon from the server block and run sudo nginx -t: it names the file and line, and as long as you reload only through sudo nginx -t && sudo systemctl reload nginx, a broken file never goes live. Then run sudo chmod 700 /srv/lab/html: the page returns 403 Forbidden and /var/log/nginx/error.log says why. Ubuntu’s nginx workers run as www-data, which can no longer enter the folder; sudo chmod 755 /srv/lab/html fixes it. For HTTPS with a real domain, continue with the Caddy reverse proxy guide.
Lab 7: A Bash script with exit codes and a timer
Goal: write a script that reports on the server, exits with an error when something is wrong, passes a linter and runs on a schedule. Install ShellCheck from Ubuntu’s universe repository, which is enabled by default:
sudo apt install shellcheck
Create ~/health.sh with nano. set -euo pipefail makes the script stop on a failing command, an unset variable or a failure inside a pipeline, as the bash(1) manual describes for -e, -u and pipefail:
#!/usr/bin/env bash
# health.sh: print a short server report; exit 1 when the root disk is too full.
set -euo pipefail
limit="${LIMIT:-80}"
used="$(df --output=pcent / | tail -n 1 | tr -dc '0-9')"
echo "== $(hostname) at $(date --iso-8601=seconds)"
uptime
free -h
echo "Root disk used: ${used}% (limit ${limit}%)"
echo "Failed units:"
systemctl --failed --no-legend
echo "Listening TCP ports:"
ss -tln
if (( used > limit )); then
echo "WARNING: root disk is above ${limit}%" >&2
exit 1
fi
chmod +x ~/health.sh
shellcheck ~/health.sh
~/health.sh; echo "exit code: $?"
LIMIT=1 ~/health.sh; echo "exit code: $?"
Now schedule it with a systemd timer instead of cron, so its output lands in the journal. *:0/15 is the systemd.time(7) way to say “every 15 minutes from the top of the hour”, and systemd-analyze calendar shows when it will next fire:
sudo install -m 755 ~/health.sh /usr/local/bin/health.sh
sudo tee /etc/systemd/system/health.service > /dev/null <<'EOF'
[Unit]
Description=Lab 7 health report
[Service]
Type=oneshot
ExecStart=/usr/local/bin/health.sh
EOF
sudo tee /etc/systemd/system/health.timer > /dev/null <<'EOF'
[Unit]
Description=Run the Lab 7 health report every 15 minutes
[Timer]
OnCalendar=*:0/15
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemd-analyze calendar '*:0/15'
sudo systemctl daemon-reload
sudo systemctl enable --now health.timer
systemctl list-timers health.timer
Success check: ShellCheck prints nothing; the script exits 0 normally and 1 with LIMIT=1; systemctl list-timers shows the next run; and sudo journalctl -u health.service shows reports every 15 minutes, before and after a reboot.
Break it on purpose: run sudo systemctl edit health.service, add Environment=LIMIT=1 under a [Service] line, and wait for the next run: systemctl --failed now lists health.service, which is exactly how a monitoring check should fail. Extra credit: turn your setup routine into a bootstrap.sh that you run on every fresh server.
Lab 8: Docker and Docker Compose
Goal: run containers, serve a folder through an nginx container with Compose, and see with your own eyes why published ports need care. After the setup routine with UFW, install Docker Engine from Docker’s apt repository with the steps in our guide to installing Docker on a VPS (they follow Docker’s Ubuntu instructions), then:
sudo docker run hello-world
mkdir -p ~/lab8/site && cd ~/lab8
echo '<h1>Lab 8: served by a container</h1>' > site/index.html
Create ~/lab8/compose.yaml. The volume mounts your folder read-only at the path the official nginx image serves, and the port is bound to 127.0.0.1, which Docker’s port publishing docs say makes it reachable only from the Docker host:
services:
web:
image: nginx:stable
ports:
- "127.0.0.1:8080:80"
volumes:
- ./site:/usr/share/nginx/html:ro
restart: unless-stopped
sudo docker compose up -d
sudo docker compose ps
curl http://127.0.0.1:8080
sudo docker compose logs web
Success check: curl on the server returns your page, while curl -m 5 http://203.0.113.10:8080 from your computer fails. After a reboot, sudo docker compose ps shows the container running again: Docker starts on boot by default on Ubuntu, and restart: unless-stopped brings the container back.
Break it on purpose: change the port line to "8080:80" and run sudo docker compose up -d again. Now the page loads from your computer, even though sudo ufw status has no rule for 8080: Docker routes published ports before UFW’s rules apply. Put 127.0.0.1 back, and read the Docker and UFW section of our Docker guide for the permanent fixes.
What if a lab breaks or locks you out?
Most lab failures print one of a handful of messages. Match the message below and fix the cause. If a server is beyond saving, delete it and deploy a fresh one: that is the point of the method.
| You see | Likely cause | Fix |
|---|---|---|
Connection timed out on a new SSH login after ufw enable | UFW is active without an SSH rule, or the IP address is wrong | Log in through the VNC console in the portal and run sudo ufw allow OpenSSH; the lockout recovery steps cover the rest |
Permission denied (publickey) | Root and password logins are off since Lab 5, or the key is missing from that user’s ~/.ssh/ | Log in as your sudo user with your key; compare fingerprints with ssh-keygen -lf as in Lab 5 |
REMOTE HOST IDENTIFICATION HAS CHANGED | A fresh server received an IP address your computer has connected to before | Check the new fingerprint, then run ssh-keygen -R 203. on your computer; the SSH troubleshooting guide explains why |
status= in systemctl status | The ExecStart= program is missing or not executable | Check the path, run sudo chmod +x on the file, then restart the unit |
403 Forbidden from nginx | The www-data user cannot enter or read the site folder | Read /var/, then give the folder mode 755 |
The following packages have been kept back | A phased update | Nothing: Ubuntu says you can safely ignore it, and security updates are never phased |
| A container answers from the internet, but UFW has no rule for its port | Docker-published ports bypass UFW | Publish on 127. as in Lab 8, or apply the Docker and UFW fixes |
After the labs: LFCS, RHCSA and AlmaLinux or Rocky Linux
The eight labs cover a good share of what the entry-level admin exams test, but not all of it. The LFCS gives you 2 hours to solve tasks from a command line, is no longer tied to one distribution, and weights its domains like this: Operations Deployment 25%, Networking 25%, Storage 20%, Essential Commands 20%, Users and Groups 10%. Storage is the clearest gap: partitions, LVM, file systems and /etc/fstab deserve a ninth lab of their own.
Red Hat’s RHCSA (EX200) is based on Red Hat Enterprise Linux 10, gives no internet access during the exam, and, like all Red Hat performance-based exams, requires configurations to persist after a reboot without intervention. HourlyVPS also offers AlmaLinux and Rocky Linux images, both RHEL-compatible, so you can repeat the labs there. These are the changes to expect:
| Task | Ubuntu 24.04 (these labs) | AlmaLinux or Rocky Linux |
|---|---|---|
| Packages | apt, dpkg | dnf, rpm |
| Firewall | ufw | firewalld: firewall-cmd --permanent --add-service=, then firewall-cmd --reload; rules without --permanent are lost on restart |
| Admin group | sudo: adduser alex sudo | wheel: usermod --append -G wheel alex |
| Mandatory access control | AppArmor, installed and loaded by default; check with sudo aa-status | SELinux, enforcing by default on RHEL; check with getenforce |
Free official resources to pair with the labs
These are free, maintained by the people who build the software or teach it, and they work on a VPS just as well as on a local VM:
| Resource | Publisher | What you get | Pair with |
|---|---|---|---|
| Welcome to the terminal, The command line in depth and Managing your software | Canonical (Ubuntu Server docs) | Three step-by-step tutorials, written for a Multipass VM | Labs 1 to 3 |
| Command-line cheat sheet | Canonical | One-page reference with links to each man page | Every lab |
| Introduction to Linux (LFS101) | The Linux Foundation | Free course, 60 hours of material, beginner level | Labs 1 to 7 |
| Linux Upskill Challenge | Community project, CC BY 4.0 | A month-long course, Day 0 to Day 21 at 1 to 2 hours a day, on “your own Internet-exposed server” | All labs, as a slower path |
| The Missing Semester of Your CS Education | MIT | 2026 lecture series on the shell, tools, Git and more | Labs 1 and 7 |
| Bandit | OverTheWire | An SSH wargame “aimed at absolute beginners” | Labs 1 and 5 |
| systemd.service, journalctl and systemd.time manual pages | systemd project, Ubuntu 24.04 versions | The reference behind every unit file in Labs 4 and 7 | Labs 4 and 7 |
| Docker Engine documentation | Docker | Install, networking and Compose reference | Lab 8 |
New to servers altogether? Read what a VPS is first. After Lab 8, put the skills to work on a first real project:
- Host a Discord bot on a VPS: a systemd service that restarts after a crash (Lab 4).
- Run a Telegram bot on a VPS: a service, its logs and a firewall rule (Labs 4 and 5).
- Self-host n8n with Docker Compose: containers, volumes and safe port binding (Lab 8).
- Set up a WireGuard VPN for a trip: networking and UFW on a server you delete afterwards (Lab 5).
Plans are on the pricing page, and hourly VPS billing keeps each lab’s cost to the hours its server exists.
Deploy this setup
Deploy a fresh Ubuntu 24.04 server for one two-hour Linux lab
Quartz Q1 · 1 shared vCPU · 1 GB RAM · 25 GB NVMe · Istanbul
- Per hour$0.01/hourFor this job
- Per day (24 h)$0.24/day
- Monthly cap$5.00/month
Starts with a $5 initial credit, which goes into the server’s balance and pays for its hours.
Billed by the hour, never more than $5.00 per billing period. Delete the server and billing stops.



