Running Docker on Windows WSL2/Ubuntu (Part 7) – Perfect Installation of Nextcloud

You can synchronize files across devices, back up your smartphone’s photos automatically, share documents securely, and even collaborate with colleagues on office files. By the end of this guide, you’ll have Nextcloud running on your WSL2 Ubuntu home server

Running Docker on Windows WSL2/Ubuntu (Part 7) – Perfect Installation of Nextcloud

If you’ve been following this series, we’ve already set up a home server stack on Windows WSL2 with Ubuntu. We installed Docker, configured Nginx Proxy Manager (NPM), and deployed media servers like Jellyfin. Now, it’s time to build something even more powerful: your own private cloud storage and collaboration platform with Nextcloud.

Think of Nextcloud as a self-hosted Dropbox, Google Drive, and Google Photos alternative—all in one. You can synchronize files across devices, back up your smartphone’s photos automatically, share documents securely, and even collaborate with colleagues on office files. By the end of this guide, you’ll have Nextcloud running on your WSL2 Ubuntu home server, accessible via a clean domain name like:

👉 https://nextcloud.yourname.dnsduck.org


Why Nextcloud?

  • File Sync & Share: Works across Windows, macOS, Linux, iOS, and Android.
  • Mobile Photo Backup: Automatically upload your camera roll to your server, replacing Google Photos.
  • Collaboration Tools: Whiteboards, chat, calendars, contacts, and more.
  • Privacy: Your files, your server, your rules.
  • Scalability: From a personal NAS setup to enterprise-grade clusters.

Step 1: Prepare Folder Structure

Inside your WSL2 home directory (e.g., ~/docker/nextcloud), create the following folders:

nextcloud/
├── docker-compose.yaml
├── php/custom.ini
├── mariadb/
│   ├── data/
│   └── conf.d/
├── nextcloud/
│   ├── html/
│   ├── config/
│   └── data/
└── redis/

This separation ensures your database, Nextcloud config, and Redis cache are persistent and easy to back up.


Step 2: docker-compose.yaml

Here’s a production-ready Docker Compose file for Nextcloud.

services:
  nextcloud-db:
    image: mariadb:11.4
    container_name: nextcloud-db
    restart: unless-stopped
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW --innodb-file-per-table=1
    environment:
      - MYSQL_ROOT_PASSWORD=your_strong_password
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=your_strong_password
      - TZ=Asia/Seoul
    volumes:
      - ./mariadb/data:/var/lib/mysql
      - ./mariadb/conf.d:/etc/mysql/conf.d
    networks:
      - nextcloud-internal
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 10s
      retries: 20
      start_period: 40s
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: 3
        
  nextcloud-app:
    image: nextcloud:latest
    container_name: nextcloud-app
    restart: unless-stopped
    depends_on:
      nextcloud-db:
        condition: service_healthy
      nextcloud-redis:
        condition: service_healthy
    environment:
      - MYSQL_HOST=nextcloud-db
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=your_strong_password
      - REDIS_HOST=nextcloud-redis
      - REDIS_HOST_PORT=6379
      - NEXTCLOUD_ADMIN_USER=admin
      - NEXTCLOUD_ADMIN_PASSWORD=your_strong_password
      - NEXTCLOUD_TRUSTED_DOMAINS=nextcloud.yourname.dnsduck.org
      - TZ=Asia/Seoul
    volumes:
      - ./nextcloud/html:/var/www/html
      - ./nextcloud/config:/var/www/html/config
      - ./nextcloud/data:/var/www/html/data
      - ./php/custom.ini:/usr/local/etc/php/conf.d/custom.ini
    networks:
      - edge
      - nextcloud-internal
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 40s
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: 3

  nextcloud-redis:
    image: redis:alpine
    container_name: nextcloud-redis
    restart: unless-stopped
    volumes:
      - ./redis:/data
    networks:
      - nextcloud-internal
    healthcheck:
      test: ["CMD", "redis-cli", "-h", "localhost", "ping"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 20s
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: 3

networks:
  edge:
    external: true
  nextcloud-internal:
    driver: bridge

Step 3: PHP Configuration (custom.ini)

Create a file custom.ini in php directory:

memory_limit=1G
upload_max_filesize=10G
post_max_size=10G
max_execution_time=36000
output_buffering=0

This ensures large uploads and long-running tasks (like syncing gigabytes of files) won’t fail.


Step 4: Bring Up the Stack

Run:

docker compose up -d

Check logs:

docker logs -f nextcloud-app

Once the containers are healthy, go to:

👉 https://nextcloud.yourname.dnsduck.org

Login with the admin credentials you set in docker-compose.yaml.


Step 5: Fix Security & Setup Warnings (Detailed)

Nextcloud performs a self-check after installation. These fixes make your instance secure and reverse-proxy-aware when running behind Nginx Proxy Manager (NPM).

1) Configure Trusted Proxies & HTTPS

Goal: Tell Nextcloud it’s sitting behind a reverse proxy (NPM) and that external traffic arrives over HTTPS, even if NPM forwards to Nextcloud over HTTP internally.

1.1 Find your edge network and NPM IP/subnet

# Edge network subnet (CIDR)
docker network inspect edge --format '{{(index .IPAM.Config 0).Subnet}}'
# Example output: 172.18.0.0/16

# NPM container IP (optional, for sanity check)
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' npm-edge
# Example: 172.18.0.3

If you’re using the shared edge network, using the whole subnet is practical:

  • trusted_proxies['172.18.0.0/16'] (or whatever your edge subnet is)

1.2 Apply the reverse-proxy aware settings

Edit file (what you already had):

sudo nano ./nextcloud/config/config.php

Add/update:

'trusted_proxies'     => ['172.18.0.0/16'],   // adjust to your edge subnet
'trusted_domains'     => ['nextcloud.yourname.dnsduck.org'], // if not already set
'overwritehost'       => 'nextcloud.yourname.dnsduck.org',
'overwrite.cli.url'   => 'https://nextcloud.yourname.dnsduck.org',
'overwriteprotocol'   => 'https',
'overwritecondaddr'   => '^172\\.18\\.',      // matches proxy source addresses
'default_phone_region'=> 'KR',

1.3 Ensure NPM forwards the right headers

In NPM → Proxy Hosts → your nextcloud.yourname.dnsduck.org host:

  • SSL: use a valid Let’s Encrypt cert, Force SSL, HTTP/2 enabled.
  • Advanced → Custom Nginx Configuration (add these if not present):
# Forwarded headers for apps behind reverse proxy
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host  $host;
proxy_set_header X-Real-IP         $remote_addr;
proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
These help Nextcloud understand original scheme/host/IP coming through NPM.

1.4 Clear caches & confirm

docker exec -it nextcloud-app bash -lc "php occ maintenance:repair"
docker exec -it nextcloud-app bash -lc "php occ config:system:get overwriteprotocol"

You should now see the HTTP/HTTPS warning disappear in Admin → Overview.


2) Set Maintenance Window (avoid heavy jobs in peak time)

Why: Nextcloud warns if you haven’t set a maintenance window. This is when intensive jobs (e.g., previews, file scans, mimetype updates) are allowed to run.

Set it (e.g., 3 AM):

docker exec -it nextcloud-app bash -lc \
"php occ config:system:set maintenance_window_start --type=integer --value=3"

3) Run MimeType Migration (can be slow)

Why: When new mimetypes are added, Nextcloud asks you to run an “expensive” repair. It’s normal on fresh installs (and on upgrades).

docker exec -it nextcloud-app bash -lc \
"php occ maintenance:repair --include-expensive"

Let it finish. It may take a while if you already imported lots of files.


4) Configure Email Notifications (and test)

In Admin Settings → Basic settings → Email server:

  • Send mode: SMTP
  • Server: mail.yourdomain.com (or provider’s SMTP)
  • Port: 465 (SSL) or 587 (STARTTLS)
  • Authentication: your SMTP username/password
  • From address: notifications@yourdomain.com (or similar)

Click “Send email” to test.


These aren’t always listed in your warnings, but they’re best practice and often remove yellow notices.

A) Enforce HSTS (after HTTPS works)

Why: Nextcloud may warn that Strict-Transport-Security isn’t set. Enable HSTS in NPM (Proxy Host → SSL):

  • Check “HSTS Enable”
  • Set max-age15552000 (180 days)
  • Consider Include subdomains if your subdomains are all HTTPS.
Turn this on only after you’re sure HTTPS works everywhere (HSTS is “sticky” in browsers).

B) Background jobs: switch from AJAX to Cron

Why: AJAX background jobs are unreliable on low-traffic sites. Cron is recommended and will quiet related warnings.

Option 1 (host Task Scheduler / cron)
Create a Windows Task (or Linux cron on the host) that runs:

wsl -d Ubuntu-24.04 -- bash -lc "docker exec -u www-data nextcloud-app php -f /var/www/html/cron.php"

Run every 5 minutes.

Option 2 (simple containerized cron)
You can also run a tiny cron container on the internal network that executes the same command; but keeping it on the host (Task Scheduler) is usually simpler in WSL.

Verify in Admin → Basic settings → Background jobs that Cron is selected.

C) Validate Redis/APCu

You already configured APCu/Redis; to confirm:

  • Admin → Overview → System should show no file locking warnings.
  • Or CLI:
docker exec -it nextcloud-app bash -lc "php occ config:list system | egrep -i 'memcache|redis'"

If you see “Request does not pass strict cookie check” in logs after flipping domains:

  • Ensure you only access via your HTTPS domain (not IP or a different host).
  • Clear browser cookies for your domain, then log in again.

Quick Validation Checklist

  • Admin → Overview has no HTTP / HSTS / proxy warnings.
  • Visiting http://nextcloud.yourname.dnsduck.org redirects to HTTPS.
  • HSTS enabled in NPM (after confirming HTTPS is stable).
  • Background jobs set to Cron; Task Scheduler job runs every 5 minutes.
  • Email test succeeds.
  • occ maintenance:repair --include-expensive completed successfully.
  • trusted_domains, trusted_proxies, and overwrite* values are correct.

Troubleshooting Tips

  • Still seeing the “You are accessing over HTTP” warning?
    Double-check:
    • overwriteprotocol is https
    • overwritehost matches your public host exactly
    • NPM is forwarding X-Forwarded-Proto and X-Forwarded-Host
    • You aren’t visiting via IP or an alternate host
  • Looping redirects / 400 errors
    Remove any stale HSTS from the browser (or test in a private window), and verify there’s no conflicting redirect in NPM’s Advanced section.
  • Emails fail with Gmail
    Use an App Password (Google Account → Security → App passwords) and set:
    • mail_smtphost=smtp.gmail.com
    • mail_smtpport=465 + mail_smtpsecure=ssl
    • Username = full Gmail address, Password = App Password

With these changes, your Nextcloud “Security & setup warnings” page should be quiet—and your instance will behave correctly behind NPM with clean HTTPS and proper proxy awareness.


Step 6: Verify Redis Cache

Redis is critical for Nextcloud performance. It reduces database load by handling file locking, distributed caching, and speeding up metadata lookups. If Redis is not configured correctly, you’ll often see “file is locked” errors or sluggish performance under concurrent use.

6.1 Check from the Admin UI

  • Log into Nextcloud as an admin
  • Go to: Admin Settings → Overview → System
  • Scroll down to System Status
  • ✅ If configured correctly, you should see:
    • “Redis” listed under Caching
    • No warnings about “Transactional file locking is disabled”

Run this inside your Nextcloud container:

docker exec -it nextcloud-app bash -lc "php occ config:list system | egrep -i 'memcache|redis'"

Expected output (example):

  "memcache.local": "\\OC\\Memcache\\APCu"
  "memcache.locking": "\\OC\\Memcache\\Redis"
  "memcache.distributed": "\\OC\\Memcache\\Redis"
  "redis": {
    "host": "nextcloud-redis",
    "port": 6379,
    "password": ""
  }

If you see all three memcache.* values pointing to Redis/APCu and your Redis host matches your Docker Compose service name → you’re good.

6.3 Test Redis Responsiveness

docker exec -it nextcloud-redis redis-cli ping

If Redis is healthy, you’ll get:

PONG

6.4 Why Redis Matters

  • ⚡ Faster performance under high load
  • 🔒 Prevents “file locking” errors during concurrent edits
  • 🗂 Essential for apps like Collabora, OnlyOffice, or large file shares

If Redis isn’t working, Nextcloud falls back to database-based locking, which is far slower and less reliable.


Step 7: Daily Usage & Mobile Apps

Nextcloud isn’t just a web UI. To fully benefit, you’ll want desktop and mobile integration.

7.1 Desktop Client

  • Available for Windows, macOS, and Linux
  • Download from: https://nextcloud.com/install
  • After installation:
    • Add a new account
    • Enter your server URL:
      👉 https://nextcloud.yourname.dnsduck.org
    • Log in with your admin (or personal) user credentials

Features:

  • 🔄 Continuous file sync (like Dropbox or OneDrive)
  • 📁 Selective Sync: choose which folders sync locally
  • ⏱ Conflict handling when editing files across multiple devices

7.2 Mobile Apps

Setup:

  1. Open the app → enter server URL:
    👉 https://nextcloud.yourname.dnsduck.org
  2. Log in with your Nextcloud credentials

Key Features:

  • 📸 Automatic Photo & Video Uploads
    • Enable in Settings → Auto Upload
    • Choose “Camera folder” or any folder you like
    • Options:
      • Upload on Wi-Fi only
      • Upload while charging only
      • Upload videos (optional, since they’re larger)
  • 📂 File browsing & offline access
  • 🔔 Push notifications (e.g., shares, calendar invites)
  • ✏️ Basic document preview/edit support

7.3 Extra Apps for Mobile Integration

  • Nextcloud Notes → sync notes across devices
  • Nextcloud Talk → secure chat & video calls (requires server app enabled)
  • Nextcloud Deck → Kanban board productivity app
  • DAVx⁵ (Android only) → sync Nextcloud Calendar & Contacts with native phone apps

7.4 Best Practices for Daily Use

  • Always connect via HTTPS to avoid security warnings
  • Keep your desktop/mobile clients updated for the latest bug fixes
  • Use separate user accounts for family/team members instead of sharing admin credentials
  • Regularly back up your ./nextcloud/data and database volumes (cron job or rsync recommended)

👉 With Redis caching verified and desktop/mobile clients configured, you now have a personal cloud platform that rivals Dropbox, Google Drive, and iCloud — but fully under your control.


Step 8: Backup Strategy

A reliable backup strategy is the most important part of running a self-hosted Nextcloud server. Disks fail, containers break, and sometimes a single mistyped command can wipe everything. If you don’t have backups, you don’t have a cloud — you have a ticking time bomb.

8.1 What to Back Up

At a minimum, you must back up these three volumes:

  • ./nextcloud/config
    • Contains all system configuration files, trusted domains, overwrite rules, email settings, Redis setup, and installed apps list.
    • Without this, you’ll lose your server identity and configuration.
  • ./nextcloud/data
    • The heart of Nextcloud: all uploaded files, user data, profile pictures, thumbnails, and previews.
    • If this folder is lost, your cloud storage is gone.
  • ./mariadb/data
    • Stores the entire Nextcloud database: users, shares, permissions, calendar events, contacts, and file index.
    • Even if you have the raw files in ./data, Nextcloud needs the DB to know which file belongs to which user.

👉 Optional but recommended:

  • ./php/custom.ini → keeps your PHP settings (upload limits, memory).
  • ./redis → Redis cache folder (less critical, can be rebuilt if lost).

8.2 Manual Backup (Quick & Easy)

From the host machine:

# Stop containers to ensure data consistency
docker compose down

# Backup config, data, and DB
tar -czf nextcloud-backup-$(date +%F).tar.gz \
  ./nextcloud/config \
  ./nextcloud/data \
  ./mariadb/data

# Start containers again
docker compose up -d

This creates a compressed snapshot of your instance with the date in the filename. Store it on a second disk, NAS, or cloud storage.


8.3 Automated Backup with Cron

You don’t want to rely on memory for backups. Let cron handle it.

Example cron job (crontab -e):

0 3 * * * /usr/bin/docker exec -t nextcloud-db \
  mysqldump -u nextcloud -p'your_strong_password' nextcloud \
  > /backups/nextcloud-db-$(date +\%F).sql

0 4 * * * tar -czf /backups/nextcloud-files-$(date +\%F).tar.gz \
  /path/to/nextcloud/config \
  /path/to/nextcloud/data
  • Database dump every night at 3 AM
  • Files + config backup every night at 4 AM
  • Store in /backups (mounted to an external disk or NAS)

8.4 Backup with a Dedicated Container

You can also use a backup container such as Duplicati, BorgBackup, or Restic.

  • These tools support incremental backups, encryption, and remote storage (Google Drive, S3, Backblaze, etc.).
  • Mount your Nextcloud volumes inside the backup container and let it handle scheduling, deduplication, and retention.

8.5 Recovery Strategy

A backup is only good if you can restore it. Test it!

  1. Spin up a temporary Nextcloud instance on another machine or VM.
  2. Copy your backup into the correct folders.
  3. Start Nextcloud and verify files, users, and settings appear.

Restore the DB dump:

docker exec -i nextcloud-db mysql -u root -p'your_strong_password' nextcloud < backup.sql

8.6 Best Practices

  • 📅 Frequency: Daily incremental + weekly full backups.
  • 🌍 Offsite storage: Don’t keep all backups on the same server. Use a NAS, external drive, or cloud bucket.
  • 🔐 Encryption: Encrypt backups if they leave your LAN.
  • Testing: Schedule quarterly restore tests to ensure your process works.

👉 With a solid backup strategy in place, you can sleep peacefully knowing that your Nextcloud instance — files, users, and data — can be recovered even if disaster strikes.


Conclusion

Congratulations 🎉! You now have a fully functional Nextcloud instance running on Docker under WSL2 Ubuntu. With MariaDB, Redis, custom PHP settings, and reverse proxy via NPM, your server is ready to act as a personal Dropbox, Google Drive, and Google Photos replacement.