01A small box, a real server, and the services you actually need
The pitch is simple: rent a cheap virtual private server, point a few Docker containers at it, and stop handing your files, notes and photos to companies whose business model is not your privacy. A VPS in the five-to-ten-dollar-per-month tier — from providers like Hetzner, DigitalOcean, Vultr or Linode — will give you one or two vCPUs, a gigabyte or two of RAM, and enough SSD storage to run a useful stack. That is plenty. The friction is lower than it used to be, and what follows is a realistic path from a blank Ubuntu or Debian machine to something genuinely useful.
Own your files, notes and photos.
02Stand the server up properly before anything else
Resist the urge to install anything interesting until the boring security work is done. SSH in as root on the freshly provisioned box, create a non-root user, and lock the door behind you.
adduser alice
usermod -aG sudo aliceNow copy your local SSH public key to the new user — ssh-copy-id alice@your-server-ip does this in one step — then edit /etc/ssh/sshd_config and set PermitRootLogin no and PasswordAuthentication no. Restart sshd with systemctl restart ssh, open a second terminal to verify the new user can log in before closing the root session, and you have already eliminated the most common attack vector on public-facing boxes. The five sshd_config changes that follow naturally from here are worth reading in full once the rest of this is running.
Next, install ufw and write three rules:
apt install ufw
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enableThat covers SSH, plain HTTP (needed for the Let's Encrypt challenge) and HTTPS. Everything else is closed. Now install Docker and Docker Compose — the official Docker repository gives you more current packages than most distro defaults, and Compose is the cleanest way to manage a stack of related containers without writing systemd units by hand.
curl -fsSL https://get.docker.com | sh
usermod -aG docker aliceLog out and back in so the group membership takes effect. You now have a properly locked-down base.
03The three services worth running first
Self-hosting folklore is full of elaborate stacks — a dozen containers, a reverse proxy, a monitoring suite. Start smaller. Three services cover almost everything a personal server needs to do: Nextcloud for files, photos and calendar; Vaultwarden for passwords; and Nginx Proxy Manager to route traffic and terminate TLS automatically. Everything else can be added later when you know the machine and trust the workflow.
Create a directory structure and a single docker-compose.yml that declares all three:
/opt/selfhost/
docker-compose.yml
data/
nextcloud/
vaultwarden/
npm/The Compose file below is functional, not decorative — edit the obvious placeholders:
version: "3.8"
services: nextcloud: image: nextcloud:latest restart: unless-stopped volumes: - ./data/nextcloud:/var/www/html environment: - MYSQL_HOST=db - MYSQL_DATABASE=nextcloud - MYSQL_USER=ncuser - MYSQL_PASSWORD=changeme depends_on: - db
db: image: mariadb:10.11 restart: unless-stopped volumes: - ./data/db:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD=rootchangeme - MYSQL_DATABASE=nextcloud - MYSQL_USER=ncuser - MYSQL_PASSWORD=changeme
vaultwarden: image: vaultwarden/server:latest restart: unless-stopped volumes: - ./data/vaultwarden:/data environment: - WEBSOCKET_ENABLED=true
npm: image: jc21/nginx-proxy-manager:latest restart: unless-stopped ports: - "80:80" - "443:443" - "81:81" volumes: - ./data/npm:/data - ./data/npm/letsencrypt:/etc/letsencrypt ```
Run docker compose up -d and all four containers start in the background. The unless-stopped restart policy means they survive reboots without any extra configuration.
Port 81 on Nginx Proxy Manager is the admin UI — access it once at http://your-server-ip:81, set a real admin password immediately (the default credentials are widely published), then close port 81 in ufw once you have it configured. The admin UI should never be permanently exposed to the internet.
04Domains, TLS, and making it actually usable
A VPS without a domain name is miserable to use and impossible to trust with HTTPS. Domain names are cheap — a .net or a less fashionable TLD is often under ten dollars a year, and Cloudflare's free tier gives you DNS management and an optional proxy layer. Point two or three subdomains at your server's IP: cloud.yourdomain.com for Nextcloud, vault.yourdomain.com for Vaultwarden.
Back in Nginx Proxy Manager's web UI, add a proxy host for each subdomain: forward cloud.yourdomain.com to nextcloud:80 (Docker's internal networking resolves container names), enable "Force SSL", and hit the Let's Encrypt tab to request a certificate. NPM handles the ACME challenge automatically; within a minute you have valid TLS on both services. No certbot cron job to manage, no manual renewal.
Visit https://cloud.yourdomain.com and walk through Nextcloud's setup wizard. Point it at the MariaDB credentials you set in the Compose file. Once the admin account is created, install the Nextcloud desktop and mobile clients — Nextcloud publishes these for Linux, macOS, Windows, Android and iOS — and your files sync exactly as they would with Dropbox, except the destination is a directory under your own control.
Vaultwarden is a community-maintained, resource-efficient server that implements the Bitwarden API. Every official Bitwarden client — browser extension, desktop app, mobile — works against it. In your Bitwarden (or Vaultwarden) client settings, change the server URL to https://vault.yourdomain.com. Create an account, migrate your passwords, and you have an end-to-end-encrypted password manager running on hardware you pay for.
05Storage limits and the honest cost calculation
A one-gigabyte-RAM VPS with 20–40 GB of attached SSD is not Dropbox. Nextcloud with a few thousand photos and documents is fine; a full media library is not. Budget accordingly: most providers let you attach block storage volumes in 10 GB increments. Mount an additional volume at /opt/selfhost/data and the containers never need to know the difference.
The honest cost: five to ten dollars a month for the VPS, two to three dollars for extra storage if needed, roughly ten dollars a year for a domain. Call it roughly seventy to one hundred and seventy dollars a year, depending on the plan and how much extra storage you need. Against that, you stop worrying about cloud providers that shut down without warning, you stop paying Bitwarden's premium tier if you were on it, and you own every byte.
06Keeping it alive
A self-hosted server that needs constant hand-holding stops being fun quickly. Three habits keep it boring-reliable.
Unattended upgrades push security patches automatically. On Debian and Ubuntu:
apt install unattended-upgrades
dpkg-reconfigure --priority=low unattended-upgradesAccept the prompt to enable automatic updates. This is not the same as automatically upgrading major versions — it only applies security patches to the currently installed release, which is exactly what you want on a production-ish box.
Container updates are separate. A simple weekly cron job that runs docker compose pull && docker compose up -d from /opt/selfhost keeps images current. If you prefer more control, tools like Watchtower can do this automatically and notify you by email or webhook — though on a small personal stack, the manual pull-and-up approach gives you a moment to notice if something broke before the next browser session.
Backups are not optional. The entire /opt/selfhost/data directory should be snapshotted and shipped somewhere that is not the VPS. restic paired with any S3-compatible bucket (Backblaze B2 is popular and cheap) handles this well:
restic -r s3:s3.us-west-000.backblazeb2.com/my-bucket backup /opt/selfhost/dataAdd that to a nightly cron job, rotate your encryption password somewhere safe, and you have off-site backups for a few cents a month in storage fees. A VPS that catches fire — or gets accidentally rm -rf'd — is then a twenty-minute restore, not a loss.
One small box, a few containers, and a Sunday afternoon. The stack above handles everything most people actually need a cloud service to do — and the data stays where you put it.
The players
Hetzner
Reference
German cloud and dedicated-server provider, popular with European self-hosters
Backblaze B2
Reference
low-cost S3-compatible object storage service
Nginx Proxy Manager (NPM)
Reference
web-UI front end for nginx with built-in Let's Encrypt support
