Beginner friendly · Fully copy-paste ready

Build your own home server
and home lab, step by step.

Turn an old office PC or a small custom build into a private cloud, media server, file share and automation hub. This guide covers the hardware you actually need, the operating system to pick, and every command to get running — explained in plain language.

12
Setup steps
6
OS options compared
40+
Copy-paste commands
$0–$600
Realistic budget range
Start here

What is a home lab, and why build one?

A home lab is simply a computer you own that runs services for you and your household, instead of renting them from a cloud provider. It is the single best way to learn Linux, networking, virtualization and storage — while actually getting useful things out of it.

Private file cloud

Your own Dropbox/Google Drive replacement. Sync photos from your phone, share folders with family, no monthly fee and no data mining.

Media server

Stream your movie and music library to any TV, phone or tablet on your network — and remotely when you are away.

Backups that work

Automated, versioned, encrypted backups of every laptop in the house. The number one reason people finally build one.

Learning platform

Spin up a virtual machine, break it, delete it, repeat. Docker, Proxmox, ZFS, VLANs — learn by doing, with zero risk.

The golden rule for beginners

Do not buy anything until you have decided what the server will do. A $150 used office PC running Docker will beat a $1200 rack server that you never finish configuring.

Hardware

Parts required — the honest list

You need far less than the internet suggests. Here is what actually matters, what you can skip, and what a sensible budget looks like at three different levels.

Component Recommended Budget pick Why it matters
CPU Intel Core i5-12400 / Ryzen 5 5600G Used Intel i5-8500T SFF PC Intel 8th gen or newer includes QuickSync, which handles Plex/Jellyfin transcoding without a GPU.
Motherboard B660M / B550M with 4+ SATA ports Whatever ships in the used PC Count SATA ports before buying. Storage expands faster than you expect.
RAM 32 GB DDR4/DDR5 16 GB DDR4 8 GB is the practical floor for Docker. Virtual machines eat RAM fast.
Boot drive 500 GB NVMe SSD 240 GB SATA SSD Keep the OS and containers separate from your bulk data. Never boot from a USB stick.
Data drives 2 × 8 TB CMR HDD 2 × 4 TB HDD Buy CMR, not SMR. Two identical drives let you run a mirror, so one failure loses nothing.
Power supply 450–550 W 80+ Bronze Included with the case/PC Home servers idle low. Quality and efficiency matter more than wattage.
Case Fractal Node 304 / Define R5 Any old ATX tower Drive bays matter more than looks. Airflow keeps drives alive.
Network card 2.5 GbE PCIe NIC Onboard 1 GbE 1 GbE gives ~110 MB/s. Fine to start; upgrade when transfers annoy you.
UPS 650 VA line-interactive Skip for now A sudden power cut during a write is the fastest way to corrupt a filesystem.

Tier 1 — Free

An old laptop or desktop you already own. Install Ubuntu Server, add a USB SSD, done. Perfect for learning Docker and Linux.

Old laptop 1 SSD 8–16 GB RAM

Tier 2 — ~$150–$250

A used SFF office PC (Dell OptiPlex, HP EliteDesk, Lenovo ThinkCentre) plus two drives. The best value in home labs, by a wide margin.

8th gen i5 16 GB RAM 2 × 4 TB HDD

Tier 3 — ~$500–$800

A quiet, efficient custom tower with room to grow. Buy this only after you know exactly what you want to run.

i5-12400 32 GB RAM 2 × 8 TB CMR
Avoid these beginner traps

Do not buy a used enterprise rack server (Dell R720, HP DL380). They are cheap to buy, extremely loud, and can add $15–$40 per month to your power bill. Do not buy SMR hard drives for a NAS. Do not buy a Raspberry Pi expecting to transcode video.

What it costs to run

A typical home server idles around 35–50 W. At 40 W continuous and $0.15/kWh, that is roughly $4.30 per month. Measure yours with a plug-in power meter if you want exact numbers.

Software

Choosing your operating system

This is the single most important decision, because it is the hardest thing to change later. Pick based on what you want to run, not on what sounds most impressive.

Ubuntu Server LTS Start here

The default recommendation. Enormous community, every tutorial online targets it, and Docker support is first class.

  • Best documentation and search results
  • 5 years of security updates
  • Great for Docker and general services

Debian 12

Rock solid and minimal. Ubuntu is built on Debian, so almost every command you learn transfers directly.

  • Extremely stable, very light
  • No telemetry, no surprises
  • Slightly older package versions

Proxmox VE

A bare-metal hypervisor. Instead of one server, you get many virtual machines and containers on the same box.

  • Run multiple OSes at once
  • Web UI for everything
  • Steeper learning curve

TrueNAS SCALE

Storage first. Built around ZFS, with a polished web interface and a growing app catalogue.

  • Best-in-class data protection
  • Wants ECC RAM and real drives
  • Less flexible for odd workloads

Unraid

Paid, but uniquely flexible: mix any drive sizes in one pool and add disks one at a time without rebuilding.

  • Add mismatched drives freely
  • Excellent app store
  • Costs money, not open source

OpenMediaVault

Debian with a NAS web interface on top. Lightweight and free, good for pure file-serving duty.

  • Very light on resources
  • Free and open source
  • Smaller community than Ubuntu
Still undecided?

Install Ubuntu Server LTS and run everything in Docker. You can always wipe and reinstall later — you will lose nothing but time, and you will understand your own requirements far better by then.

Step by step

From bare metal to running server

Follow these in order. Every command block has a copy button in the top-right corner. Replace IP addresses, usernames and disk names with your own before running anything.

01

Plan before you touch hardware

Write down three things: the IP address range of your home network, which services you want, and where the server will physically live. Ten minutes here saves hours later.

A sane starting plan

Router: 192.168.1.1 · Server static IP: 192.168.1.50 · Subnet: /24 · DNS: 1.1.1.1. Choose an IP outside your router's DHCP pool so it never gets handed to another device.

02

Create the installation USB

Download the Ubuntu Server ISO, then write it to a USB stick (8 GB minimum). On Windows or macOS, use a GUI tool. On Linux, dd is fastest.

bash — identify your USB device first
lsblk                          # Linux: find the USB (e.g. /dev/sdb)
diskutil list                  # macOS: find the disk (e.g. /dev/disk2)
bash — write the ISO (DESTRUCTIVE, check the device!)
sudo dd if=ubuntu-24.04-live-server-amd64.iso of=/dev/sdX bs=4M status=progress oflag=sync
This erases the target device

Double-check the device name with lsblk before running dd. Writing to the wrong device will destroy your data. On Windows, use Rufus or balenaEtcher instead.

03

Install the OS and update everything

Boot from the USB, follow the installer, and choose the "Ubuntu Server" option (not the minimized one). Enable OpenSSH when prompted. After the first reboot, log in and update immediately.

bash — first boot
sudo apt update && sudo apt full-upgrade -y
sudo apt install -y curl wget git htop vim ufw ca-certificates gnupg
sudo timedatectl set-timezone America/New_York
sudo reboot

Change the timezone to match your location. Run timedatectl list-timezones to find yours.

04

Harden SSH access

The installer created a user. Now switch from password logins to SSH keys, and turn off password authentication entirely. This blocks the vast majority of automated attacks.

bash — on your laptop, copy your key to the server
ssh-keygen -t ed25519 -C "homelab"
ssh-copy-id admin@192.168.1.50
ssh admin@192.168.1.50
bash — on the server: edit the SSH config
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
sudo nano /etc/ssh/sshd_config

Find and set these lines (remove any leading #):

config — /etc/ssh/sshd_config
PubkeyAuthentication yes
PasswordAuthentication no
PermitRootLogin no
X11Forwarding no
MaxAuthTries 3
bash — validate and restart
sudo sshd -t && sudo systemctl restart ssh
# Keep your current session open and test a NEW connection before closing it!
Never close your only session

Open a second terminal and confirm key-based login works before you disconnect. If it fails, you still have the original session to fix it.

05

Give the server a static IP

A server whose address changes will break every bookmark, share and container you set up. Pin it with Netplan (Ubuntu 18.04 and newer).

bash — find your interface name
ip -br link show
ls /etc/netplan/
bash — back up, then edit
sudo cp /etc/netplan/50-cloud-init.yaml /etc/netplan/50-cloud-init.yaml.bak
sudo nano /etc/netplan/50-cloud-init.yaml
yaml — replace eth0 and the addresses with your own
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: false
      addresses:
        - 192.168.1.50/24
      routes:
        - to: default
          via: 192.168.1.1
      nameservers:
        addresses: [1.1.1.1, 9.9.9.9]
bash — apply and verify
sudo netplan try      # auto-reverts after 120s if you lose connection
sudo netplan apply
ip -br addr show
ping -c 3 1.1.1.1
06

Turn on the firewall

Deny everything inbound by default, then open only what you need. This takes two minutes and is the highest-value security step on this page.

bash — UFW basics
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp          # SSH - do this BEFORE enabling!
sudo ufw allow 80,443/tcp      # web / reverse proxy
sudo ufw enable
sudo ufw status verbose
Allow SSH before enabling

If you run ufw enable without allowing port 22 first, you will lock yourself out and need physical access to the machine.

07

Install Docker and Docker Compose

Docker is how you will run almost everything from now on. Each service lives in its own isolated container, so one broken app cannot take down the server.

bash — official convenience script
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker
docker run hello-world
docker compose version
Why newgrp docker?

Adding yourself to the docker group only takes effect in new sessions. newgrp applies it immediately so you can run Docker without sudo.

08

Mount your data drives

Format your data disks, then mount them permanently by UUID. Never use /dev/sdb in fstab — device names can shuffle between reboots.

bash — format and mount (replace sdX with YOUR drive)
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
sudo mkfs.ext4 -L data /dev/sdX1
sudo mkdir -p /mnt/data
sudo blkid /dev/sdX1              # copy the UUID from the output
sudo nano /etc/fstab
config — add to /etc/fstab
UUID=your-uuid-here  /mnt/data  ext4  defaults,nofail  0  2
bash — test the mount without rebooting
sudo mount -a
df -h /mnt/data
The nofail option matters

Without it, a failed drive mount can drop your server into emergency mode and it will not boot. Always include nofail for data disks.

09

Deploy your first services

Create a folder for your stacks and describe your apps in one docker-compose.yml file. Docker Compose is declarative — the file is your documentation.

bash — create the project folder
mkdir -p ~/homelab && cd ~/homelab
nano docker-compose.yml
yaml — ~/homelab/docker-compose.yml
services:

  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    restart: unless-stopped
    ports:
      - "9443:9443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./portainer:/data

  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - "3001:3001"
    volumes:
      - ./uptime-kuma:/app/data

  filebrowser:
    image: filebrowser/filebrowser:latest
    container_name: filebrowser
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - /mnt/data:/srv
      - ./filebrowser.db:/database.db
bash — start, inspect, follow logs
docker compose up -d
docker compose ps
docker compose logs -f --tail=50

You now have: Portainer at https://192.168.1.50:9443 for managing containers, Uptime Kuma at http://192.168.1.50:3001 for monitoring, and FileBrowser at http://192.168.1.50:8080 for web file access.

10

Set up a network file share

Samba lets Windows, macOS, Linux and Android devices browse your server like a normal network folder.

bash — install Samba and prepare a share
sudo apt install -y samba
sudo mkdir -p /mnt/data/media
sudo chown -R $USER:$USER /mnt/data/media
sudo smbpasswd -a $USER
sudo cp /etc/samba/smb.conf /etc/samba/smb.conf.bak
sudo nano /etc/samba/smb.conf
config — append to /etc/samba/smb.conf
[media]
   path = /mnt/data/media
   browseable = yes
   read only = no
   valid users = admin
   create mask = 0664
   directory mask = 0775
bash — validate and restart
testparm -s
sudo systemctl restart smbd nmbd
sudo ufw allow from 192.168.1.0/24 to any app Samba

Connect from Windows with \\192.168.1.50\media, or from macOS Finder with smb://192.168.1.50/media.

11

Add a reverse proxy for clean URLs

Instead of remembering port numbers, give each service a hostname. Caddy handles HTTPS certificates automatically, which makes it the easiest option to start with.

bash — install Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy
config — /etc/caddy/Caddyfile
status.home.lan {
    reverse_proxy 192.168.1.50:3001
}

files.home.lan {
    reverse_proxy 192.168.1.50:8080
}
bash — reload Caddy
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo systemctl status caddy --no-pager
Local DNS names

To use names like status.home.lan you need local DNS. Options: add entries to your router's DNS, run Pi-hole/AdGuard Home on the server, or simply add lines to each device's hosts file.

12

Secure remote access with Tailscale

Do not forward ports on your router to reach your server from outside. That exposes it to the entire internet. A mesh VPN like Tailscale is safer, faster to set up, and free for personal use.

bash — install and authenticate
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
tailscale ip -4
tailscale status

Install the Tailscale app on your phone and laptop, sign in to the same account, and your server becomes reachable from anywhere at its 100.x.x.x address — with no open ports on your router.

Bonus: MagicDNS

Enable MagicDNS in the Tailscale admin console and you can reach your server by name instead of IP, from any device on your tailnet.

Self-hosted apps

Services worth running first

Start with two or three. Add more only when you actually miss them. Each one below is a Docker container you can add to your existing compose file.

Nextcloud

Full private cloud: file sync, calendars, contacts, photo backup from your phone. The most complete Google Drive replacement.

FilesPhotosCalendar

Jellyfin

Fully open source media server. Streams movies, shows and music to any device with no subscription and no telemetry.

MoviesMusicFree

Pi-hole / AdGuard

Network-wide ad blocking. Point your router's DNS at it and every device in the house gets cleaner, faster browsing.

DNSAd blocking

Home Assistant

Automate lights, sensors, cameras and appliances. Works with thousands of devices and keeps all automation local.

Smart homeAutomation

Vaultwarden

A lightweight Bitwarden-compatible password manager. Your credentials stay encrypted on hardware you control.

PasswordsLightweight

Uptime Kuma

Monitors every service and pings you when something goes down. Deploy this early — it tells you when things break.

MonitoringAlerts
yaml — example: adding Jellyfin to your stack
  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    restart: unless-stopped
    user: "1000:1000"
    ports:
      - "8096:8096"
    volumes:
      - ./jellyfin/config:/config
      - ./jellyfin/cache:/cache
      - /mnt/data/media:/media:ro
    devices:
      - /dev/dri:/dev/dri    # Intel QuickSync hardware transcoding
The /dev/dri device

Passing this through gives Jellyfin access to Intel integrated graphics for hardware transcoding, which can cut CPU usage from 100% to under 15% during playback. Only works on Intel CPUs with QuickSync.

Networking

Networking essentials

You do not need to be a network engineer. You need to understand four things: static IPs, ports, DNS, and why port forwarding is dangerous.

Static IP vs DHCP reservation

Two ways to keep your server's address fixed. A static IP set on the server itself is the most reliable and works even if your router is replaced. A DHCP reservation on the router is easier but ties you to that router.

  • Pick an IP outside the DHCP pool
  • Document it somewhere you will find again
  • Never use two static IPs for the same machine

Ports you should know

Every service listens on a numbered port. If a web page will not load, the port is the first thing to check.

22SSH
80 / 443HTTP / HTTPS
3001Uptime Kuma
8096Jellyfin
9443Portainer
445SMB file sharing

Why not to port forward

Opening port 443 to the internet exposes your server to constant automated scanning. Within hours you will see login attempts in your logs. Use Tailscale, WireGuard or Cloudflare Tunnel instead.

  • VPN access: nothing is exposed
  • Reverse proxy with auth as a second layer
  • Fail2ban to auto-ban brute force attempts

Fail2ban: automatic banning

Watches your logs and temporarily blocks IPs that repeatedly fail to log in. A five-minute install that dramatically reduces noise in your auth logs.

bash — install Fail2ban with an SSH jail
sudo apt install -y fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
config — add to /etc/fail2ban/jail.local
[sshd]
enabled  = true
port     = ssh
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 5
bantime  = 3600
findtime = 600
sudo systemctl restart fail2ban && sudo fail2ban-client status sshd
Data safety

Backups and maintenance

A server without backups is a countdown timer. Follow the 3-2-1 rule: three copies of your data, on two different types of media, with one copy off-site.

1. Local backup

A second disk or NAS in the same room. Fast to restore from, but does not protect against fire, theft or flood.

2. Off-site backup

Encrypted copies to cloud storage or a friend's house. Slow to restore, but survives disaster. Backblaze B2 is affordable.

3. Test your restores

An untested backup is not a backup. Restore a file every few months and confirm it actually opens. Automate everything else.

Automated backups with Restic

Restic creates encrypted, deduplicated, versioned snapshots. Deduplication means your second backup only stores what changed, so backups stay small and fast.

bash — install and initialise a repository
sudo apt install -y restic
sudo mkdir -p /mnt/backup/restic

export RESTIC_REPOSITORY=/mnt/backup/restic
export RESTIC_PASSWORD='replace-with-a-long-passphrase'
restic init
Write down your restic password

Without it, your backups are mathematically unrecoverable. Store it in a password manager. There is no reset option.

bash — run a backup, list snapshots, restore
restic backup /home /mnt/data --exclude-caches --exclude '*.tmp'
restic snapshots
restic restore latest --target /tmp/restore-test
restic check
bash — schedule it nightly with cron
crontab -e
cron — nightly backup at 03:00, prune old snapshots on Sundays
0 3 * * *  /usr/bin/restic backup /home /mnt/data --quiet
0 5 * * 0  /usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
Cron does not read your shell exports

The RESTIC_REPOSITORY and RESTIC_PASSWORD variables you exported manually are not available to cron. Either put them in a small script that cron calls, or add them to root's crontab at the top of the file.

Reference

Command cheat sheet

The commands you will actually type again and again. Bookmark this section.

bash — Docker
docker ps -a                          # list all containers, running and stopped
docker compose up -d                  # start everything in the compose file
docker compose down                   # stop and remove containers
docker compose pull && docker compose up -d   # update all images
docker compose logs -f --tail=100     # follow logs for the whole stack
docker compose restart jellyfin       # restart one service
docker stats                          # live CPU / RAM / network per container
docker system prune -a                # reclaim disk space from unused images
bash — system and storage
df -h                                 # disk usage by filesystem
du -sh /mnt/data/* | sort -h          # folder sizes, largest last
ncdu /mnt/data                        # interactive disk usage explorer
free -h                               # RAM and swap
htop                                  # interactive process viewer
uptime                                # load average and uptime
lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT  # block devices and mounts
bash — systemd and logs
systemctl status smbd                 # is a service running?
sudo systemctl restart caddy          # restart a service
sudo systemctl enable docker          # start a service at boot
journalctl -u docker --since "1 hour ago"   # logs for one service
journalctl -xe                        # recent errors across the system
journalctl --disk-usage               # how much space logs take
sudo journalctl --vacuum-time=14d     # keep only 14 days of logs
bash — networking
ip -br addr show                      # IP addresses, brief format
ip route show                         # routing table / default gateway
ss -tulpn                             # listening ports and owning processes
ping -c 3 1.1.1.1                     # connectivity test
dig google.com @1.1.1.1               # DNS lookup
curl -I http://localhost:3001         # check if a local service responds
sudo ufw status numbered              # firewall rules with index numbers
sudo ufw delete 3                     # delete rule number 3
Save time with aliases

Add shortcuts to ~/.bashrc and run source ~/.bashrc to activate them.

bash — append to ~/.bashrc
alias dc='docker compose'
alias dcu='docker compose up -d'
alias dcd='docker compose down'
alias dcl='docker compose logs -f --tail=100'
alias ports='ss -tulpn'
alias update='sudo apt update && sudo apt full-upgrade -y && sudo apt autoremove -y'
Questions

Frequently asked questions

Honest answers to the things beginners ask most.

How much RAM do I really need?

8 GB is the practical minimum — enough for a handful of containers. 16 GB is comfortable for a typical home lab with a media server, file sync and monitoring. 32 GB is where you stop thinking about it, and it matters if you plan to run virtual machines. RAM is usually the cheapest upgrade with the biggest impact.

Can I just use a Raspberry Pi?

Yes, for light workloads. A Pi 4 or Pi 5 with 8 GB handles Pi-hole, Home Assistant, a file share and small Docker containers very well while sipping power. The limits: USB-attached storage is slower and less reliable than SATA, most apps in the ecosystem are built for x86 and have no ARM images, and hardware video transcoding is not practical. Great as a second server, frustrating as your only one.

Do I need ECC memory?

No, not for a home lab. ECC detects and corrects memory errors, which matters in enterprise environments where silent data corruption is unacceptable. For a home server running media and files, non-ECC RAM is completely fine. If you build a TrueNAS box with critical data and want maximum safety, ECC is a nice-to-have — but a tested backup strategy matters far more.

Is it cheaper than just paying for cloud storage?

Not immediately. If you need 2 TB of storage, Google Drive costs around $10/month. A used SFF PC plus two 4 TB drives runs about $300 once, plus roughly $4–5/month in electricity. Break-even is around three years. But you also get media streaming, password management, automation, ad blocking, and complete control over your data — none of which cloud storage gives you. For most people the learning and the control are the real payoff.

Should I use RAID?

RAID protects against drive failure, not against deletion, ransomware, fire or theft. A two-drive mirror means if one disk dies, you swap it and keep going. It does not mean your data is safe. Always pair RAID with real backups. For beginners, two mirrored drives plus an off-site backup is the right answer.

What happens if the power goes out?

Most likely nothing. Modern filesystems (ext4, ZFS, Btrfs) are journaled and recover from sudden power loss. But a power cut during a write can corrupt files, and repeated events shorten drive life. A 650 VA UPS gives you 10–20 minutes to shut down cleanly and costs about $70. It is the best cheap insurance you can buy for a home server.

How do I access it from outside my home?

Use a mesh VPN. Install Tailscale or WireGuard on the server and on your phone and laptop. You get secure access to everything — SSH, web interfaces, file shares — from anywhere, without opening a single port on your router. It is faster to set up than port forwarding and dramatically safer. Port forwarding should be a last resort, and only with HTTPS and authentication in front of it.

I broke something. How do I start over?

Good news: that is the entire point of a home lab. Reinstalling the OS takes 20 minutes. As long as your data lives on separate drives (which is why we mounted /mnt/data separately), you can wipe and reinstall the OS without touching a single file. This is exactly why you should keep the operating system and your data on different disks.

What should I learn first?

In order: Linux command line basics, file permissions, systemd services, Docker and Docker Compose, networking fundamentals (IP, ports, DNS), then backups. That sequence takes you from beginner to fully competent. Everything else — Kubernetes, Proxmox clustering, VLANs — is optional and only worth learning once you have a real problem that requires it.

You are ready to build it

Start with whatever hardware you already own. Install Ubuntu Server. Get Docker running. Add one service. You will learn more in a weekend of tinkering than in a month of reading.