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.
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.
Your own Dropbox/Google Drive replacement. Sync photos from your phone, share folders with family, no monthly fee and no data mining.
Stream your movie and music library to any TV, phone or tablet on your network — and remotely when you are away.
Automated, versioned, encrypted backups of every laptop in the house. The number one reason people finally build one.
Spin up a virtual machine, break it, delete it, repeat. Docker, Proxmox, ZFS, VLANs — learn by doing, with zero risk.
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.
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. |
An old laptop or desktop you already own. Install Ubuntu Server, add a USB SSD, done. Perfect for learning Docker and Linux.
A used SFF office PC (Dell OptiPlex, HP EliteDesk, Lenovo ThinkCentre) plus two drives. The best value in home labs, by a wide margin.
A quiet, efficient custom tower with room to grow. Buy this only after you know exactly what you want to run.
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.
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.
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.
The default recommendation. Enormous community, every tutorial online targets it, and Docker support is first class.
Rock solid and minimal. Ubuntu is built on Debian, so almost every command you learn transfers directly.
A bare-metal hypervisor. Instead of one server, you get many virtual machines and containers on the same box.
Storage first. Built around ZFS, with a polished web interface and a growing app catalogue.
Paid, but uniquely flexible: mix any drive sizes in one pool and add disks one at a time without rebuilding.
Debian with a NAS web interface on top. Lightweight and free, good for pure file-serving duty.
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.
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.
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.
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.
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.
lsblk # Linux: find the USB (e.g. /dev/sdb)
diskutil list # macOS: find the disk (e.g. /dev/disk2)
sudo dd if=ubuntu-24.04-live-server-amd64.iso of=/dev/sdX bs=4M status=progress oflag=sync
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.
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.
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.
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.
ssh-keygen -t ed25519 -C "homelab"
ssh-copy-id admin@192.168.1.50
ssh admin@192.168.1.50
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 #):
PubkeyAuthentication yes
PasswordAuthentication no
PermitRootLogin no
X11Forwarding no
MaxAuthTries 3
sudo sshd -t && sudo systemctl restart ssh
# Keep your current session open and test a NEW connection before closing it!
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.
A server whose address changes will break every bookmark, share and container you set up. Pin it with Netplan (Ubuntu 18.04 and newer).
ip -br link show
ls /etc/netplan/
sudo cp /etc/netplan/50-cloud-init.yaml /etc/netplan/50-cloud-init.yaml.bak
sudo nano /etc/netplan/50-cloud-init.yaml
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]
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
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.
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
If you run ufw enable without allowing port 22 first, you will lock yourself out and need physical access to the machine.
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.
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker
docker run hello-world
docker compose version
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.
Format your data disks, then mount them permanently by UUID. Never use /dev/sdb in fstab — device names can shuffle between reboots.
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
UUID=your-uuid-here /mnt/data ext4 defaults,nofail 0 2
sudo mount -a
df -h /mnt/data
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.
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.
mkdir -p ~/homelab && cd ~/homelab
nano 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
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.
Samba lets Windows, macOS, Linux and Android devices browse your server like a normal network folder.
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
[media]
path = /mnt/data/media
browseable = yes
read only = no
valid users = admin
create mask = 0664
directory mask = 0775
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.
Instead of remembering port numbers, give each service a hostname. Caddy handles HTTPS certificates automatically, which makes it the easiest option to start with.
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
status.home.lan {
reverse_proxy 192.168.1.50:3001
}
files.home.lan {
reverse_proxy 192.168.1.50:8080
}
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo systemctl status caddy --no-pager
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.
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.
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.
Enable MagicDNS in the Tailscale admin console and you can reach your server by name instead of IP, from any device on your tailnet.
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.
Full private cloud: file sync, calendars, contacts, photo backup from your phone. The most complete Google Drive replacement.
Fully open source media server. Streams movies, shows and music to any device with no subscription and no telemetry.
Network-wide ad blocking. Point your router's DNS at it and every device in the house gets cleaner, faster browsing.
Automate lights, sensors, cameras and appliances. Works with thousands of devices and keeps all automation local.
A lightweight Bitwarden-compatible password manager. Your credentials stay encrypted on hardware you control.
Monitors every service and pings you when something goes down. Deploy this early — it tells you when things break.
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
/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.
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.
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.
Every service listens on a numbered port. If a web page will not load, the port is the first thing to check.
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.
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.
sudo apt install -y fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /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
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.
A second disk or NAS in the same room. Fast to restore from, but does not protect against fire, theft or flood.
Encrypted copies to cloud storage or a friend's house. Slow to restore, but survives disaster. Backblaze B2 is affordable.
An untested backup is not a backup. Restore a file every few months and confirm it actually opens. Automate everything else.
Restic creates encrypted, deduplicated, versioned snapshots. Deduplication means your second backup only stores what changed, so backups stay small and fast.
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
Without it, your backups are mathematically unrecoverable. Store it in a password manager. There is no reset option.
restic backup /home /mnt/data --exclude-caches --exclude '*.tmp'
restic snapshots
restic restore latest --target /tmp/restore-test
restic check
crontab -e
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
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.
The commands you will actually type again and again. Bookmark this section.
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
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
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
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
Add shortcuts to ~/.bashrc and run source ~/.bashrc to activate them.
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'
Honest answers to the things beginners ask most.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.