Skip to content

Command Palette

Search for a command to run...

How to Deploy Nextcloud on a VPS

Updated Jul 2026

Run your own Dropbox/Google Drive — files, calendar, and contacts — on a VPS you control, with Docker, HTTPS, and off-box backups.

Before you start
  • A VPS with 2 vCPU / 4 GB RAM (more if you add Collabora or Talk)
  • A fresh Ubuntu 24.04 server with root/sudo SSH access
  • A domain you can point at the server (HTTPS is required for the sync clients)
  • Generous disk — or attached block storage — for user files; this is where your data lives

What Nextcloud is

Nextcloud is a self-hosted content-collaboration platform — a Dropbox or Google Drive you run yourself. Its core is file sync and share: desktop clients for Windows/macOS/Linux and mobile apps keep folders in step across every device, with a web UI for everything else. On top of that sits an app ecosystem — Calendar and Contacts (CalDAV/CardDAV), Notes, Photos, Talk for calls, and optional office editing via Collabora — so it replaces a fair slice of a Google Workspace, not just the drive.

The appeal is the same as the rest of this site: your files live on a box you own, priced by the gigabyte you rent, not by the seat. A managed cloud drive charges per user per month and meters storage. A VPS running Nextcloud costs the same whether one person or ten use it — you pay for the server and the disk.

One honest framing: Nextcloud is heavier than a single-binary app. It's PHP plus a database plus a cache, with a handful of well-known sharp edges — trusted domains, background jobs, upload limits — that trip up almost everyone the first time. This guide uses the official All-in-One (AIO) image to smooth most of that over, and calls out each sharp edge explicitly.

Server sizing — disk is the real question

Nextcloud's compute floor is modest. PHP, a database (PostgreSQL or MariaDB), and Redis together run comfortably on 2 vCPU / 4 GB RAM for a handful of users. Add Collabora (in-browser document editing) or Talk with a TURN server and you'll want 6–8 GB — those are real workloads, not plugins. So treat 4 GB as the floor and size RAM up only when you enable the heavier apps.

The dimension that actually matters is disk. Nextcloud is file storage — every document, photo, and phone backup your users sync lands on the server. The compute numbers barely move as you grow; the storage numbers are the whole game. Plan for what you'll hold in a year, not today, and put the data directory on the large volume, not the small OS disk.

That storage-first shape is why the VPS choice matters more here than for most apps. Kamatera lets you attach block storage on demand and grow it as your library grows, so you never re-provision the whole box to add space — the natural fit for a service whose disk need only goes up. Hetzner is the value pick and pairs cleanly with its separately-priced Volumes for the same reason. Either way the rule holds: user files belong on a volume you can grow, not the root disk.

Prepare the server

Start from a fresh Ubuntu 24.04 server and do the base setup once. If you haven't already installed Docker and set up a firewall on this box, work through Docker & Compose on Ubuntu first — this guide assumes Docker Engine, a non-root deploy user, and ufw are already in place.

With that done, open the ports Nextcloud needs. AIO's admin interface listens on 8080, and your files are served over 80/443:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 8080/tcp
sudo ufw enable

If your VPS host has a separate cloud firewall (Hetzner and others do), allow the same ports there too — a cloud firewall sits in front of ufw.

Now decide where user files live. AIO stores everything under a single Docker volume by default, but you can bind its data directory to a mounted disk. If you attached a block volume, mount it (say at /mnt/ncdata), add it to /etc/fstab so it survives reboots, and hand that path to AIO as the data location (below):

sudo mkdir -p /mnt/ncdata
# ...mount your block volume there, add it to /etc/fstab, then confirm:
df -h /mnt/ncdata

Install Nextcloud (All-in-One)

The recommended path is the official Nextcloud All-in-One. It's a single mastercontainer (nextcloud/all-in-one) that provisions and manages the whole stack for you — Nextcloud itself, its database, Redis, the cron/background-jobs worker, and optionally Collabora and Talk — plus a built-in backup tool. You don't hand-wire any of those; you drive the whole thing from a web admin interface.

Start the mastercontainer. The NEXTCLOUD_DATADIR variable is what puts user files on your big volume:

sudo docker run -d \
  --name nextcloud-aio-mastercontainer \
  --restart unless-stopped \
  -p 8080:8080 \
  -e NEXTCLOUD_DATADIR="/mnt/ncdata" \
  -v nextcloud_aio_mastercontainer:/mnt/docker-aio-config \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  nextcloud/all-in-one:latest

A note on that Docker-socket mount: AIO manages its sibling containers by talking to the Docker daemon, which is why the socket is mounted (read-only here). That's expected and by design.

Then open the admin interface — note it's HTTPS on 8080 with a self-signed certificate, so your browser will warn you; that's fine for this one page:

https://YOUR_SERVER_IP:8080

The first screen shows a one-time passphrase — save it in your password manager, it's how you get back into AIO admin. From here you'll set your domain, pick which optional containers to run (Collabora, Talk, etc.), and click Start containers. AIO pulls and wires up the whole stack; give it a few minutes on the first run.

Reverse proxy: let AIO handle TLS, or front it yourself

AIO can terminate HTTPS two ways, and you pick one up front:

  • AIO manages TLS itself (simplest). Its built-in Apache binds 80/443 and obtains a Let's Encrypt certificate for your domain automatically. Choose this if Nextcloud is the only thing on the box — nothing else needs those ports. This is the default and the least to go wrong.
  • Behind your own reverse proxy. If you already run Caddy, Traefik, or nginx in front of other services on the same server, put AIO behind it instead. In that mode AIO exposes its Apache on an internal port (commonly 11000) and your proxy terminates TLS and forwards to it. See Automatic HTTPS with Caddy for the proxy half — you'll add one reverse-proxy block pointing at localhost:11000, and select the matching option in AIO's setup.

Pick the first unless you already have a proxy running. Mixing both — AIO grabbing 443 and a second proxy wanting it — is the most common self-inflicted failure.

The alternative: hand-rolled Docker Compose

If you want full control over versions, database engine, and tuning — or you're folding Nextcloud into an existing Compose-managed stack — skip AIO and run the pieces yourself. The stack is four services: the nextcloud image (PHP + Apache), a database (PostgreSQL or MariaDB), Redis for file locking and caching, and a cron container for background jobs. A minimal shape:

services:
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes: ["db:/var/lib/postgresql/data"]
    environment:
      POSTGRES_DB: nextcloud
      POSTGRES_USER: nextcloud
      POSTGRES_PASSWORD: change-me

  redis:
    image: redis:alpine
    restart: unless-stopped

  app:
    image: nextcloud:apache
    restart: unless-stopped
    ports: ["80:80"]
    volumes: ["/mnt/ncdata:/var/www/html"]
    environment:
      POSTGRES_HOST: db
      POSTGRES_DB: nextcloud
      POSTGRES_USER: nextcloud
      POSTGRES_PASSWORD: change-me
      REDIS_HOST: redis
      NEXTCLOUD_TRUSTED_DOMAINS: cloud.example.com
    depends_on: [db, redis]

  cron:
    image: nextcloud:apache
    restart: unless-stopped
    volumes: ["/mnt/ncdata:/var/www/html"]
    entrypoint: /cron.sh
    depends_on: [db, redis]

volumes:
  db:

Choose this route only if you specifically need that control; you're taking on the database, cache, cron, backups, and upgrades yourself — exactly the work AIO does for you. Everything below still applies; you just wire it by hand. AIO stays the recommended default.

HTTPS, domain, and trusted domains

HTTPS is not optional here — the desktop and mobile sync clients refuse to connect to a plain-http:// server. Point an A record for your chosen hostname (say cloud.example.com) at the server's public IP before you finish setup, so the certificate can be issued.

If AIO manages TLS, it requests a Let's Encrypt certificate for that domain the moment you enter it and DNS resolves — no manual step. If you're fronting AIO with your own proxy, your proxy handles the certificate and AIO just serves plain HTTP internally.

Then the single most common Nextcloud gotcha: trusted domains. Nextcloud rejects any request whose Host header isn't on its trusted_domains allowlist and shows "You are accessing the server from an untrusted domain." AIO sets this for you from the domain you entered. On the Compose route you set it yourself — via NEXTCLOUD_TRUSTED_DOMAINS (as above) or later with occ:

docker compose exec --user www-data app \
  php occ config:system:set trusted_domains 1 --value=cloud.example.com

First-run setup

Once containers are up and HTTPS is live, browse to your domain (https://cloud.example.com).

Create the admin account first. On AIO the admin credentials are generated and shown to you in the AIO interface — copy them into your password manager before you leave the page. On the Compose route, the very first web visit shows a setup form where you create the admin user; do it immediately, since that page is open until an account exists.

With the admin account in hand:

  • Install a sync client. Grab the desktop client for your OS and the mobile app, sign in with your server URL and credentials, and pick which local folders to sync. This is the whole point — confirm a file dropped on your laptop appears on your phone.
  • Add users. Under Administration settings → Users, create an account per person and optionally set a per-user storage quota so one user can't fill the disk.
  • Connect calendar and contacts. Enable the Calendar and Contacts apps, then point a CalDAV/CardDAV client (your phone's built-in calendar works) at the server to sync appointments and contacts alongside files.

Performance essentials

Four settings separate a Nextcloud that feels solid from one that feels broken. AIO configures all four correctly out of the box — this section matters most on the Compose route, and is worth verifying either way.

Background jobs via system cron. Nextcloud runs maintenance — file scans, notifications, cleanup — as background jobs. The default AJAX mode only runs them when someone loads a page, so on a quiet server they don't run and things silently rot. Switch to Cron (system cron every 5 minutes); AIO and the Compose stack above both run a dedicated cron container for this. Confirm the mode in Administration settings → Basic settings and that the "last ran" timestamp is recent.

Redis for transactional file locking. Without it, concurrent edits and syncs can corrupt or lock files, and Nextcloud nags you in the admin overview. Both stacks include Redis and wire it to file locking and the memory cache — that's what makes simultaneous multi-device sync safe.

PHP memory_limit. The default (often 512 MB) is fine for browsing but tight for large uploads, previews, and some apps. If you hit memory errors, raise it — AIO exposes this as an env var on the mastercontainer; on Compose set PHP_MEMORY_LIMIT=1024M on the app service.

Upload size limits — the classic "upload fails at N MB." Large uploads die not because of Nextcloud but because a limit in front of it cuts the request off. Two knobs: PHP's upload_max_filesize/post_max_size, and — if you front AIO with a reverse proxy — the proxy's body-size cap. On nginx that's client_max_body_size (Caddy has no default limit). Set the proxy cap generously (e.g. client_max_body_size 10G;) or big uploads will fail at whatever the default happens to be, with a confusing 413 rather than a clear message.

Backups

Three things must be backed up together, or a restore won't be consistent:

  1. the database (all metadata: users, shares, file index),
  2. the data directory (the actual files — your /mnt/ncdata), and
  3. config/config.php (the instance configuration and secrets).

AIO has a built-in backup feature using BorgBackup. In the AIO interface you set a location and schedule, and Borg produces deduplicated, encrypted snapshots of the whole stack — database, data, and config in one consistent operation (it briefly enters maintenance mode so the snapshot is coherent).

The one rule that makes a backup real: target off-box storage. A backup on the same server dies with the server. Point Borg at a mounted external location or a remote Borg repository — never just another folder on the same disk. And test a restore at least once (AIO restores through the same interface); an untested backup is only a hope.

Upgrades

Nextcloud releases regularly, and the upgrade discipline matters because you're holding people's files.

On AIO, updates are a button: the interface shows when a new mastercontainer or Nextcloud version is available, and clicking Update pulls the new images, runs the database migrations, and restarts the stack — after (by default) taking a backup first. Let the backup run.

On Compose, bump the image tag, then run the upgrade through occ:

docker compose pull
docker compose up -d
docker compose exec --user www-data app php occ upgrade

Two rules apply to both paths. Read the release notes before a major upgrade — Nextcloud occasionally changes minimum PHP/database versions or app compatibility. And upgrade one major version at a time (28 → 29 → 30, never 28 → 30): Nextcloud's migrations assume sequential major versions and skipping one can leave the instance unbootable. Always make sure a backup ran first.

Troubleshooting

"You are accessing the server from an untrusted domain." The hostname you loaded isn't in trusted_domains. Add it — through AIO's domain setting, or with occ config:system:set trusted_domains N --value=cloud.example.com. This is the single most common first-run error, and it's exactly the trusted-domains check doing its job.

Uploads fail at some size limit. Not a Nextcloud bug — a size cap in front of it. Raise PHP's upload_max_filesize/post_max_size, and if you front AIO with a reverse proxy, raise its body-size limit too (client_max_body_size on nginx). A 413 from the proxy is the tell.

Everything feels slow, or the admin overview shows warnings. Almost always missing Redis or background jobs stuck on AJAX. Confirm cron mode is Cron with a recent last-run timestamp, and that Redis is connected for file locking. Those two fix the large majority of "Nextcloud is sluggish" reports.

An AIO container won't start. Check the mastercontainer's logs (docker logs nextcloud-aio-mastercontainer) — the usual causes are a port conflict (something else already on 80/443, often a second reverse proxy), DNS not yet resolving so the certificate can't issue, or the data volume not mounted. Fix the underlying cause and re-run Start containers from the AIO interface; it's safe to retry.

Verification + next steps

You're done when you can: reach Nextcloud over HTTPS on your own domain with a valid certificate, sign in a desktop and a mobile client and watch a file sync between them, see background jobs running on Cron with Redis connected in the admin overview, and confirm a scheduled backup has landed in off-box storage.

From here, enable the apps you actually want — Calendar, Contacts, Photos, Collabora for document editing — and add your users. The thing that makes or breaks Nextcloud long-term is the disk underneath it, because this is where your files physically live. Hetzner is the value pick and pairs with growable Volumes; reach for Kamatera when you want to attach and expand block storage on demand as your library grows — the natural fit for a service whose storage need only ever goes up. See Best VPS for self-hosting for the ranked picks.

Next steps

We use analytics cookies (Google Analytics, PostHog) to see which guides are useful. No ad networks, no cross-site tracking. See our privacy policy.