2025 Infrastructure Live Demo ↗

Overview

This project is a self-hosted Nextcloud instance deployed at cloud.tyfsadik.org that fully replaces Google Drive for file storage and synchronization. Nextcloud provides desktop sync clients for Linux and Windows, a mobile app for iOS and Android, and a web interface accessible from any browser.

The stack runs on Docker Compose with MariaDB as the relational backend for file metadata, Redis for in-memory caching and file-locking, and Nginx as the SSL-terminating reverse proxy. Files are stored on a dedicated Docker volume mounted from a ZFS dataset on the Proxmox host, giving snapshot-based backups for free. The deployment supports large file uploads up to 10 GB via chunked upload, and Redis caching reduces database queries for frequently accessed metadata, keeping the web UI responsive.

Architecture

graph TD subgraph Clients["Client Layer"] DA["Desktop App\nLinux / Windows"] MA["Mobile App\niOS / Android"] WB["Web Browser"] end subgraph Proxy["Proxy Layer"] NG["Nginx\n:443 SSL / TLS"] end subgraph App["Application Layer"] NC["Nextcloud\nPHP-FPM :9000"] RD["Redis\n:6379 cache + lock"] end subgraph Data["Data Layer"] DB[("MariaDB\n:3306 metadata")] FV[("Files Volume\nZFS dataset")] end DA -->|"WebDAV HTTPS"| NG MA -->|"HTTPS sync"| NG WB -->|"HTTPS :443"| NG NG -->|"fastcgi_pass"| NC NC <-->|"queries"| DB NC <-->|"cache / lock"| RD NC -->|"read/write"| FV style DA fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style MA fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style WB fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style NG fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0 style NC fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style RD fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style DB fill:#181818,stroke:#1e1e1e,color:#888 style FV fill:#181818,stroke:#1e1e1e,color:#888

Tech Stack

  • Nextcloud — open-source file hosting with sync clients and web UI
  • MariaDB — relational database for file metadata, shares, and users
  • Redis — in-memory cache for APCu emulation and file-locking backend
  • Nginx — reverse proxy with WebDAV support and large upload config
  • Docker & Docker Compose — container orchestration
  • Let's Encrypt / Certbot — TLS certificate for cloud.tyfsadik.org
  • ZFS — underlying storage with snapshots for backup of the files volume

Build Process

1

Write docker-compose.yml

The compose file defines four services: Nextcloud (PHP-FPM), MariaDB, Redis, and Nginx. Named volumes are defined for the files data, MariaDB data, and Nginx config. All services are placed on a shared internal network, and only Nginx exposes ports 80 and 443 to the host.

version: "3.8"
services:
  db:
    image: mariadb:10.11
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: ncuser
      MYSQL_PASSWORD: ncpass
    volumes:
      - db_data:/var/lib/mysql

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

  app:
    image: nextcloud:28-fpm-alpine
    restart: unless-stopped
    depends_on: [db, redis]
    environment:
      MYSQL_HOST: db
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: ncuser
      MYSQL_PASSWORD: ncpass
      REDIS_HOST: redis
      NEXTCLOUD_TRUSTED_DOMAINS: cloud.tyfsadik.org
    volumes:
      - nc_data:/var/www/html

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - nc_data:/var/www/html:ro
      - ./certs:/etc/letsencrypt:ro

volumes:
  db_data:
  nc_data:
2

Nginx Config with Large File Upload Support

The Nginx configuration proxies PHP requests to the Nextcloud FPM container and raises the upload body size limit to support large file uploads. WebDAV methods are enabled for the sync clients. Headers required by Nextcloud (X-Frame-Options, X-Content-Type) are added for security compliance.

# nginx.conf (server block excerpt)
server {
    listen 443 ssl;
    server_name cloud.tyfsadik.org;

    ssl_certificate /etc/letsencrypt/live/cloud.tyfsadik.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cloud.tyfsadik.org/privkey.pem;

    # Large file upload support
    client_max_body_size 10G;
    client_body_timeout 300s;
    fastcgi_read_timeout 300s;

    root /var/www/html;
    index index.php;

    location ~ \.php$ {
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    # Nextcloud required headers
    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-Frame-Options SAMEORIGIN always;
}
3

Provision SSL Certificate

Certbot is run in webroot mode so it can validate domain ownership through the running Nginx container without stopping the server. The certificate is stored on the host and bind-mounted into the Nginx container.

apt install certbot -y

# Use webroot mode (Nginx handles /.well-known/acme-challenge)
certbot certonly --webroot \
  -w /var/lib/docker/volumes/nextcloud_nc_data/_data \
  -d cloud.tyfsadik.org \
  --agree-tos --email [email protected]

# Renew hook to reload Nginx
echo '#!/bin/bash
docker exec nextcloud-nginx-1 nginx -s reload' \
  > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
4

Nextcloud Initial Setup

After bringing the stack up, the Nextcloud installer is run via the web UI or occ CLI to create the admin account and confirm the database connection. Trusted domains are set so Nextcloud accepts requests from the public hostname.

docker compose up -d

# Run setup via CLI (recommended for automation)
docker exec --user www-data nextcloud-app-1 php occ maintenance:install \
  --database mysql \
  --database-host db \
  --database-name nextcloud \
  --database-user ncuser \
  --database-pass ncpass \
  --admin-user admin \
  --admin-pass changeme \
  --data-dir /var/www/html/data

# Add trusted domain
docker exec --user www-data nextcloud-app-1 php occ \
  config:system:set trusted_domains 1 --value=cloud.tyfsadik.org
5

Install Sync Clients on Devices

The official Nextcloud desktop client is installed on Linux and Windows machines. On mobile, the Nextcloud app is installed from the App Store / Play Store. Each client is pointed to https://cloud.tyfsadik.org with the admin credentials. Selective folder sync keeps only relevant directories synchronized locally.

# Linux desktop client (Debian/Ubuntu)
sudo apt install nextcloud-desktop

# Or AppImage from official releases:
wget https://github.com/nextcloud-releases/desktop/releases/latest/download/Nextcloud-x.x.x-x86_64.AppImage
chmod +x Nextcloud-*.AppImage && ./Nextcloud-*.AppImage

# Verify sync via CLI
nextcloudcmd -u admin -p pass /local/sync/folder https://cloud.tyfsadik.org
6

Configure Redis for Memory Caching

Redis is configured as both the APCu memory cache and the file-locking backend in Nextcloud's config.php. This eliminates database-level file locks and significantly improves performance when multiple clients sync simultaneously.

# Add to /var/www/html/config/config.php
# (edit inside the app container or on the bind-mounted config volume)

'memcache.local' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => [
    'host' => 'redis',
    'port' => 6379,
    'timeout' => 0.0,
],

# Apply config and run maintenance repair
docker exec --user www-data nextcloud-app-1 php occ maintenance:repair
docker exec --user www-data nextcloud-app-1 php occ db:add-missing-indices

Data Flow

flowchart TD A["Desktop Client\nfile change detected"] -->|"WebDAV HTTPS"| B["Nginx :443\nSSL termination"] B -->|"fastcgi_pass :9000"| C["Nextcloud PHP-FPM"] C -->|"chunked upload\n(10 MB chunks)"| D["Files Volume\n/var/www/html/data"] C -->|"metadata write\n(path, size, etag)"| E[("MariaDB")] C -->|"lock file\nduring write"| F[("Redis")] F -->|"lock released"| C C -->|"propagate change"| G["Other Sync Clients\nmobile / web"] style A fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style B fill:#1a1a2e,stroke:#00ff88,color:#e0e0e0 style C fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0 style D fill:#181818,stroke:#1e1e1e,color:#888 style E fill:#181818,stroke:#1e1e1e,color:#888 style F fill:#181818,stroke:#1e1e1e,color:#888 style G fill:#1a1a2e,stroke:#00d4ff,color:#e0e0e0

Challenges & Solutions

  • Slow sync for large files: Syncing multi-gigabyte files stalled because the default PHP upload limits (upload_max_filesize = 2M) rejected the request. Resolved by setting client_max_body_size 10G in Nginx and configuring chunked uploads in Nextcloud so files are split into 10 MB pieces that each satisfy the PHP limit.
  • Redis session locking causing timeout errors: During multi-client sync bursts, file operations blocked waiting for Redis locks to expire. Fixed by setting memcache.locking to the Redis backend in config.php, which uses Redis atomic operations rather than slow database row locks.
  • Nextcloud security scan warnings: The Nextcloud admin panel security scan flagged missing HTTP headers and a missing .well-known redirect. Resolved by adding Strict-Transport-Security, X-Frame-Options, and X-Content-Type-Options headers in Nginx, and adding rewrite rules for /.well-known/carddav and /.well-known/caldav.
  • MariaDB running out of connections under load: The default MariaDB max_connections=150 was exhausted during large sync operations. Increased to 300 and added connection pooling via the Nextcloud dbdriveroptions config.

What I Learned

  • PHP-FPM and Nginx FastCGI integration for PHP applications
  • WebDAV protocol for file synchronization and its HTTP method extensions
  • Redis as a distributed lock manager and cache layer in web applications
  • MariaDB performance tuning: connection limits, query cache, InnoDB buffer pool
  • Nextcloud occ CLI for administration, upgrades, and maintenance tasks
  • Chunked file upload mechanisms and how clients negotiate upload resumption
Nextcloud Docker MariaDB Nginx Cloud Storage Privacy Self-Hosted