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.
- 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:
- the database (all metadata: users, shares, file index),
- the data directory (the actual files — your
/mnt/ncdata), and 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.