Guide
Your first self-hosted server: the decisions that are hard to undo
Hardware is reversible. The operating system, the directory your data lives in, how you reach the box from outside and how it gets backed up are not.
What should you decide before setting up your first self-hosted server?
Four things, because they are the only ones you cannot cheaply change later: the host operating system, where on disk your service data lives, how you reach the server from outside your network, and how it gets backed up. A working default is Debian stable, Docker Compose with bind mounts under a single root such as /srv, a WireGuard-style overlay plus one reverse proxy, and restic to a paid object store. Which applications you run is reversible in an afternoon and does not belong on this list.
Hardware is the reversible decision. A Compose stack moves to a bigger box in an evening, and Choosing home server hardware covers what to buy. Four other decisions are not reversible in an evening, because within three months your data, your DNS records and your habits are all built on top of them:
- The host operating system. Changing it means reinstalling and re-migrating everything.
- Where service data lives on disk. Changing it means stopping every service and moving state while it is quiet.
- How you reach the server from outside. Changing it means re-enrolling every device and every family member.
- How it gets backed up. Changing it means either abandoning your snapshot history or running two systems until the old retention window expires.
Everything else, including which applications you run, is a docker compose down and a directory copy. Spend your first evening on the four, not on the app list.
The recommended path, stated plainly#
Debian stable on bare metal. Docker Engine from Docker's own apt repository, with Compose. One directory tree under a single root, /srv, holding both the stack definitions and the bind-mounted data. Caddy as the only thing listening on 80 and 443. restic to a cheap S3-compatible bucket, on a timer, with a restore you have actually tested.
That stack costs you about 145 MB of idle RAM before you run anything useful: Docker Engine is roughly 120 MB for dockerd plus containerd on an idle host, and Caddy idles around 25 MB. It is the cheapest complete foundation on this site, and it is boring in every direction, which is the point.
Decision one: Debian stable, and what that rules out#
Pick the distribution that changes least, because the operating system is not the interesting part of your server and every hour spent on it is an hour not spent on the thing you actually wanted. Debian stable ships a security-only update channel that unattended-upgrades enables by default, which means you can turn on automatic OS patching and reasonably expect it not to change behavior. That is a different question from updating containers, which you should not automate; see An update strategy that does not lose data.
What this rules out, deliberately:
- A hypervisor first. Proxmox VE scores 4 out of 5 on operational load here and idles around 2 GB before a guest boots. Its fresh install points
aptatenterprise.proxmox.comand fails to update until you enable the no-subscription channel, and the 8 to 9 upgrade requires every node to be on the latest 8.4 andpve8to9 --fullclean first. Every one of those is fine once you know why you want virtualization. On day one you do not. - A NAS appliance you cannot ssh into. Turnkey NAS platforms are pleasant until the day you need to read a log, pin a package, or run a command the web UI does not expose. If the vendor's shell is a support-voiding secret, your recovery options during an outage are whatever the UI offers. The tradeoff is worth pricing out before you buy.
- A desktop edition on a headless box. You will spend the first evening removing a display manager, a network configuration layer and a sleep timer.
Decision two: one directory tree, and you own the path#
This is the decision people regret most, and it costs nothing to get right. Put every stack definition and every bit of persistent state under one root, one directory per service, on storage you have chosen on purpose:
/srv/
├── stacks/ # tracked in git, no secrets
│ ├── caddy/
│ │ ├── compose.yaml
│ │ └── Caddyfile
│ └── linkding/
│ ├── compose.yaml
│ └── .env # gitignored
└── data/ # every bind mount target lives here
├── caddy/ # certificates, ACME account, local CA
└── linkding/ # db.sqlite3 and assetsBind mounts, not named volumes. A named volume is real storage in /var/lib/docker/volumes/<name>/_data, but you cannot see it in a file manager, you cannot easily point a backup tool at a subset of it, and a docker volume prune on a host where a stack is down deletes it without ceremony. With bind mounts, "back up my server" is restic backup /srv, and a restore is a directory that already contains the files the container expects. Docker Compose conventions has the full layout, ownership and PUID/PGID rules.
Two adjacent traps worth fixing on day one. /var/lib/docker grows quietly through dangling images and build cache, and docker system df is the only honest accounting of it. And Docker's default json-file log driver has no size limit at all, so one chatty container can fill your root filesystem. Write /etc/docker/daemon.json before you have gigabytes of JSON:
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}Decision three: how you reach it, and what you do not expose#
The default that ages well is: nothing from your application layer is reachable from the internet. You get in over a WireGuard-based overlay network, and the only public listener, if you have one at all, is a reverse proxy terminating TLS.
Start with the overlay. A mesh VPN gives your phone and laptop an address on the server's network without a single port forward, without your home IP being in anyone's scan list, and without depending on your ISP giving you a routable address. Remote access without port forwarding compares the options, and Headscale plus the official clients is the self-hosted end of that spectrum.
Put Caddy in front of everything anyway, including internal-only services. It obtains and renews certificates itself, redirects HTTP to HTTPS, and for names that never touch the public internet it mints its own local certificate authority so https://linkding.internal stops throwing warnings. A minimal file:
# /srv/stacks/caddy/Caddyfile
{
email you@example.com
}
linkding.example.com {
reverse_proxy linkding:9090
}Three things to know before you build on it. The stock Caddy binary and official image contain zero DNS provider modules, so wildcard certificates and the DNS-01 challenge need a custom build with xcaddy, and every upgrade is a rebuild. Caddy's /data must be a persistent volume: without one, every redeploy discards the ACME account and re-requests every certificate, and Let's Encrypt allows only 5 certificates per identical identifier set per 7 days before it starts refusing you. And Caddy's admin API on port 2019 replaces the entire running configuration for anyone who can POST to it, so do not publish that port or put Caddy on network_mode: host without thinking. Reverse proxy and TLS covers the rest, including when label-driven routing is worth the extra moving parts.
What you should not do is forward port 443 straight to an application's own login page. Application login forms are written by application developers, not security teams, they rarely rate limit, and a single unauthenticated pre-auth bug in any one of them is a foothold on your network. If something genuinely must be public, put it behind the proxy and add an auth layer in front, which is what Single sign-on for self-hosters is about.
Decision four: backups, decided before you need them#
The rule is simple and almost nobody follows it: a backup you have not restored is a hypothesis. Across the 105 tools profiled here, 81.9% cannot be safely copied as live files and need a dump or a stop first, which is exactly the class of mistake that produces a backup set that restores into a corrupt database.
restic to an S3-compatible bucket is the default recommendation: one static Go binary, client-side encryption, deduplication, and no server to run. restic vs BorgBackup covers when to pick something else.
#!/usr/bin/env bash
# /usr/local/sbin/backup.sh (root, chmod 700)
set -euo pipefail
export RESTIC_REPOSITORY="s3:s3.us-west-004.backblazeb2.com/yourbucket/srv"
export RESTIC_PASSWORD_FILE=/root/.restic-password
export AWS_ACCESS_KEY_ID="..." # keep in an EnvironmentFile, not here
export AWS_SECRET_ACCESS_KEY="..."
restic backup /srv --tag srv --exclude-caches
restic forget --tag srv --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
restic check --read-data-subset=1/30
curl -fsS -m 10 https://hc-ping.com/YOUR-UUID # dead man's switchFour details in that script that matter. forget without --tag or --host applies the retention policy to every snapshot in the repository, which is how people delete another machine's history by accident. forget alone frees nothing: prune is what reclaims space. check without --read-data verifies the index but never reads pack contents, so it cannot see bit rot; --read-data-subset=1/30 covers the whole repository over a month at a thirtieth of the egress. And the final curl is a dead man's switch: a backup job that stops running is the normal failure, and a monitor that alerts on the absence of a ping is the only thing that catches it.
The restic passphrase is the only key. There is no vendor, no recovery path and no backdoor. If it lives only in the environment file of the machine you are backing up, then losing that machine loses the backups. Print it and put it somewhere physical. Then read Backups that actually restore and actually do a restore.
What I would not do first#
- Kubernetes. Every problem it solves is a problem you get from having more than one machine and more than one operator. On one box it adds a control plane, a networking layer and a storage abstraction between you and a directory of files.
- Self-hosted email. Deliverability is a reputation system you are not in. Google requires valid forward and reverse DNS on the sending IP and a spam rate under 0.3%, residential and small-provider address space starts with a reputation deficit, and outbound port 25 is blocked by default on most consumer ISPs and several cloud providers. Mailcow and Stalwart are both good software and a bad first project.
- A NAS appliance whose shell you do not have.
- Exposing an application's own login to the internet.
- Photos and passwords as your first service. Immich is very good and it idles around 900 MB with a Postgres and a machine learning container behind it, its trash genuinely deletes originals when it empties, and downgrades are explicitly unsupported. Vaultwarden holds the credentials to your whole life in a SQLite file you must not copy while it is running. Both are excellent second-year services. Neither is a good place to learn what a bind mount is.
The first service should be boring and load-bearing#
Pick something with one container, a data surface you can read with ls, and a use often enough that you notice within a day when it breaks. linkding (90 MB, one container, one SQLite file, port 9090) and Navidrome (70 MB, port 4533, reads your existing files and never writes to them) both qualify. So does a static site.
The reason to want a service you will notice breaking is feedback. A first server that hosts nothing you use tells you nothing about whether your proxy, your backup timer and your update habits work. A first server hosting your bookmarks tells you within 24 hours.
Two things to be wary of as a first service. Do not start with Pi-hole or AdGuard Home: DNS for the whole house is load-bearing for people who did not consent to your hobby, and a failed upgrade means nobody can reach anything. And do not mistake Syncthing for a backup: its own FAQ says plainly that it propagates every change including deletions to every device, which is replication, not history.
| Tool | Category | Ops load | Idle RAM |
|---|---|---|---|
| Dozzle | Monitoring | 1, Set and forget | 15 MB |
| LLDAP | Identity | 1, Set and forget | 15 MB |
| File Browser | Photos and files | 1, Set and forget | 20 MB |
| Gotify | Communication | 1, Set and forget | 20 MB |
| Caddy | Networking | 1, Set and forget | 25 MB |
| Diun | Security | 1, Set and forget | 25 MB |
| Gatus | Monitoring | 1, Set and forget | 25 MB |
| Glance | Dashboards | 1, Set and forget | 25 MB |
| ntfy | Communication | 1, Set and forget | 30 MB |
| Pocket ID | Identity | 1, Set and forget | 30 MB |
| Beszel | Monitoring | 1, Set and forget | 40 MB |
| grocy | Productivity | 1, Set and forget | 40 MB |
What to do next#
Set up the four decisions in order: install Debian, create /srv/stacks and /srv/data, deploy Caddy plus one small service, then write the restic timer before you add a second service. When that loop works end to end, use the Stack planner to see what your intended stack will cost in RAM, and read When not to self-host before you migrate anything irreplaceable.
Questions#
Should I start with Proxmox or just install Debian?
Install Debian. Proxmox VE is excellent and it is also a hypervisor you administer: it idles around 2 GB before a single guest runs, its fresh install points at a subscription-only apt repository that fails apt update until you switch it to the no-subscription channel, and the 8 to 9 upgrade has a required order you must follow node by node. None of that is hard, but it is work you are doing instead of running services. If you later want VMs, you can install Proxmox on the same hardware and restore your Compose stacks into a guest.
Ubuntu or Debian for a home server?
Either works and the difference is small. Debian stable changes less between releases and ships fewer defaults you have to undo, which is the property you want on a machine you touch once a month. The practical reason to pick Ubuntu Server instead is a specific driver or a vendor package that only publishes for it. Do not use a desktop edition on a headless box: you get a display manager, a network configuration layer and automatic sleep behavior you will spend an evening removing.
Do I need a domain name to self-host?
You need one as soon as you want TLS certificates that browsers trust, which is roughly immediately. A cheap domain plus DNS at a provider with an API is about 10 to 15 USD a year and unlocks the DNS-01 challenge, which is how you get certificates for services that are not reachable from the internet at all. Without a domain you are choosing between plain HTTP on your LAN and a private certificate authority you install on every device.
Is a Raspberry Pi enough for a first server?
For one or two light services, yes. The failure you will hit is not CPU, it is the SD card: containers writing logs, SQLite databases and metrics wear out flash quickly, and the symptom is a filesystem that silently goes read-only. If you use a Pi, boot from a USB SSD, set Docker log rotation on day one, and keep anything that writes constantly off the card.
Where should Docker bind mounts live?
Under one predictable root, one directory per service, on a filesystem you have deliberately chosen to back up. /srv/data/<service> is a good default because it survives an OS reinstall if it is on its own partition or disk. The alternative, named volumes, puts your data in /var/lib/docker/volumes/<name>/_data where it is real but invisible, and where a stray docker volume prune deletes it.
What is the first service I should actually run?
Something with one container, a file-shaped data surface and a use you have every day: linkding, Navidrome or a static site all qualify. The point of the first service is to make you exercise the whole loop (deploy, proxy, back up, restore) on something whose loss would annoy you rather than hurt you. Photos and passwords are the wrong first service for exactly that reason.
How much does a first self-hosted server cost to run?
Budget the electricity, not the hardware. A modern mini PC idling at 8 to 15 W costs roughly 20 to 40 USD a year at typical residential rates, and used enterprise gear at 60 to 90 W costs several times that. Add a domain, and add object storage for backups: 100 GB of deduplicated restic snapshots at Backblaze B2 or equivalent is a few dollars a month, and it is the line item people skip.
Sources#
Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.