What to plan before you install
Nextcloud gives you file sync, calendars, contacts and shared documents on your own server. It is a PHP application with a database, and it is heavier than it looks. As a guide, plan for at least 2 GB of memory, more with several users or office integration, and plan storage for all the files you will sync plus room for versions and the trash.
Decide where the data will live and how you will back it up before you start. Photos and documents are the kind of data people regret losing. This guide uses Docker Compose with MariaDB and Redis, and publishes the web interface on localhost for a reverse proxy to expose. You need Docker (see the Docker guide) and a domain.
free -h
df -h /
sudo docker compose versionCreate the project and secrets
Make a project folder and an environment file with random passwords. Keep the file private. The values are read by the Compose file and are needed again when you restore.
mkdir -p ~/nextcloud && cd ~/nextcloud
(umask 077; cat > .env <<EOF
MYSQL_ROOT_PASSWORD=$(openssl rand -hex 16)
MYSQL_PASSWORD=$(openssl rand -hex 16)
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=$(openssl rand -base64 18)
EOF
)
sed 's/=.*/=(hidden)/' .envWrite the Compose file
The file defines the database, a Redis cache, the application and a cron container. The application is published on 127.0.0.1:8080 only. Set your real domain in the trusted domains. The two proxy settings tell Nextcloud that visitors arrive over HTTPS through a reverse proxy. Pin the image to the current major version once it is running, so that updates are your decision.
Trust only the Compose gateway address from which the host reverse proxy reaches the container. Recheck the gateway if this network is recreated.
cat > compose.yaml <<'EOF'
services:
db:
image: mariadb:lts
command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW
restart: unless-stopped
volumes:
- db:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
redis:
image: redis:alpine
restart: unless-stopped
app:
image: nextcloud:apache
restart: unless-stopped
depends_on: [db, redis]
ports:
- "127.0.0.1:8080:80"
volumes:
- nextcloud:/var/www/html
environment:
- MYSQL_HOST=db
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
- REDIS_HOST=redis
- NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER}
- NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD}
- NEXTCLOUD_TRUSTED_DOMAINS=cloud.example.com
- OVERWRITEPROTOCOL=https
- OVERWRITEHOST=cloud.example.com
- TRUSTED_PROXIES=${NEXTCLOUD_PROXY_IP:-127.0.0.1}
cron:
image: nextcloud:apache
restart: unless-stopped
depends_on: [db, redis]
entrypoint: /cron.sh
volumes:
- nextcloud:/var/www/html
volumes:
db:
nextcloud:
EOF
sudo docker compose config --quiet && echo "Compose file OK"
sudo docker compose create
PROXY_IP=$(sudo docker network inspect nextcloud_default --format '{{(index .IPAM.Config 0).Gateway}}')
printf 'NEXTCLOUD_PROXY_IP=%s\n' "$PROXY_IP" >> .env
sudo docker compose up -d
sudo docker compose logs --tail 20 appPublish it with HTTPS
Use the reverse proxy guide, or the Caddy guide, to proxy your domain to 127.0.0.1:8080. Nextcloud needs large uploads and long timeouts, so raise the body size limit in Nginx, and forward the standard calendar and contacts discovery addresses. With Caddy the proxy line and certificate need no extra settings for uploads.
client_max_body_size 10G;
proxy_request_buffering off;
proxy_read_timeout 3600s;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location = /.well-known/carddav { return 301 /remote.php/dav/; }
location = /.well-known/caldav { return 301 /remote.php/dav/; }curl -I https://cloud.example.com/status.php
curl -s https://cloud.example.com/status.phpFirst sign-in and settings checks
Sign in with the administrator account from your environment file, and change that password. Open Administration settings, then Overview, and read the security and setup warnings, which list missing settings such as the maintenance window or phone region. Set the background jobs mode to Cron, since the cron container handles it.
cd ~/nextcloud
sudo docker compose exec -u www-data app php occ status
sudo docker compose exec -u www-data app php occ background:cron
sudo docker compose exec -u www-data app php occ config:system:set maintenance_window_start --type=integer --value=1Plan storage
All files live in the nextcloud volume under the data directory, and the database holds metadata and shares. Your plan's disk must hold the files, versions, the trash and a margin. Set per-user quotas so nobody fills the server. For large collections, use an external storage option, or a bigger plan, rather than pushing a small disk to its limit.
sudo docker system df -v | grep -E "nextcloud|db"
df -h /
cd ~/nextcloud && sudo docker compose exec -u www-data app php occ user:setting admin files quota "20 GB"Back up the database and the files
You need three things: the database, the files and the configuration, all from the same moment. Put Nextcloud in maintenance mode, dump the database, archive the volume, then switch maintenance mode off. Copy the results off the server, and keep the .env file with them, because restoring needs the same passwords.
Stop the application and cron after enabling maintenance mode. Keep .env, Compose and both archives together in protected off-server storage; a files-only or database-only copy is insufficient. Resume the application before turning maintenance mode off, then resume cron.
cd ~/nextcloud
sudo docker compose exec -u www-data app php occ maintenance:mode --on
set -o pipefail
umask 077
sudo docker compose stop cron app
source .env
sudo docker compose exec -T db mariadb-dump -u nextcloud -p"$MYSQL_PASSWORD" --single-transaction nextcloud | gzip > nextcloud-db-$(date +%F).sql.gz
sudo docker run --rm -v nextcloud_nextcloud:/data -v "$PWD":/backup alpine tar czf /backup/nextcloud-files-$(date +%F).tar.gz -C /data .
sudo docker compose start app
# Wait until php occ status responds before the next command.
sudo docker compose exec -u www-data app php occ maintenance:mode --off
sudo docker compose start cron
ls -lh nextcloud-*-$(date +%F).*Update step by step
Nextcloud can update only one major version at a time, so skipping versions breaks the upgrade. Take a backup, read the release notes, then pull the new image for the next major version and start it. The container runs the upgrade on start. Check the admin overview afterwards, and update apps from the apps page.
cd ~/nextcloud
sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs --tail 40 app
sudo docker compose exec -u www-data app php occ statusRestore
Confirm the database name, archive and volume before restoring. Stop app and cron so neither writes while the backup is replayed. Use the same application image version as the backup; restore files and database as a pair. The archive contains the maintenance flag from backup time, so turn it off only after the restored application starts.
The commands replace this application volume, including hidden files. They erase its current contents. Verify the archive first and preserve the current state if you might need to roll back.
cd ~/nextcloud
set -o pipefail
sudo docker compose stop app cron
sudo docker compose up -d db
source .env
gunzip -c nextcloud-db-YYYY-MM-DD.sql.gz | sudo docker compose exec -T db mariadb -u nextcloud -p"$MYSQL_PASSWORD" nextcloud
sudo docker run --rm -v nextcloud_nextcloud:/data -v "$PWD":/backup alpine sh -c 'find /data -mindepth 1 -maxdepth 1 -exec rm -rf {} + && tar xzf /backup/nextcloud-files-YYYY-MM-DD.tar.gz -C /data'
sudo docker compose start app
# Wait until php occ status responds.
sudo docker compose exec -u www-data app php occ maintenance:mode --off
sudo docker compose exec -u www-data app php occ maintenance:data-fingerprint
sudo docker compose start cron
# Sign in and download a known file; compare its checksum with the original.Authenticated WebDAV content: byte-identical
Maintenance mode enabled
Maintenance mode disabled
Updated data-fingerprint to f60b32ce0fa1c75c4c55211b77be5a27
Authenticated WebDAV content: byte-identicalOfficial documentation
These upstream references provide more detail on the commands and workflows in this guide.