LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

Nextcloud: Your Self-Hosted Google Workspace Replacement

nextcloudself-hostingprivacyhomelabcloud-storagecollaborationdockergoogle-workspace
Contents

Every time you open Google Drive, Google’s crawlers have already read what’s in it. Every calendar event feeds a behavioral profile. Every document you collaborate on in Google Docs is processed, indexed, and used to serve ads and train models. You agreed to this in the Terms of Service — you just probably didn’t think about it much.

For individual users the price is your data. For teams the price is also money: Google Workspace Business Starter is $6/user/month, Business Standard is $12/user/month, and it scales from there. A ten-person team on Business Standard is $1,440/year for a suite you don’t control, running on hardware you’ve never seen, in a jurisdiction that may not be your own.

Nextcloud is the answer to both problems. It’s a mature, open-source, self-hosted collaboration platform that replaces Drive, Calendar, Contacts, Google Photos, Docs, Sheets, Slides, and Meet — running on your hardware, under your control, with zero licensing fees. This guide walks through the full deployment: Docker Compose stack with Traefik and Collabora, performance tuning, CalDAV/CardDAV sync, office editing, security hardening, backup, and migrating your data out of Google.


Part 1: Why Leave Google Workspace?

The Privacy Argument

Google’s core business is advertising. Everything Google builds serves the purpose of knowing more about you so it can sell more targeted ads. The G Suite / Workspace product suite is not an exception to this.

Under Google’s Terms of Service, Google retains broad rights to process content stored in Drive and Gmail for the purpose of “improving services.” This includes scanning file contents, analyzing communication patterns, and building behavioral profiles. Google Photos runs facial recognition on every uploaded photo. Calendar events feed “smart” features that happen to require full read access to your schedule.

For personal use this may feel abstract. For businesses handling client data, medical records, financial information, or any regulated data category, it’s a concrete compliance risk. HIPAA, GDPR, SOC2, and sector-specific frameworks all have something to say about storing sensitive data with a third party whose primary incentive is to monetize it.

The Cost Argument

At the individual level, Google Workspace pricing seems reasonable. At team scale it compounds fast:

Plan Per User/Month 10 Users/Year 50 Users/Year
Business Starter $6 $720 $3,600
Business Standard $12 $1,440 $7,200
Business Plus $18 $2,160 $10,800

A self-hosted Nextcloud stack on a $50/month VPS or on hardware you already own eliminates these recurring fees entirely. The break-even against a dedicated server is often under 12 months.

Data Sovereignty

When your data lives on Google’s infrastructure, it lives in Google’s jurisdiction, under Google’s legal obligations. Law enforcement requests, subpoenas, and data access demands go to Google — not to you. Google may comply with requests you’re never informed about.

Self-hosting puts your data under your physical and legal control. You choose the jurisdiction. You decide who has access. You receive and respond to any legal requests yourself.

The Vendor Lock-In Problem

Google kills products. Not occasionally — systematically. Google Reader, Google+, Inbox, Stadia, Google Play Music, Google Hangouts, Google Podcasts, and dozens more. Every time Google shutters a product, users scramble to export their data and find alternatives, often losing years of accumulated content in the process.

Nextcloud is open source. The code isn’t going anywhere. The formats it uses — CalDAV, CardDAV, WebDAV, open document formats — are open standards. Even if Nextcloud GmbH ceased to exist tomorrow, every existing instance would continue running, and the community would carry the project forward.

The Replacement Map

Google Service Nextcloud Equivalent
Google Drive Nextcloud Files
Google Photos Nextcloud Photos + Face Recognition app
Google Calendar Nextcloud Calendar (CalDAV)
Google Contacts Nextcloud Contacts (CardDAV)
Google Docs/Sheets/Slides Collabora Online or OnlyOffice
Google Meet Nextcloud Talk
Google Tasks Nextcloud Tasks (CalDAV VTODO)
Google Notes (Keep) Nextcloud Notes

What Nextcloud does not replace directly: Gmail (use it alongside an external mail server like Stalwart or Postfix/Dovecot, or with a provider like Fastmail/Proton), and Google Maps (OpenStreetMap-based apps like OsmAnd fill this on mobile).


Part 2: Nextcloud Architecture and Editions

Nextcloud Hub

The current release is Nextcloud Hub, which bundles the core platform with four integrated pillars:

  • Files — file sync, sharing, versioning, external storage
  • Talk — chat, video calls, and screen sharing
  • Groupware — Calendar, Contacts, Mail client (IMAP front-end)
  • Office — Collabora/OnlyOffice integration for document editing

The Hub branding signals that Nextcloud is no longer just a file sync tool — it’s a full collaboration platform.

Nextcloud vs ownCloud

Nextcloud and ownCloud share a common history. Frank Karlitschek founded ownCloud in 2010. In 2016, tensions over the project’s direction led him to fork it as Nextcloud. The fork took most of the active developer community with it.

Today, Nextcloud is unambiguously the more active project: more frequent releases, a vastly larger app ecosystem, more community involvement, and more transparent development. ownCloud still exists as a commercial-focused fork, but for self-hosting purposes Nextcloud is the clear choice.

Deployment Options

  • Self-hosted (this guide): Full control, no licensing cost, you handle maintenance
  • Nextcloud.com managed hosting: Nextcloud GmbH offers hosted instances starting around €15/month — useful if you want Nextcloud features without the ops burden
  • Snap/AIO packages: The Nextcloud All-in-One Docker image bundles everything in a single container with a management interface — good for beginners, less flexible for advanced configs

Core Architecture

┌─────────────────────────────────────────┐
│              Traefik (HTTPS)            │
└────────────────┬────────────────────────┘
                 │
     ┌───────────┴──────────┐
     │                      │
┌────▼──────┐      ┌────────▼──────┐
│ Nextcloud │      │   Collabora   │
│  (PHP)    │      │   Online      │
└────┬──────┘      └───────────────┘
     │
     ├──── PostgreSQL (persistent data)
     ├──── Redis (sessions, file locking, cache)
     └──── Data Volume / NAS Mount (files)

Nextcloud is a PHP application. It supports SQLite (dev only), MySQL/MariaDB, and PostgreSQL as database backends. PostgreSQL is strongly recommended for any multi-user deployment — it handles concurrent writes better than MySQL and has superior full-text search support for Nextcloud’s file indexing.

Redis serves two critical roles: distributed session locking (prevents file corruption when multiple clients write simultaneously) and a fast key-value cache that dramatically reduces database load.

The app ecosystem is a key differentiator. The Nextcloud App Store lists 300+ apps: antivirus scanning with ClamAV, full-text search, LDAP/AD integration, mail client, password manager (Passman/Passwords), Kanban boards (Deck), wiki, Bookmarks, Forms, Polls, and much more.


Part 3: Full Docker Compose Deployment

This stack includes Nextcloud, PostgreSQL, Redis, Collabora Online, and Traefik with automatic Let’s Encrypt certificates.

Directory Structure

/opt/nextcloud/
├── docker-compose.yml
├── .env
├── traefik/
│   ├── traefik.yml
│   └── acme.json          # chmod 600
└── data/                  # or mount NAS here

Environment File

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# /opt/nextcloud/.env
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=changeme_strong_password

POSTGRES_DB=nextcloud
POSTGRES_USER=nextcloud
POSTGRES_PASSWORD=changeme_db_password

REDIS_HOST=redis
REDIS_HOST_PASSWORD=changeme_redis_password

NEXTCLOUD_TRUSTED_DOMAINS=cloud.example.com
NEXTCLOUD_DATA_DIR=/var/www/html/data

COLLABORA_DOMAIN=cloud\\.example\\.com
COLLABORA_ADMIN_USER=admin
COLLABORA_ADMIN_PASSWORD=changeme_collabora_password

LETSENCRYPT_EMAIL=you@example.com
DOMAIN=cloud.example.com
COLLABORA_FQDN=office.example.com

TZ=America/New_York

Complete docker-compose.yml

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
version: "3.9"

networks:
  proxy:
    external: true
  nextcloud_internal:
    internal: true

volumes:
  nextcloud_html:
  nextcloud_data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /opt/nextcloud/data   # or your NAS mount point
  db_data:
  redis_data:

services:

  # ── Nextcloud ──────────────────────────────────────────────────────────────
  nextcloud:
    image: nextcloud:28-apache
    container_name: nextcloud
    restart: unless-stopped
    depends_on:
      - db
      - redis
    networks:
      - proxy
      - nextcloud_internal
    volumes:
      - nextcloud_html:/var/www/html
      - nextcloud_data:/var/www/html/data
    environment:
      POSTGRES_HOST: db
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      REDIS_HOST: ${REDIS_HOST}
      REDIS_HOST_PASSWORD: ${REDIS_HOST_PASSWORD}
      NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
      NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
      NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_TRUSTED_DOMAINS}
      NEXTCLOUD_DATA_DIR: ${NEXTCLOUD_DATA_DIR}
      TZ: ${TZ}
      PHP_MEMORY_LIMIT: 1G
      PHP_UPLOAD_LIMIT: 10G
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.nextcloud.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.nextcloud.entrypoints=websecure"
      - "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
      - "traefik.http.routers.nextcloud.middlewares=nextcloud-redirectregex,nextcloud-headers"
      # CalDAV/CardDAV redirect
      - "traefik.http.middlewares.nextcloud-redirectregex.redirectregex.regex=https://(.*)/.well-known/(?:card|cal)dav"
      - "traefik.http.middlewares.nextcloud-redirectregex.redirectregex.replacement=https://$${1}/remote.php/dav"
      - "traefik.http.middlewares.nextcloud-redirectregex.redirectregex.permanent=true"
      # Security headers
      - "traefik.http.middlewares.nextcloud-headers.headers.stsSeconds=31536000"
      - "traefik.http.middlewares.nextcloud-headers.headers.stsIncludeSubdomains=true"
      - "traefik.http.middlewares.nextcloud-headers.headers.stsPreload=true"
      - "traefik.http.middlewares.nextcloud-headers.headers.customResponseHeaders.X-Robots-Tag=noindex,nofollow"
      - "traefik.http.services.nextcloud.loadbalancer.server.port=80"

  # ── PostgreSQL ─────────────────────────────────────────────────────────────
  db:
    image: postgres:16-alpine
    container_name: nextcloud_db
    restart: unless-stopped
    networks:
      - nextcloud_internal
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5

  # ── Redis ──────────────────────────────────────────────────────────────────
  redis:
    image: redis:7-alpine
    container_name: nextcloud_redis
    restart: unless-stopped
    networks:
      - nextcloud_internal
    volumes:
      - redis_data:/data
    command: redis-server --requirepass ${REDIS_HOST_PASSWORD} --save 60 1 --loglevel warning
    healthcheck:
      test: ["CMD", "redis-cli", "--pass", "${REDIS_HOST_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  # ── Collabora Online (CODE) ────────────────────────────────────────────────
  collabora:
    image: collabora/code:latest
    container_name: collabora
    restart: unless-stopped
    networks:
      - proxy
    cap_add:
      - MKNOD
    environment:
      aliasgroup1: "https://${COLLABORA_DOMAIN}"
      DONT_GEN_SSL_CERT: "YES"
      extra_params: "--o:ssl.enable=false --o:ssl.termination=true"
      username: ${COLLABORA_ADMIN_USER}
      password: ${COLLABORA_ADMIN_PASSWORD}
      TZ: ${TZ}
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.collabora.rule=Host(`${COLLABORA_FQDN}`)"
      - "traefik.http.routers.collabora.entrypoints=websecure"
      - "traefik.http.routers.collabora.tls.certresolver=letsencrypt"
      - "traefik.http.services.collabora.loadbalancer.server.port=9980"

  # ── Nextcloud Cron ─────────────────────────────────────────────────────────
  nextcloud_cron:
    image: nextcloud:28-apache
    container_name: nextcloud_cron
    restart: unless-stopped
    depends_on:
      - nextcloud
    networks:
      - nextcloud_internal
    volumes:
      - nextcloud_html:/var/www/html
      - nextcloud_data:/var/www/html/data
    entrypoint: /cron.sh
    environment:
      POSTGRES_HOST: db
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      REDIS_HOST: ${REDIS_HOST}
      REDIS_HOST_PASSWORD: ${REDIS_HOST_PASSWORD}

Traefik Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# /opt/nextcloud/traefik/traefik.yml
api:
  dashboard: true

entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

certificatesResolvers:
  letsencrypt:
    acme:
      email: "you@example.com"
      storage: /etc/traefik/acme.json
      tlsChallenge: {}

providers:
  docker:
    exposedByDefault: false
    network: proxy
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Create the external proxy network
docker network create proxy

# Fix acme.json permissions (required)
touch /opt/nextcloud/traefik/acme.json
chmod 600 /opt/nextcloud/traefik/acme.json

# Start the stack
cd /opt/nextcloud
docker compose up -d

# Watch logs during first boot
docker compose logs -f nextcloud

Post-Install occ Commands

The occ command is Nextcloud’s CLI administration tool. Run it as the www-data user inside the container:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Shortcut alias — add to your .bashrc
alias ncc='docker exec -u www-data nextcloud php /var/www/html/occ'

# Verify installation
ncc status

# Add additional trusted domains
ncc config:system:set trusted_domains 1 --value="cloud.example.com"
ncc config:system:set trusted_domains 2 --value="192.168.1.100"

# Set the overwrite CLI URL (critical for background jobs)
ncc config:system:set overwrite.cli.url --value="https://cloud.example.com"

# Switch background jobs to cron (more reliable than Ajax)
ncc background:cron

# Set phone region (affects phone number formatting)
ncc config:system:set default_phone_region --value="US"

Part 4: Initial Configuration

The config.php File

Nextcloud’s main configuration lives at /var/www/html/config/config.php inside the container. You can edit it directly or use occ config:system:set. Here’s a complete production-ready config section:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php
$CONFIG = array (
  'instanceid' => 'abc123xyz456',   // auto-generated, do not change
  'passwordsalt' => '...',           // auto-generated
  'secret' => '...',                 // auto-generated

  // ── URLs and domains ──────────────────────────────────────────────────
  'trusted_domains' => [
    0 => 'localhost',
    1 => 'cloud.example.com',
  ],
  'overwrite.cli.url' => 'https://cloud.example.com',
  'overwriteprotocol' => 'https',

  // ── Database ──────────────────────────────────────────────────────────
  'dbtype' => 'pgsql',
  'dbname' => 'nextcloud',
  'dbhost' => 'db',
  'dbport' => '',
  'dbuser' => 'nextcloud',
  'dbpassword' => 'changeme_db_password',

  // ── Caching ───────────────────────────────────────────────────────────
  'memcache.local' => '\\OC\\Memcache\\APCu',
  'memcache.distributed' => '\\OC\\Memcache\\Redis',
  'memcache.locking' => '\\OC\\Memcache\\Redis',
  'redis' => [
    'host' => 'redis',
    'port' => 6379,
    'password' => 'changeme_redis_password',
  ],

  // ── Mail ─────────────────────────────────────────────────────────────
  'mail_smtpmode' => 'smtp',
  'mail_smtphost' => 'smtp.example.com',
  'mail_smtpport' => 587,
  'mail_smtpsecure' => 'tls',
  'mail_smtpauth' => true,
  'mail_smtpname' => 'nextcloud@example.com',
  'mail_smtppassword' => 'smtp_password',
  'mail_from_address' => 'nextcloud',
  'mail_domain' => 'example.com',

  // ── Localization ─────────────────────────────────────────────────────
  'default_language' => 'en',
  'default_locale' => 'en_US',
  'default_phone_region' => 'US',
  'logtimezone' => 'America/New_York',

  // ── Data directory ───────────────────────────────────────────────────
  'datadirectory' => '/var/www/html/data',

  // ── Logging ──────────────────────────────────────────────────────────
  'loglevel' => 1,      // 0=DEBUG, 1=INFO, 2=WARN, 3=ERROR, 4=FATAL

  // ── Maintenance ──────────────────────────────────────────────────────
  'maintenance' => false,
);

Background Jobs (Cron)

Nextcloud performs background tasks: generating previews, sending notifications, cleaning up trash, syncing federated shares. These need to run reliably. The default “Ajax” method runs jobs only when someone opens the web UI. Cron is far more reliable.

The nextcloud_cron service in the compose file handles this automatically via the built-in /cron.sh script, which runs occ cron:cron every 5 minutes.

Verify it’s working:

1
2
ncc background:cron   # ensure mode is set to cron
ncc cron:status       # show last run time and status

Email Notifications

Configure SMTP so Nextcloud can send share notifications, password resets, and security alerts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
ncc config:system:set mail_smtpmode --value="smtp"
ncc config:system:set mail_smtphost --value="smtp.example.com"
ncc config:system:set mail_smtpport --value=587
ncc config:system:set mail_smtpsecure --value="tls"
ncc config:system:set mail_smtpauth --value=1 --type=boolean
ncc config:system:set mail_smtpname --value="nextcloud@example.com"
ncc config:system:set mail_smtppassword --value="password"
ncc config:system:set mail_from_address --value="nextcloud"
ncc config:system:set mail_domain --value="example.com"

# Test it
ncc mail:test your@email.com

Maintenance Mode

1
2
3
4
5
6
7
8
# Enable maintenance mode (blocks all user access)
ncc maintenance:mode --on

# Disable maintenance mode
ncc maintenance:mode --off

# Check current status
ncc status

Part 5: Performance Tuning

A default Nextcloud installation will feel sluggish with multiple users. These optimizations make a substantial difference.

Redis Caching

Redis serves as both a distributed cache and a file locking backend. File locking is critical in multi-user environments — without it, simultaneous edits or uploads can corrupt files. The config.php settings shown above configure all three cache layers:

  • memcache.local → APCu (in-process PHP memory cache, fastest for single-process reads)
  • memcache.distributed → Redis (shared across PHP workers, used for session data)
  • memcache.locking → Redis (transactional file locks)

Verify Redis is being used:

1
2
ncc config:system:get memcache.locking
# Should output: \OC\Memcache\Redis

APCu Configuration

APCu is an in-memory cache for PHP. Ensure it’s configured with adequate memory in your PHP config:

1
2
3
4
5
; /usr/local/etc/php/conf.d/apcu.ini  (inside the container)
extension=apcu.so
apc.enabled=1
apc.shm_size=128M
apc.ttl=7200

For the Apache-based image, you can add this via a custom Docker build or by exec-ing into the container and adding the file.

PHP-FPM Tuning

For the nextcloud:fpm-alpine image variant, tune the pool configuration:

1
2
3
4
5
6
7
; www.conf
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.max_requests = 500

The right pm.max_children depends on your RAM. A conservative estimate: each PHP-FPM worker uses roughly 50-100MB. On a 4GB server with other services running, 20 children is a reasonable ceiling.

OPcache

OPcache compiles PHP scripts to bytecode and caches them in memory, eliminating repeated parsing overhead:

1
2
3
4
5
6
opcache.enable=1
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=10000
opcache.memory_consumption=128
opcache.save_comments=1
opcache.revalidate_freq=60

Database Optimization

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Add missing database indices (run after upgrades too)
ncc db:add-missing-indices

# Add missing primary keys
ncc db:add-missing-primary-keys

# Convert filecache bigint columns (one-time migration)
ncc db:convert-filecache-bigint

# Check for any remaining conversion needs
ncc db:convert-type pgsql nextcloud localhost nextcloud

Preview Generation

Nextcloud generates thumbnails for images, PDFs, and videos. This is I/O intensive and can slow down the file browser if done on-demand. The Preview Generator app processes previews in bulk as a background job:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Install the Preview Generator app
ncc app:install previewgenerator

# Generate previews for all existing files (run once)
ncc preview:generate-all --path="/"

# Configure preview sizes
ncc config:app:set previewgenerator squareSizes --value="32 256"
ncc config:app:set previewgenerator widthSizes --value="256 384"
ncc config:app:set previewgenerator heightSizes --value="256"

# Add to crontab for ongoing generation (already handled by nextcloud_cron service)
# /var/www/html/occ preview:pre-generate

Enable the preview providers you need in config.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
'enabledPreviewProviders' => [
  'OC\\Preview\\PNG',
  'OC\\Preview\\JPEG',
  'OC\\Preview\\GIF',
  'OC\\Preview\\WEBP',
  'OC\\Preview\\HEIC',
  'OC\\Preview\\BMP',
  'OC\\Preview\\XBitmap',
  'OC\\Preview\\MP3',
  'OC\\Preview\\TXT',
  'OC\\Preview\\MarkDown',
  'OC\\Preview\\OpenDocument',
  'OC\\Preview\\Krita',
  'OC\\Preview\\Movie',    // requires ffmpeg
  'OC\\Preview\\PDF',      // requires Imagick/ghostscript
],

Rescanning External Storage

1
2
3
4
5
6
7
8
# Rescan all files for all users
ncc files:scan --all

# Rescan a specific user
ncc files:scan username

# Rescan and repair inconsistencies
ncc files:scan --all --repair

Part 6: Nextcloud Files

Web Interface

The Files app is Nextcloud’s core. The web UI provides folder navigation, drag-and-drop upload, right-click context menus for sharing and versioning, integrated search, and tag-based filtering. The Activities sidebar shows a per-file audit trail: who accessed, edited, or shared a file and when.

Desktop Sync Clients

Download from nextcloud.com/install:

  • Windows: Installer with system tray icon and selective sync
  • macOS: Native app with Finder integration, virtual files (on-demand sync)
  • Linux: AppImage, Flatpak, or distro packages; also virtual file support

Configuration: enter your server URL (https://cloud.example.com), authenticate, choose folders to sync. The client handles conflict resolution automatically — conflicted copies are named filename (conflicted copy YYYY-MM-DD).ext.

Mobile Apps

  • iOS: Nextcloud app on the App Store — Files integration, auto-upload for Camera Roll
  • Android: Nextcloud app on Google Play or F-Droid — same features plus optional auto-upload

External Storage

Mount external systems as if they were local Nextcloud folders. Enable the External Storage Support app, then configure mounts in Settings → Administration → External Storages:

Backend Auth Method Notes
Local path None Mount a directory on the host
NFS None (host-level) Mount NFS share on the Docker host
SMB/CIFS Username/password Mount Windows shares
S3/S3-compatible Access key MinIO, Wasabi, Backblaze B2
SFTP Username/key Any SFTP server
WebDAV Username/password Another Nextcloud, OwnCloud

Versioning and Trash Bin

Nextcloud stores previous versions of files automatically. The retention policy uses exponential backoff:

  • All versions from the last second
  • One version per second for the first 10 seconds
  • One version per minute for the first 10 minutes
  • One version per hour for the first 24 hours
  • One version per day for the first month
  • One version per week after that

Deleted files go to the trash bin, where they’re retained for 30 days by default. Both can be configured:

1
2
'versions_retention_obligation' => 'auto',  // or 'D, auto' where D is minimum days to keep
'trashbin_retention_obligation' => 'auto',  // or 'D, auto'

Encryption

Server-Side Encryption (SSE): Files are encrypted at rest on the server. The encryption key is derived from the user’s Nextcloud password. This protects against someone stealing the raw disk, but not against a compromised Nextcloud admin account — the server holds the keys.

1
2
3
ncc app:enable encryption
ncc encryption:enable
ncc encryption:encrypt-all

End-to-End Encryption (E2EE): Keys are generated and held on the client. The server never sees plaintext. More secure, but significantly more operationally complex — recovery is difficult if keys are lost, and some features (admin access, server-side search) don’t work with E2EE folders. Enable via the E2E Encryption app and the desktop client.

For most homelab use cases, SSE is the practical choice. The bigger security gain comes from strong access controls, 2FA, and not exposing the server to the internet without proper hardening.

Sharing

  • Internal shares: Share files/folders with other Nextcloud users or groups. Set read-only, can edit, or can share permissions.
  • Public links: Generate a shareable URL with optional password protection and expiry date. Enable downloads only, or allow uploads (public dropbox-style folder).
  • Federated sharing: Share files with users on other Nextcloud instances using the format user@other-cloud.example.com. Enables a decentralized, cross-organization collaboration network.

Large File Uploads

Nextcloud uses chunked upload for large files. Configure the maximum chunk and total size:

1
ncc config:app:set files max_chunk_size --value=104857600   # 100MB chunks

In Apache’s nextcloud config (/etc/apache2/sites-available/nextcloud.conf):

1
LimitRequestBody 0

The PHP_UPLOAD_LIMIT=10G environment variable in the compose file sets upload_max_filesize and post_max_size for PHP.


Part 7: Groupware — Calendar and Contacts

Nextcloud Calendar (CalDAV)

Install the Calendar app from the Nextcloud App Store (or via ncc app:install calendar). Each user gets a personal calendar by default. Calendars can be shared with specific users, groups, or as public read-only links.

Calendar URL format:

https://cloud.example.com/remote.php/dav/calendars/USERNAME/CALENDAR-NAME/

Syncing Calendars

macOS:

  1. System Settings → Internet Accounts → Add Account → CalDAV (manual)
  2. Account type: Advanced
  3. User name: your Nextcloud username
  4. Password: your Nextcloud password (or app password)
  5. Server address: cloud.example.com
  6. Server path: /remote.php/dav/principals/users/USERNAME/
  7. Port: 443, SSL: checked

iOS:

  1. Settings → Calendar → Accounts → Add Account → Other
  2. Add CalDAV Account
  3. Server: cloud.example.com
  4. User name and password as above

Android — DAVx5 (recommended, available on F-Droid and Play Store):

  1. Add account → URL and user name
  2. Base URL: https://cloud.example.com/remote.php/dav/
  3. Enter credentials
  4. DAVx5 discovers all calendars and address books automatically
  5. Select which to sync, set sync interval

Thunderbird: Thunderbird 102+ has built-in CalDAV support. Calendar → New Calendar → On the Network → CalDAV. Enter the calendar URL directly.

Import/Export

1
2
# Export from Google Calendar: settings → export → download .ics zip
# Import to Nextcloud: Calendar app → Import → select .ics file

The Birthday Calendar app automatically creates a calendar that pulls birthdays from your Contacts app — a nice replacement for the feature Google Calendar provides.

Nextcloud Contacts (CardDAV)

Install the Contacts app. Address books work the same as calendars — each user gets a default address book, shareable with other users.

CardDAV URL format:

https://cloud.example.com/remote.php/dav/addressbooks/users/USERNAME/ADDRESSBOOK-NAME/

Syncing Contacts

macOS: System Settings → Internet Accounts → Add Account → CardDAV. Use the same server details as CalDAV above. macOS will auto-discover address books.

iOS: Settings → Contacts → Accounts → Add Account → Other → Add CardDAV Account. Server: cloud.example.com, username and password.

Android — DAVx5: Same setup as Calendar sync — DAVx5 handles both CalDAV and CardDAV from a single account configuration.

Thunderbird: Tools → Add-ons → search “CardBook” — a well-maintained CardDAV extension for Thunderbird.

Migrating from Google Contacts

  1. Google Contacts → Export → select “Google CSV” or “vCard (for iOS Contacts)”
  2. In Nextcloud Contacts app → Import → select the .vcf file

For large contact lists:

1
2
3
4
5
6
# Split a large vCard file into individual contacts
csplit -z contacts.vcf '/^BEGIN:VCARD/' '{*}'

# Or use vdirsyncer to sync directly between Google and Nextcloud
pip install vdirsyncer
# Configure pairs in ~/.config/vdirsyncer/config

Nextcloud Tasks

The Tasks app syncs via the CalDAV VTODO standard. Install it, and your tasks appear in Thunderbird, iOS Reminders (with CalDAV sync), and Android apps like OpenTasks or Tasks.org. Tasks created in the Calendar view with a date show up alongside events.


Part 8: Nextcloud Office — Document Editing

What is Collabora Online?

Collabora Online is a server-side document editing solution built on LibreOffice. It runs in a Docker container and renders documents in the browser using a web socket connection. Nextcloud integrates with it through the Nextcloud Office app (formerly called Richdocuments).

From the user’s perspective: click a .docx, .xlsx, or .pptx file in Nextcloud and it opens in a rich browser editor without leaving the Nextcloud interface. Multiple users can edit simultaneously and see each other’s cursors in real time.

CODE (Collabora Online Development Edition) is the free, community version. It has some resource and connection limits but is fully functional for homelab and small team use.

Connecting Nextcloud to Collabora

1
2
3
4
5
6
7
8
# Install the Nextcloud Office app
ncc app:install richdocuments

# Configure the Collabora server URL
ncc config:app:set richdocuments wopi_url --value="https://office.example.com"

# Verify connectivity (should return version info)
curl -k https://office.example.com/hosting/discovery

In the Nextcloud web UI: Settings → Administration → Nextcloud Office → enter the Collabora URL.

Collabora Configuration

The aliasgroup1 environment variable in the compose file tells Collabora which domains are allowed to send documents to it. The value is a regex:

1
2
3
4
environment:
  aliasgroup1: "https://cloud\\.example\\.com"
  # For multiple domains:
  # aliasgroup1: "https://cloud\\.example\\.com|https://cloud2\\.example\\.com"

OnlyOffice as an Alternative

OnlyOffice Document Server is an alternative to Collabora with notably better Microsoft Office format compatibility — particularly for complex Word documents with tracked changes, and Excel files with advanced formulas. The trade-offs:

Collabora Online (CODE) OnlyOffice
Base LibreOffice Proprietary engine
MS Office compat Good Excellent
Free tier Full features, some limits 20 connections free
Resource usage Lower Higher (~2GB RAM minimum)
Native format ODF OOXML
Collaborative editing Yes Yes
Best for Pure OSS, privacy-first MS Office-heavy workflows

To use OnlyOffice instead:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# In docker-compose.yml, replace collabora with:
onlyoffice:
  image: onlyoffice/documentserver:latest
  container_name: onlyoffice
  restart: unless-stopped
  networks:
    - proxy
  environment:
    JWT_ENABLED: "true"
    JWT_SECRET: "changeme_jwt_secret"
  labels:
    - "traefik.enable=true"
    - "traefik.docker.network=proxy"
    - "traefik.http.routers.onlyoffice.rule=Host(`office.example.com`)"
    - "traefik.http.routers.onlyoffice.entrypoints=websecure"
    - "traefik.http.routers.onlyoffice.tls.certresolver=letsencrypt"
    - "traefik.http.services.onlyoffice.loadbalancer.server.port=80"

Then install the ONLYOFFICE app in Nextcloud instead of Richdocuments.

For homelab use where the team is primarily using Nextcloud’s native ODF formats or needs a fully open-source stack, Collabora is the better fit. If your team lives in .docx files from Microsoft 365 collaborators, OnlyOffice’s compatibility edge is worth the extra RAM.


Part 9: Nextcloud Talk

What it Replaces

Nextcloud Talk is a self-hosted chat and video conferencing platform. It replaces Google Meet for video calls, and partially replaces Slack/Teams for team chat — with the significant advantage of being fully integrated into your Nextcloud file sharing and calendar ecosystem.

Features: 1:1 and group text chat, file sharing in chat, 1:1 and group video calls, screen sharing, call recordings (with the Recording Backend), and message reactions.

The TURN Server Problem

WebRTC (the technology behind browser video calls) requires peers to exchange IP addresses to establish direct connections. Behind NAT — which is almost every homelab — this direct exchange often fails. A TURN server acts as a relay when direct peer-to-peer fails.

Without a TURN server, Talk calls will fail for users behind strict NAT. This is the most common Talk misconfiguration.

coturn is the standard open-source TURN server:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Add to docker-compose.yml
coturn:
  image: coturn/coturn:latest
  container_name: coturn
  restart: unless-stopped
  network_mode: host   # TURN requires host networking
  command: >
    -n
    --log-file=stdout
    --min-port=49160
    --max-port=49200
    --fingerprint
    --lt-cred-mech
    --realm=cloud.example.com
    --static-auth-secret=changeme_turn_secret
    --total-quota=100
    --bps-capacity=0
    --stale-nonce
    --no-multicast-peers
  ports:
    - "3478:3478"
    - "3478:3478/udp"
    - "5349:5349"
    - "5349:5349/udp"
    - "49160-49200:49160-49200/udp"

Configure Talk to use the TURN server:

1
2
3
# Via occ
ncc talk:turn:add turn cloud.example.com:3478 udp,tcp changeme_turn_secret
ncc talk:stun:add cloud.example.com:3478

Or in the Nextcloud web UI: Settings → Administration → Talk → STUN servers and TURN servers.

Open firewall ports: TCP/UDP 3478, TCP/UDP 5349, and UDP 49160-49200.

Mobile Apps

Nextcloud Talk apps for iOS and Android handle push notifications for incoming calls and messages — a key practical requirement for a Google Meet replacement. Install from the App Store / Google Play / F-Droid.

High-Performance Backend (optional)

For organizations with more than ~10 simultaneous call participants, the default Talk server struggles with signaling. The Spreed High Performance Backend (HPB) offloads signaling to a dedicated Go service. This is optional for homelab use and adds significant deployment complexity — skip it unless you’re running large recurring meetings.


Part 10: Security Hardening

HTTPS is Non-Negotiable

Never run Nextcloud over plain HTTP. The Traefik configuration above handles this. Additionally, Nextcloud will refuse to work correctly without HTTPS — it sets secure cookies and will warn loudly in the admin panel if HTTPS is not detected.

Two-Factor Authentication

Enable 2FA for admin and all user accounts:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Install TOTP app (Google Authenticator, Authy, Bitwarden, etc.)
ncc app:install twofactor_totp

# Force 2FA for all users
ncc twofactorauth:enforce --on

# Force 2FA only for admin group
ncc twofactorauth:enforce --on --group=admin

# Generate backup codes for a user
ncc twofactorauth:cleanup USERNAME

For hardware security keys (YubiKey, Passkey):

1
ncc app:install twofactor_webauthn

App Passwords for Clients

Require app-specific passwords for sync clients, DAVx5, and the desktop app. This means a compromised client password doesn’t expose your main account and enables you to revoke individual client access. Users create app passwords under Settings → Security → Devices & sessions.

Brute Force Protection

Nextcloud includes built-in brute force protection that throttles login attempts from an IP after repeated failures. This is enabled by default. If you lock yourself out:

1
ncc bruteforce:reset YOUR_IP_ADDRESS

Fail2ban Integration

For additional protection, fail2ban can monitor Nextcloud’s log file and block IPs at the firewall level:

1
2
3
4
5
6
# /etc/fail2ban/filter.d/nextcloud.conf
[Definition]
_groupsre = (?:(?:,?\s*"\w+":(?:"[^"]+"|\w+))*)
failregex = ^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Login failed:
            ^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Trusted domain error.
datepattern = ,?\s*"time"\s*:\s*"%%Y-%%m-%%dT%%H:%%M:%%S
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# /etc/fail2ban/jail.d/nextcloud.conf
[nextcloud]
backend = auto
enabled = true
port = 80,443
protocol = tcp
filter = nextcloud
maxretry = 5
bantime = 3600
findtime = 3600
logpath = /opt/nextcloud/data/nextcloud.log

Security Scan

Run the official security scan at scan.nextcloud.com after setup. It checks your Nextcloud version, known CVEs, HSTS configuration, and a set of common misconfigurations.

Disabling User Enumeration

By default, Nextcloud’s login page reveals whether a username exists through error messages. Disable this:

1
2
3
ncc config:system:set auth.bruteforce.protection.enabled --value=true --type=boolean
# Also set in config.php:
# 'login_options' => ['hide_users' => true],

Limiting Public Access

1
2
3
4
5
6
7
# Disable public link sharing (if not needed)
ncc config:app:set core shareapi_allow_links --value="no"

# Set default share expiry
ncc config:app:set core shareapi_expire_after_n_days --value=7
ncc config:app:set core shareapi_default_expire_date --value="yes"
ncc config:app:set core shareapi_enforce_expire_date --value="yes"

Keeping Nextcloud Updated

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Check for updates
ncc update:check

# Put in maintenance mode
ncc maintenance:mode --on

# Pull new Docker image
docker pull nextcloud:28-apache

# Recreate containers
docker compose up -d

# Run upgrade
ncc upgrade

# Add missing indices post-upgrade
ncc db:add-missing-indices

# Disable maintenance mode
ncc maintenance:mode --off

Part 11: Backup Strategy

What to Back Up

A complete Nextcloud backup requires three components:

  1. Data directory — all user files (/var/www/html/data volume)
  2. Database dump — all metadata, shares, calendar entries, contacts
  3. config.php — your system configuration

The nextcloud_html volume (the application code) does not need to be backed up if you’re using a named Docker image — you can recreate it from the image.

Complete Backup Script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env bash
# /opt/nextcloud/backup.sh

set -euo pipefail

BACKUP_DIR="/mnt/backups/nextcloud/$(date +%Y-%m-%d)"
DATA_DIR="/opt/nextcloud/data"
DB_CONTAINER="nextcloud_db"
NC_CONTAINER="nextcloud"
DB_NAME="nextcloud"
DB_USER="nextcloud"
DB_PASS="changeme_db_password"

mkdir -p "${BACKUP_DIR}"

echo "[$(date)] Starting Nextcloud backup..."

# 1. Enable maintenance mode
echo "[$(date)] Enabling maintenance mode..."
docker exec -u www-data "${NC_CONTAINER}" php /var/www/html/occ maintenance:mode --on

# 2. Dump the database
echo "[$(date)] Dumping PostgreSQL database..."
docker exec "${DB_CONTAINER}" pg_dump \
  -U "${DB_USER}" \
  -d "${DB_NAME}" \
  --no-password \
  -Fc \
  > "${BACKUP_DIR}/nextcloud_db.dump"

# 3. Backup config.php
echo "[$(date)] Backing up config.php..."
docker cp "${NC_CONTAINER}:/var/www/html/config/config.php" \
  "${BACKUP_DIR}/config.php"

# 4. Rsync data directory
echo "[$(date)] Rsyncing data directory..."
rsync -aAX --delete \
  --exclude='./nextcloud.log' \
  "${DATA_DIR}/" \
  "${BACKUP_DIR}/data/"

# 5. Disable maintenance mode
echo "[$(date)] Disabling maintenance mode..."
docker exec -u www-data "${NC_CONTAINER}" php /var/www/html/occ maintenance:mode --off

# 6. Remove backups older than 30 days
find /mnt/backups/nextcloud -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +

echo "[$(date)] Backup complete: ${BACKUP_DIR}"

For remote backup, use Restic for encrypted, deduplicated backup to any S3-compatible backend:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Initialize Restic repository (once)
restic -r s3:s3.amazonaws.com/my-bucket/nextcloud init

# Run backup (add to the script above)
restic -r s3:s3.amazonaws.com/my-bucket/nextcloud \
  backup "${BACKUP_DIR}" \
  --password-file /etc/restic/password \
  --tag nextcloud

# Forget old snapshots per policy
restic -r s3:s3.amazonaws.com/my-bucket/nextcloud \
  forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune

Restore Procedure

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1. Stop Nextcloud (not the DB or Redis)
docker stop nextcloud nextcloud_cron

# 2. Restore database
docker exec -i nextcloud_db psql -U nextcloud nextcloud \
  < /mnt/backups/nextcloud/2026-03-25/nextcloud_db.dump
# Or for custom format:
# docker exec -i nextcloud_db pg_restore -U nextcloud -d nextcloud \
#   < /mnt/backups/nextcloud/2026-03-25/nextcloud_db.dump

# 3. Restore data directory
rsync -aAX /mnt/backups/nextcloud/2026-03-25/data/ /opt/nextcloud/data/

# 4. Restore config.php
docker cp /mnt/backups/nextcloud/2026-03-25/config.php \
  nextcloud:/var/www/html/config/config.php

# 5. Start Nextcloud
docker start nextcloud nextcloud_cron

# 6. Rescan files to rebuild the filecache
docker exec -u www-data nextcloud php /var/www/html/occ files:scan --all

# 7. Disable maintenance mode if still on
docker exec -u www-data nextcloud php /var/www/html/occ maintenance:mode --off

Part 12: Migrating from Google

Step 1: Export Your Google Data

Start with Google Takeout (takeout.google.com):

  • Select: Drive, Photos, Calendar, Contacts
  • Format: choose .zip for files, .ics for Calendar, .vcf for Contacts
  • Request the export — Google will email you when it’s ready (can take hours for large accounts)

Step 2: Import Google Drive Files

For large Drive archives, rclone is far more reliable than a manual upload:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Install rclone
curl https://rclone.org/install.sh | bash

# Configure Google Drive remote
rclone config
# → New remote → name: gdrive → type: drive → follow OAuth flow

# Configure Nextcloud WebDAV remote
rclone config
# → New remote → name: nextcloud → type: webdav
#   URL: https://cloud.example.com/remote.php/dav/files/USERNAME/
#   vendor: nextcloud
#   user: USERNAME
#   pass: APP_PASSWORD  (use app password, not your main password)

# Sync Google Drive to Nextcloud
rclone sync gdrive: nextcloud:GoogleDrive/ \
  --transfers=4 \
  --checkers=8 \
  --tpslimit=10 \
  --progress \
  --log-file=/tmp/rclone_migration.log

# Verify
rclone size gdrive:
rclone size nextcloud:GoogleDrive/

After the rclone sync completes, rescan so Nextcloud indexes the new files:

1
ncc files:scan --path="/USERNAME/files/GoogleDrive" --shallow

Step 3: Import Google Calendar

From your Google Takeout zip, find the .ics files (one per calendar):

  1. Open Nextcloud Calendar
  2. Click the three-dot menu next to the calendar name → Import
  3. Select the .ics file

For the command line (via rclone and the CalDAV API):

1
2
3
4
5
6
# Using curl to import an ICS via CalDAV
curl -u "USERNAME:APP_PASSWORD" \
  -X PUT \
  -H "Content-Type: text/calendar" \
  --data-binary @calendar.ics \
  "https://cloud.example.com/remote.php/dav/calendars/USERNAME/personal/imported.ics"

Step 4: Import Google Contacts

From Takeout or directly from contacts.google.com → Export:

  1. Export as vCard (.vcf)
  2. In Nextcloud Contacts → Import → select the .vcf file

Google Contacts exports a single large .vcf file containing all contacts. Nextcloud handles this in one import.

Step 5: Google Photos Migration

Google Photos export via Takeout includes photos in their original resolution but the metadata (album membership, descriptions, some EXIF) is in sidecar JSON files rather than embedded in the images. The tool gphotos-exiftool or exiftool can merge the metadata back:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Install exiftool
sudo apt install libimage-exiftool-perl

# Merge Google Takeout JSON metadata into images
exiftool -r -d %s -tagsfromfile "%d/%F.json" \
  "-GPSAltitude<GeoDataAltitude" \
  "-GPSLatitude<GeoDataLatitude" \
  "-GPSLongitude<GeoDataLongitude" \
  "-Keywords<Tags" \
  "-Subject<Tags" \
  "-Caption-Abstract<Description" \
  "-ImageDescription<Description" \
  "-DateTimeOriginal<PhotoTakenTimeTimestamp" \
  -ext jpg -overwrite_original -progress \
  /path/to/takeout/photos/

Then upload via rclone or the desktop sync client:

1
rclone copy /path/to/takeout/photos/ nextcloud:Photos/ --progress

Enable the Nextcloud Photos app and the Face Recognition app (requires PHP extension and some CPU time for initial scan) for an experience similar to Google Photos.

Step 6: Update Your Workflow

  • Install the Nextcloud desktop sync client and point it at ~/Nextcloud instead of Google Drive
  • Replace Google Calendar with the Nextcloud Calendar app + DAVx5 (Android) or native sync (iOS/macOS)
  • Replace Google Contacts with the Nextcloud Contacts app via the same sync tools
  • Set .docx/.xlsx file associations to open in the browser via Nextcloud, or use LibreOffice locally
  • Configure mobile auto-upload in the Nextcloud iOS/Android app to replace Google Photos auto-backup

Closing Thoughts

Nextcloud is not a weekend project — a production deployment with Collabora, Talk, and proper backups is a real infrastructure commitment. You’re taking on the maintenance that Google would otherwise handle: applying security updates, monitoring disk usage, managing database growth, and testing restores.

But the payoff is substantial. Your data stays on hardware you own, in the jurisdiction you choose, under policies you set. You can audit every configuration. You’re not funding an advertising company with your private communications. And you’re not at the mercy of a product manager deciding to sunset a service you depend on.

The stack in this guide handles a dozen users on a reasonably modest server. With proper Redis caching and PostgreSQL tuning it scales further. The open standards it uses — CalDAV, CardDAV, WebDAV, OpenDocument Format — mean your data is never locked in a proprietary format.

Start with the basics: get Files and the desktop client working. Add Calendar and Contacts via DAVx5. Then add Collabora when you’re ready to drop Google Docs. Each component you migrate reduces your dependence on cloud services you don’t control.

The tools are ready. The question is whether the operational overhead is worth it to you — and for privacy-conscious users who’ve thought seriously about what Google is doing with their data, it almost always is.

Comments