Lessons 1-3
This post is part of my “Self-Hosting from Scratch” series.
Tired of paying AWS, Render, or Fly.io while juggling multiple projects? I’m documenting how I built my own self-hosting setup, step by step, with all the mistakes, fixes, and lessons learned along the way.
Series roadmap:
Lesson 4: Docker & Docker Compose
Lesson 5: Reverse Proxy with Traefik
Lesson 6: Cloudflare Tunnel (public HTTPS without router configs)
Lesson 7: Smoke Tests with Whoami
Lesson 8: Postgres in Docker (volumes, healthchecks, backups)
…and more coming soon
Bookmark this series and follow along. By the end, you’ll be running your own server confidently
End goal for today: you’ll finish with a clean, secure Ubuntu server you can log into from your laptop using SSH keys, with a firewall and automatic security updates. This is the bedrock we’ll reuse for Docker, Traefik, Cloudflare Tunnel, databases, and file storage in later lessons.
I’ll teach this the way I built mine, so you understand why each command exists, when to use it, and how to fix common mistakes without panic.
Roadmap (today & next)
Today (Lessons 1–3):
Hardware choices (home server vs small PC vs VPS)
Install Ubuntu Server (LTS) & first boot
Secure access: SSH keys, firewall, updates
Coming next (in order):
Docker & Compose → Traefik → Cloudflare Tunnel → “whoami” smoke test → Postgres → Prisma → Node/Express API → Cloudflare R2/S3-compatible storage → presigned uploads → observability → clean dev/prod separation.
Throughout the series I’ll also call out real errors we hit (e.g., Traefik 404s from an unhealthy container, Prisma OpenSSL mismatch, S3 CORS quirks) and how we fixed them, so you won’t burn a day like we did.
LESSON 1: Hardware (pick what fits your budget)
The decision tree (plain English)
-
£0–£50: Reuse an old PC or laptop.
- Pros: free, quick start. Cons: power draw, older disks.
-
£120–£300: Refurbished mini PC (Lenovo Tiny, Dell Micro, Intel NUC).
- Sweet spot: 16–32 GB RAM, 256+ GB SSD, quiet, low power.
-
£5–£10/mo: VPS (Hetzner/Contabo/Linode).
- Pros: always-on, public IP. Cons: monthly cost.
Minimum specs for this course
CPU: any x86_64 from the last ~10 years
RAM: 8 GB (16 GB if you’ll run DB + object storage + search)
Disk: 128 GB SSD (NVMe ideal). Start simple (single disk).
Network: wired ethernet if possible (Wi-Fi works, but is fussier)
Quality-of-life add-ons (optional)
UPS (mini battery) so a power blip won’t corrupt databases.
Label the box with its hostname & static IP; you’ll thank yourself later.
Checkpoint: pick your machine. If it’s at home, plug in ethernet. If it’s a VPS, note the provided IP and login.
LESSON 2 — Ubuntu Server (Install & first boot)
We’ll install Ubuntu Server 24.04 LTS because it’s stable and well-supported by Docker.
2.1 Create install media
Download the Ubuntu Server 24.04 LTS ISO.
Flash it to a USB stick with balenaEtcher (or Rufus on Windows).
* Why: it writes the ISO in a bootable format safely.
2.2 BIOS/UEFI boot
- Plug the USB into your server, power on, press F12 / F2 / DEL / ESC to open the boot menu, choose the USB device.
2.3 The installer (what to pick & why)
Keyboard/Language: defaults usually fine.
Network: DHCP is OK for now (we’ll use Cloudflare Tunnel later, so no port-forwarding).
Storage: “Use entire disk”. LVM optional. Start simple.
User: create an admin user (this account gets
sudo
).OpenSSH: Enable it (lets you log in remotely).
Reboot, remove USB.
If you see a hostname prompt, choose something memorable, e.g.,
blackscript-server
ororion
.
2.4 First login (locally or over SSH)
On the server screen you’ll see an IP address (e.g., 192.168.1.50
).
From your laptop/desktop terminal:
ssh youruser@192.168.1.50
ssh
opens a secure shell to the server.First time, you’ll be asked to trust a fingerprint → type
yes
, then your password.
2.5 Update the system (security baseline)
sudo apt update
sudo apt upgrade -y
apt update
refreshes the package list.apt upgrade -y
applies upgrades immediately.
Checkpoint: run hostnamectl
(see your hostname) and ip a | grep inet
(confirm the IP). If SSH works and updates completed, you’re ready for security hardening.
LESSON 3 — Secure Access (SSH keys, firewall, updates)
We’ll get you to a place where you log in with a key (not a password), a firewall protects the box, and security updates auto-apply.
⚠️ Safety tip: keep one SSH session open while you change settings. If you misconfigure, you still have a lifeline.
3.1 Generate an SSH key (on your laptop)
If you don’t have one already:
ssh-keygen -t ed25519 -C "you@example.com"
What it does:
Creates a keypair in
~/.ssh/
(id_ed25519
+id_
ed25519.pub
).-t ed25519
is a modern, secure key type.-C
adds a label so you recognize it later.Set a passphrase when prompted (local theft protection).
3.2 Copy your key to the server
Still on your laptop:
ssh-copy-id youruser@192.168.1.50
What it does:
Appends your public key to the server’s
~/.ssh/authorized_keys
.After this, your key can log in without typing the server password every time.
If
ssh-copy-id
isn’t available, you can copy manually:cat ~/.ssh/id_ed25519.pub | ssh youruser@192.168.1.50 "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
3.3 Test key login
Close your SSH session and reconnect:
ssh youruser@192.168.1.50
If it logs you in without asking the server password (it may ask your key passphrase), you’re good.
3.4 Enable & configure the firewall (UFW)
sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status
allow OpenSSH
opens port 22 so you don’t lock yourself out.enable
turns the firewall on.status
shows allowed rules.
Later, when we run Traefik, we’ll open HTTP (80). With Cloudflare Tunnel you often don’t need to open public ports at all, but we’ll cover both patterns.
3.5 Harden SSH (disable password & root logins)
sudo nano /etc/ssh/sshd_config
Find/set:
PasswordAuthentication no
PermitRootLogin no
Save and restart SSH:
sudo systemctl restart ssh
Why:
Password logins are bruteforced on the public internet. Keys are safer.
Root login disabled reduces blast radius.
Recovery if you lock yourself out:
Use the console/KVM/monitor+keyboard to revert
PasswordAuthentication yes
temporarily.Or re-enable via cloud provider console.
Always keep one SSH window open while testing changes.
3.6 Automatic security updates
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Installs a service that applies security updates automatically.
The reconfigure step enables it persistently.
You can inspect logs in
/var/log/unattended-upgrades/
.
Bonus safety nets (optional but nice)
A. Create a friendly SSH alias on your laptop
Add to ~/.ssh/config
:
Host home-server
HostName 192.168.1.50
User youruser
IdentityFile ~/.ssh/id_ed25519
Now you can do ssh home-server
.
B. Timezone & NTP (useful for logs)
sudo timedatectl set-timezone Europe/London
timedatectl
C. Basic fail2ban (blocks repeated auth failures. Handy if you expose SSH)
sudo apt install -y fail2ban
Defaults are sensible; we can tune later if you open SSH to the world.
“What can go wrong?” (real mistakes we’ve made)
Locked myself out of SSH.
I disabled password logins before confirming key auth worked.
Fix: Console in, setPasswordAuthentication yes
, restart ssh, copy key correctly, then disable again.UFW dropped my SSH.
I enabled UFW without allowing OpenSSH first.
Fix: Console in,sudo ufw allow OpenSSH
,sudo ufw enable
.No network after reboot.
Router changed IP via DHCP.
Fix: Check the screen/ip a
for the new IP. Later we’ll set a DHCP reservation or a static IP.
Bigger errors we’ll meet later (and solve):
Traefik 404s because the backend container was unhealthy (our healthcheck hit a non-existent
/healthz
).Prisma OpenSSL mismatch (client built for
openssl-1.1.x
but runtime had3.0.x
). We fixed it by settingbinaryTargets = ["native","debian-openssl-3.0.x"]
and generating in the build image.S3 CORS headaches on MinIO; we worked around by injecting CORS at Traefik.
You’ll see those fixes in context when we reach those lessons.
Recap (what you achieved today)
Picked sensible hardware that won’t wreck your wallet.
Installed Ubuntu Server 24.04 LTS the right way (with SSH).
Logged in from your laptop, enabled a firewall, switched to key-based auth, and turned on automatic security updates.
This is the foundation. With this done, everything else (Docker, Traefik, tunnels, DBs) becomes straightforward and repeatable.
Top comments (0)