What You Need
- Hardware: One machine that can stay on: an old PC or laptop, a mini PC, a Raspberry Pi 4 or 5 for light services, or a refurbished server. An SSD and a wired Ethernet connection.
- Software: Debian 13 “trixie” or Ubuntu Server 26.04 or 24.04 LTS (or Proxmox VE 9 if you want virtual machines), Docker Engine with the Compose plugin
- Estimated Time: An afternoon for the base system and a first service; add the rest gradually
Who this is for: You're comfortable with basic Linux commands but haven't set up your own server infrastructure. Maybe you've spun up a VPS once or twice. You want to self-host applications but don't know where to start, or you tried once and got lost in the decisions. This guide walks through the whole process, from hardware to deployment to long-term maintenance, and links to a detailed tutorial for each piece.
Three things to get right from the start:
- Run services in containers with Docker Compose. It keeps each application's dependencies separate and makes upgrades and rebuilds repeatable.
- Set up backups before you put data you care about on the server, and test a restore.
- Reach your services remotely through a VPN rather than by opening ports on your router.
Why Self-Host?
Self-hosting is more work than paying for a SaaS product. Updates, backups and outages become your job, and things tend to break at inconvenient times. If you need a service to be reliably available to other people, factor that in honestly.
What you get in return:
- Control over your data: files, photos and passwords live on hardware you own, under policies you set
- No vendor lock-in: a discontinued product or a price change doesn't take your tools away
- Customization: you choose the software, the versions and how services connect
- Learning: every service teaches you something about networking, security, databases or system administration
Start with one or two services rather than trying to replace everything at once. Each new service adds something to update, back up and monitor, so add them when you actually need them.
Decide What You Want to Run
Work backwards from the problem you want to solve. The services you pick decide how much hardware you need, not the other way round. Common starting points by category:
Network and Privacy
- Pi-hole or AdGuard Home: DNS-level ad and tracker blocking for every device on your network, no browser extension needed. See the Pi-hole Deployment Guide or AdGuard Home Deployment.
- A VPN: secure access to everything else from outside (covered under Remote Access below).
- A reverse proxy: one HTTPS entry point with a clean name per service (see Reverse Proxy).
Files, Passwords and Documents
- Nextcloud: file sync, calendar and contacts. See Nextcloud Self-Hosted Cloud.
- Vaultwarden: a lightweight server compatible with the Bitwarden password manager apps. Because it holds your passwords, keep it behind a VPN and back it up carefully.
- Paperless-ngx: scans and OCRs paper documents so they're searchable.
Media and Photos
- Jellyfin or Plex: stream your own movie and music library. Transcoding is the demanding part; hardware video decoding in the CPU or GPU helps a lot.
- Immich or PhotoPrism: photo libraries that can replace a cloud photo service. Immich includes mobile apps with automatic backup.
- Audiobookshelf: audiobooks and podcasts.
Monitoring and Tinkering
- Uptime Kuma: a simple dashboard that checks whether your services respond and alerts you when they don't.
- Prometheus and Grafana: metrics and dashboards for the hosts and containers. See Prometheus Grafana Monitoring.
- Proxmox VE: if you want to run virtual machines as well as containers. See the Proxmox VE Setup Guide.
- Kubernetes (k3s): worth it if learning Kubernetes is the goal. For running a dozen services at home, Docker Compose is far simpler to operate.
Hardware: What You Actually Need
A typical first set of services (DNS filtering, a password manager, file sync, a few small web apps) runs comfortably on modest hardware. The four usual routes:
Whatever You Already Own
An old desktop or laptop is a perfectly good first server. A laptop even brings its own battery, which rides through short power cuts. Connect it with Ethernet, set it not to sleep when the lid closes, and replace a spinning hard drive with an SSD if you can. Cost: little or nothing.
Single-Board Computers (Raspberry Pi)
A Raspberry Pi 4 or 5 with 4–8 GB of RAM handles light services well: Pi-hole, a WireGuard VPN, Home
Assistant, small web apps. It's tiny, silent and uses very little power. The limits are RAM, the ARM
architecture (most popular images support arm64, but not all, and some projects have dropped
32-bit ARM), and storage: microSD cards wear out under constant writes, so boot from or store data on an
SSD over USB for anything important. It struggles with media transcoding and busy databases. Proxmox VE
doesn't support the Pi. The Raspberry Pi Setup Guide covers the
initial setup.
Mini PCs and Used Business Desktops
This is where most people should land. Small-form-factor business machines (Dell OptiPlex, HP ProDesk, Lenovo ThinkCentre Tiny and similar) and new low-power mini PCs are quiet, efficient, well documented, and run x86 images without compatibility issues. What to look for:
- CPU: a reasonably recent Intel or AMD chip; newer generations idle more efficiently and many include hardware video decoding for media servers
- RAM: 8 GB to start, 16 GB is comfortable, 32 GB if you plan to run VMs
- Storage: an SSD for the OS and containers; check whether there's room for a second drive
- Network: wired Gigabit Ethernet at minimum
Refurbished Enterprise Servers
Used rack and tower servers (Dell PowerEdge, HPE ProLiant, Lenovo ThinkSystem) offer many cores, lots of RAM slots and drive bays for little money. The catches are noise and power: their fans are designed for data centres, and their idle power draw is often several times that of a mini PC, every hour of the year. If you can, power one on and listen before you buy it, and check the idle power figure. They make sense when you need the drive bays or the RAM and have a garage or basement to put them in.
When to Go Bigger
You need more hardware when you want to run virtual machines (not just containers), media transcoding is a priority, you're hosting for more than your household, or you want redundant drives. At that point look at a machine with more RAM and drive bays, or a separate NAS (see TrueNAS Storage Setup).
Power and UPS
Power is the running cost that's easy to overlook, because a server is on around the clock. Measure the actual draw with a plug-in power meter instead of guessing, then work out the yearly energy:
watts × 24 × 365 ÷ 1000 = kWh per year
# Example: a machine idling at 20 W
20 × 24 × 365 ÷ 1000 ≈ 175 kWh per year
Multiply by your electricity rate to see the cost. The number often changes which machine you leave on 24/7 and which you power up only when needed.
UPS (Uninterruptible Power Supply)
A small UPS protects against the momentary outages and brownouts that corrupt databases and file systems. Connect it to the server over USB and configure a clean shutdown when the battery runs low. On Linux, Network UPS Tools (NUT) handles this and can also tell other machines on the network to shut down. Do this before you set up storage with important data on it.
Operating System: Just Pick One
Any mainstream server distribution works. A sensible shortlist:
Debian 13 “trixie”: the current Debian stable release, released on August 9, 2025 and supported for five years through the Debian security and LTS teams (see Debian's release announcement). Minimal and stable.
Ubuntu Server 26.04 or 24.04 LTS: the most tutorials and community answers are written for Ubuntu, which helps when you're starting out. 26.04 is the newest LTS release; 24.04 is still supported, and Docker's packages cover both.
Proxmox VE 9: if you want to run virtual machines as well as containers. It's built on Debian 13 and managed through a web interface (see the Proxmox VE Setup Guide).
Avoid rolling-release distributions (Arch, Fedora Rawhide and similar) for a server. Predictable updates matter more than having the newest packages.
Base Installation Checklist
After installing Debian or Ubuntu, a reasonable baseline:
# Update everything
sudo apt update && sudo apt upgrade -y
# Install basic tools
sudo apt install -y curl wget git htop ncdu tmux ufw
# Firewall: allow SSH, deny other incoming traffic by default
sudo ufw allow OpenSSH
sudo ufw enable
# Set the time zone
sudo timedatectl set-timezone Your/Timezone
# Work as a normal user with sudo rather than as root
# sudo adduser yourname
# sudo usermod -aG sudo yourname
Then lock down SSH (key-only login, no root login) as described in Locking Down SSH: Keys, Configs, and Fail2Ban, which also covers fail2ban.
One firewall caveat: Docker's documentation warns that ports you publish from containers
bypass ufw rules, because Docker manages its own iptables rules. Don't rely on ufw to hide
a published container port; publish only what you need, or bind it to a specific address such as
127.0.0.1:8080:80 when only a local reverse proxy should reach it.
Docker: The Foundation of Most Self-Hosting
Most self-hosted applications publish an official container image, and their documentation assumes Docker. It's worth learning properly.
Why Docker?
Without containers, installing software means:
- Installing dependencies (and hoping they don't conflict with other applications)
- Configuring paths, users and permissions by hand
- Managing upgrades (and hoping nothing breaks)
- Cleaning up leftovers when you remove something
With Docker, each application runs in its own container with its dependencies. Upgrading means pulling a new image; removing it means deleting the container. Your data lives in volumes or bind-mounted directories that you back up separately.
Installing Docker
Install Docker Engine from Docker's own apt repository, following the
Docker Installation Guide or the official instructions for
Debian
and Ubuntu.
Both cover Debian 13 and 12 and Ubuntu 26.04, 24.04 and 22.04. If you previously installed Docker from your distribution's own
packages (docker.io and friends), follow the “Uninstall old versions” step there first.
Docker also offers a convenience script. Its documentation says the script is only recommended for testing and development environments, so for a server you'll keep, prefer the repository method. On a throwaway test machine:
# Convenience script: for test machines only (see Docker's docs)
curl -fsSL https://get.docker.com | sudo sh
# Optional: let your user run docker without sudo.
# Note: membership in the docker group is equivalent to root access.
sudo usermod -aG docker $USER
# Log out and back in, then verify:
docker run hello-world
Docker Compose: Define the Whole Stack in One File
Running containers with docker run works but gets messy. Docker Compose lets you describe
the services, their volumes and settings in a YAML file that you can keep in version control and
reproduce on new hardware.
# compose.yaml example
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
restart: unless-stopped
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
volumes:
postgres_data:
There's no top-level version: key: Docker's
Compose file reference
marks it obsolete. Put secrets such as DB_PASSWORD in a .env file next to the
compose file, and keep that file out of version control.
Then docker compose up -d starts everything, docker compose down stops it, and
docker compose pull && docker compose up -d upgrades to newer images.
Keep your compose files in a Git repository. If the server dies, you can rebuild the same setup on new hardware from the repository plus your data backups.
Networking: Making Services Accessible
Give the Server a Fixed Address
Inside your home network you reach a service at http://server-ip:port. That address must not
change. The simplest way is a DHCP reservation for the server's MAC address in your router. Alternatively,
configure a static IP on the server itself. On Ubuntu Server that's done with netplan:
# The YAML file the installer created in /etc/netplan/ (its name varies; "ls /etc/netplan/" shows it)
# Replace enp1s0 with your interface name from "ip link".
network:
version: 2
ethernets:
enp1s0:
dhcp4: false
addresses: [192.168.1.100/24]
routes:
- to: default
via: 192.168.1.1
nameservers:
addresses: [1.1.1.1, 8.8.8.8]
Then sudo netplan try (it rolls back automatically if you lose the connection) or
sudo netplan apply. If that YAML file's header says it was generated by cloud-init, edits to it
won't survive a reboot until you disable cloud-init's network configuration as the header explains (see the
netplan tutorial).
Pick an address outside the router's DHCP range. Debian without netplan
uses /etc/network/interfaces instead, which is one more reason the router reservation is the
easier option.
Flat Network or VLANs
Most setups start flat: one subnet for everything.
Internet → ISP router → Home server
↳ All other devices
That's fine while you run a few services and nothing is reachable from the internet. As the setup grows, segmenting with VLANs keeps device groups apart:
Internet → Router/firewall → LAN VLAN (your computers and phones)
↳ Server VLAN (self-hosted services)
↳ IoT VLAN (smart plugs, cameras, TVs)
↳ Guest VLAN (visitors)
With firewall rules between VLANs, a compromised smart device can't reach your servers or computers, and guests only get internet access. VLANs need two things: a router or firewall that supports them (for example OPNsense, see OPNsense Firewall Setup), and a managed switch, because unmanaged switches can't tag or separate VLAN traffic. Inexpensive managed switches are enough for a home lab. If subnets and gateways are new to you, start with Networking Fundamentals.
Remote Access
Sooner or later you'll want to reach a service while away from home. The options, from safest to most exposed:
WireGuard VPN
A WireGuard server on your network (on the home server, a Pi or your firewall) puts your phone or laptop on your home LAN through an encrypted tunnel. You forward a single UDP port, and WireGuard doesn't respond to packets without a valid key, so scanners don't see a service there. The configuration is a few lines per device. WireGuard-Easy Deployment adds a web interface for managing clients; WireGuard Cloud Deployment covers a plain WireGuard server.
Tailscale
Tailscale builds a WireGuard-based mesh between your devices and handles keys and NAT traversal for you, so you don't forward any ports. Its coordination service is hosted by Tailscale, while traffic goes directly between your devices where possible. There's a free Personal plan for non-commercial use; check the pricing page for current limits. Setup on a Linux server:
# Install Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
# Join your tailnet (prints a login URL)
sudo tailscale up
With Tailscale on your phone and laptop, you reach the server by its Tailscale address from anywhere. See Tailscale Mesh VPN for exit nodes, subnet routers and access controls.
Tunnel Services
A tunnel service runs a small agent on your server that connects outwards to the provider, which then serves your web app on a public hostname. No ports are opened on your router. It suits web services you want to share publicly. The trade-off is that the provider sits in the path of the traffic, and it's less suited to SSH or non-HTTP protocols.
Port Forwarding (Proceed With Caution)
Forwarding a port exposes that service directly to the internet, where automated scanners probe continuously. Only do this for a service you keep patched, behind a reverse proxy with HTTPS and strong authentication, and only when one of the options above doesn't fit. A public setup needs a domain name, DNS records pointing to your home IP (or a dynamic DNS service), a reverse proxy handling HTTPS, and the port forward itself. Get comfortable with local and VPN access first.
Reverse Proxy: One Domain, Many Services
A reverse proxy lets you run many services on one server and reach each one by name instead of by port:
- nextcloud.example.com → Nextcloud
- jellyfin.example.com → Jellyfin
- git.example.com → Gitea
Nginx Proxy Manager is a good fit when you want a web interface for proxy hosts and automatic Let's Encrypt certificates. Traefik and Caddy are popular alternatives configured through files or container labels. If you prefer plain Nginx, the Nginx Web Server Setup guide covers reverse proxy blocks and TLS.
# compose.yaml for Nginx Proxy Manager
services:
npm:
image: jc21/nginx-proxy-manager:2.16.0
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "127.0.0.1:81:81" # Admin interface: loopback only
environment:
TZ: "Europe/London" # your time zone
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
This snippet was written against Nginx Proxy Manager 2.16.0, the current release on the
project's releases page
at the time of writing. To update, read the release notes (some releases include database migrations or
Certbot changes), back up the data and letsencrypt folders, change the tag and run
docker compose pull && docker compose up -d. The project's
setup documentation
notes that 32-bit ARM (armv7) images are no longer published from 2.14 onwards.
After starting it, open http://your-server:81. On first start the application generates its
keys, initialises the database and creates an admin user, which can take a couple of minutes. Sign in as
the setup documentation describes for your version and immediately set your own admin email and a strong
password. Then add proxy hosts and request certificates through the UI. Keep port 81 reachable only
from your LAN or VPN. For a remote server, open ssh -N -L 8181:127.0.0.1:81 user@server and
browse http://localhost:8181, and keep port 81 closed at the provider firewall.
Backups: The Part Everyone Skips
Hardware can be replaced and software reinstalled, but data that wasn't backed up is gone. Set backups up before you store anything important, not after the first scare.
The 3-2-1 Rule
- 3 copies of anything you'd be upset to lose (the original plus two backups)
- 2 different storage media or devices, so one failure can't take out both
- 1 copy off-site: physically away from your home, or with a cloud storage provider
For a home setup this might look like:
Working data → server SSD (or a RAID/ZFS pool)
↓ automated nightly backup
Local backup → external drive or NAS
↓ automated backup, encrypted
Off-site copy → cloud object storage or a second location
RAID and ZFS mirrors protect against a failed drive, not against deletion, corruption or ransomware, so they don't count as a backup on their own.
What to Back Up
- Your compose files and
.envfiles (the compose files can also live in Git; keep the.envsecrets out of it) - The volumes and bind-mounted directories where containers store data
- Any custom configuration files on the host
Databases need care: copying a database's files while it's writing can produce an unusable backup. Either
stop the stack briefly during the backup, or dump the database first (for example with
pg_dump for PostgreSQL) and back up the dump.
Example with restic
The backup example below assumes each application has already produced a consistent database dump and required file export in /srv/backup-staging. Schedule that application-specific export first, require it to succeed, and monitor both jobs. A copy of live database volume files is not a substitute.
restic makes
encrypted, deduplicated backups to local disks or cloud storage. Every repository has a password; for
unattended runs the documentation lists a password file (--password-file or
RESTIC_PASSWORD_FILE) among the options.
# Create the repository (you'll be asked for a password; store it in your password manager)
sudo restic init --repo /mnt/backup
# Save the same password in a root-only file for scheduled runs
sudo sh -c 'umask 077; nano /root/.restic-password'
# Back up the staged dumps and exports, and your compose directory
sudo restic backup /srv/backup-staging /opt/stacks --repo /mnt/backup --password-file /root/.restic-password
# Keep 7 daily and 4 weekly snapshots, delete the rest
sudo restic forget --repo /mnt/backup --password-file /root/.restic-password --keep-daily 7 --keep-weekly 4 --prune
# Root crontab entry (sudo crontab -e): every night at 03:00
0 3 * * * restic backup /srv/backup-staging /opt/stacks --repo /mnt/backup --password-file /root/.restic-password --quiet
Replace /opt/stacks with wherever you keep your compose files. If you lose the repository
password, the backups can't be decrypted, so keep a copy somewhere other than the server. Once in a while,
restore a few files to a temporary directory with restic restore and check they open. A backup
you've never restored is a guess.
Security Basics
SSH
# /etc/ssh/sshd_config changes:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
Use SSH keys only, with no password logins and no root login. Copy your key to the server and confirm you can log in with it before disabling passwords. The SSH hardening guide walks through this safely.
Keep Things Updated
# Automatic security updates (Debian and Ubuntu)
sudo apt install unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades
Container images don't update themselves. Check your images regularly, read the release notes for
anything with security fixes, and pull new versions. Pin specific image tags (as in the Nginx Proxy Manager
example) so updates happen when you choose, not whenever latest moves.
Principle of Least Privilege
Don't expose more than you need. If something only needs local access, keep it local. Use a VPN for remote access instead of opening ports. Any service with a login page that's reachable from the internet will see automated login attempts, so keep those to a minimum.
Monitoring
Check logs occasionally and set up something that tells you when a service stops responding, such as
Uptime Kuma. For resource metrics and dashboards, see
Prometheus Grafana Monitoring. SMART monitoring of your
drives (the smartmontools package) gives early warning before a disk fails.
Document Your Setup
You will forget how you set things up. A few months later a config file won't explain why you chose those settings. Write down:
- IP addresses, hostnames and what runs where
- Every credential, stored in a password manager rather than a text file
- The steps you took to install and configure each service, especially workarounds
- What broke, when, and how you fixed it
Plain Markdown files in the same Git repository as your compose files work well. If you host a wiki for this on the server itself, keep an exported copy elsewhere: the notes are most useful exactly when the server is down.
Long-Term Maintenance
Self-hosting isn't set-and-forget. Budget some time every month.
Regular Tasks
- Weekly: check that backups ran, review any alerts
- Monthly: update container images, check disk space and SMART status, skim the logs
- Quarterly: test a backup restore, review what's running and remove what you no longer use
When Things Break
A methodical order of checks:
docker compose psanddocker compose logs --tail 100 <service>: is the container running, and what is it saying?- Check what changed recently: an image update, a config edit, a full disk (
df -h). docker compose down && docker compose up -drecreates the containers; it often clears up transient problems, but note the error first so you can tell if it comes back.- Search the project's GitHub issues and documentation for the exact error message.
- Ask in the project's forum or chat, including versions, your compose file (without secrets) and the logs.
Write down each problem and its fix in your documentation. The same issue tends to come back.
Common Mistakes
Buying Hardware Before Knowing What to Run
Decide on the services first, then buy the minimum hardware that supports them. It's easy to add more later and hard to justify an oversized, power-hungry machine that sits idle.
Underestimating Noise and Power
Enterprise servers are designed for data centres. In living space their fans can be hard to tolerate, and their idle power draw is a cost every month. Listen to a machine and measure its draw before committing to it.
Putting Off Backups
Backups that only exist as a plan don't help when a drive fails. Automate them from day one, keep a copy off-site and test restores.
Complexity for Its Own Sake
Running Kubernetes at home to learn it is a fine goal. Running it because you think a dozen containers need it isn't. A few compose files handle most home setups with far less to maintain.
Exposing Services Too Early
Forwarding ports before you have updates, authentication and backups in place puts your data at risk. Start with local and VPN access.
Where to Go Next
Pick one service and get it running end to end, including its backup, before adding the next. DNS filtering is a good first project because it's useful immediately and easy to undo. A sensible order from here:
- Docker Installation Guide: Docker Engine and Compose from the official repository
- Pi-hole Deployment Guide or AdGuard Home Deployment: DNS filtering
- Tailscale Mesh VPN or WireGuard-Easy Deployment: remote access
- Nextcloud Self-Hosted Cloud: files, calendar and contacts
Some things are better left as a service: email in particular, where deliverability depends on IP reputation and correct DNS records, and a mistake means your messages land in spam.




