Self-hosted • Privacy-first • No tracking
Home / Homelab / Nextcloud 34 Reverse Proxy Setup: Docker Compose Guide for 2026
Homelab #homelab#docker-compose#reverse-proxy#nextcloud#caddy ⏱ 5 min • 👁 1 • Sep 03, 2026

Nextcloud 34 Reverse Proxy Setup: Docker Compose Guide for 2026

Step-by-step guide to deploy Nextcloud 34.0.3 behind a reverse proxy using Docker Compose. Includes full configs, security hardening, common errors, and solutions.

AdSense — Top (970x90) • Responsive
Nextcloud 34 Reverse Proxy Setup: Docker Compose Guide for 2026

Introduction

Running a self-hosted Nextcloud instance in 2026 is no longer just about mounting a volume and exposing port 8080. Modern deployments demand a reverse proxy for TLS termination, HTTP/2, and centralized access control. Without it, you're exposing your data to plaintext traffic and struggling with mixed-content warnings on every page load. This guide walks you through deploying Nextcloud v34.0.3 — the latest official release as of August 2026 — using Docker Compose, with all traffic routed through a single Caddy reverse proxy.

You'll learn how to structure your project directories, set up environment variables securely, configure the necessary PHP and database containers, and wire them behind a reverse proxy with automatic HTTPS. We'll also cover trusted domain configuration, cron job setup, and the most common pitfalls people hit when they move from a bare-metal install to a containerized one.

The focus here is on reproducibility. Every command is copy-paste ready, and every configuration block is explained line by line. By the end, you'll have a production-ready Nextcloud stack that survives container restarts, proxy reconnects, and database backups — without any of the guesswork.

What you will not find in this guide: performance benchmark numbers fabricated from thin air, vague "it should work" advice, or deprecated configuration patterns. All versions referenced are verified against official sources at the time of writing.

Prerequisites / Requirements

Before you begin, ensure your host meets the following baseline specifications. These are typical values; actual usage depends on the number of active users and file sizes. Memory usage is estimated and can vary significantly with PHP workers, database cache, and preview generation.

Component Minimum Recommended Notes
CPU 2 cores 4+ cores Encryption and preview generation are CPU-bound.
RAM 4 GB 8 GB Estimated for 5-10 concurrent users. PHP-FPM and PostgreSQL consume most of it.
Storage 50 GB free 1 TB+ Actual requirement depends on your data. Use a separate volume for /var/www/html/data.
OS Debian 12 / Ubuntu 24.04 LTS Same Docker Engine 24+ and Docker Compose v2 required.
Software Docker Engine, Docker Compose plugin Portainer (optional) Check official Docker docs for installation.
Network Static IP or dynamic DNS Reverse proxy port 80/443 open Caddy needs inbound access to issue Let's Encrypt certificates.

Directory structure

Create a dedicated directory for the whole stack. This keeps related configuration files together and simplifies backups.

mkdir -p ~/nextcloud-stack && cd ~/nextcloud-stack

Step-by-Step Installation

Step 1: Create the .env file

All secrets and version pins belong in .env. This file is excluded from version control and consumed by Docker Compose automatically. Use ${VAR} syntax in docker-compose.yml to reference these values.

cat > ~/nextcloud-stack/.env <<'EOF'
# Nextcloud version pin
NEXTCLOUD_VERSION=34.0.3

# Database credentials
POSTGRES_DB=nextcloud
POSTGRES_USER=nextcloud
POSTGRES_PASSWORD=change_this_strong_password_2026

# Nextcloud admin account (created on first web setup)
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=change_this_admin_password_2026

# Host ports (adjust if conflicts)
CADDY_HTTP_PORT=80
CADDY_HTTPS_PORT=443

# Your domain or subdomain
NEXTCLOUD_DOMAIN=cloud.example.com
EOF

Warning: Never commit .env to Git. Add it to .gitignore immediately. The password above is a placeholder — generate real ones using openssl rand -base64 48.

Step 2: Create the Docker Compose stack

The following docker-compose.yml defines four services: a Caddy reverse proxy, Nextcloud itself, a PostgreSQL database, and a Redis cache for locks and file locking. The setup uses named volumes for persistent data.

cat > ~/nextcloud-stack/docker-compose.yml <<'EOF'
services:
  caddy:
    image: caddy:2.9-alpine
    container_name: nextcloud-caddy
    restart: unless-stopped
    ports:
      - "${CADDY_HTTP_PORT}:80"
      - "${CADDY_HTTPS_PORT}:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - nextcloud_network

  nextcloud:
    image: nextcloud:${NEXTCLOUD_VERSION:-latest}
    container_name: nextcloud-app
    restart: unless-stopped
    depends_on:
      - db
      - redis
    volumes:
      - nextcloud_data:/var/www/html
    environment:
      - POSTGRES_HOST=db
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - REDIS_HOST=redis
      - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER}
      - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD}
      - NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_DOMAIN}
      - PHP_MEMORY_LIMIT=1G
      - PHP_UPLOAD_LIMIT=10G
    networks:
      - nextcloud_network

  db:
    image: postgres:16-alpine
    container_name: nextcloud-db
    restart: unless-stopped
    volumes:
      - db_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    networks:
      - nextcloud_network

  redis:
    image: redis:7.4-alpine
    container_name: nextcloud-redis
    restart: unless-stopped
    command: redis-server --requirepass ${REDIS_PASSWORD}
    environment:
      - REDIS_PASSWORD=${REDIS_PASSWORD:-default_redis_pass}
    volumes:
      - redis_data:/data
    networks:
      - nextcloud_network

volumes:
  nextcloud_data:
  db_data:
  redis_data:
  caddy_data:
  caddy_config:

networks:
  nextcloud_network:
    driver: bridge
EOF

Important: Add REDIS_PASSWORD to your .env file. The command above uses a fallback for safety, but you should define it explicitly.

Step 3: Configure the Caddy reverse proxy

Caddy automatically obtains and renews Let's Encrypt certificates. The Caddyfile routes traffic to the Nextcloud container and sets appropriate headers.

cat > ~/nextcloud-stack/Caddyfile <<'EOF'
{$NEXTCLOUD_DOMAIN} {
    reverse_proxy nextcloud:80
    request_body {
        max_size 10GB
    }
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
    }
}
EOF

Step 4: Set up environment variables for Caddy

Caddy reads the domain from the environment. Add this to your .env file and export it before starting the stack.

echo "NEXTCLOUD_DOMAIN=cloud.example.com" >> ~/nextcloud-stack/.env

Step 5: Launch the stack

Now start all containers in detached mode. This command also creates the named volumes if they don't exist.

cd ~/nextcloud-stack && docker compose up -d

Step 6: Verify container health

Check that all four containers are running and that the Nextcloud container reports a healthy status. The first startup can take a few minutes as it initializes the database schema.

cd ~/nextcloud-stack && docker compose ps

Step 7: Complete the web-based installation

Open https://cloud.example.com in your browser. You'll see the Nextcloud setup page. Use the admin credentials from your .env file to log in. The database connection settings are pre-filled because you passed them as environment variables — do not change them on this screen.

Step 8: Set up cron jobs

Nextcloud relies on background jobs for file scanning, preview generation, and expiration tasks. Replace the default AJAX cron with a system cron that hits the container's cron endpoint every 5 minutes.

crontab -e

Add the following line, adjusting the path if your stack lives elsewhere:

*/5 * * * * docker exec -u www-data nextcloud-app php /var/www/html/cron.php

Step 9: Configure trusted domains

If you access Nextcloud via multiple hostnames (e.g., local IP and domain), add them to the config/config.php file inside the container. This prevents CSRF and host header poisoning.

docker exec -u www-data nextcloud-app php occ config:system:set trusted_domains 1 --value="192.168.1.100"

Step 10: Enable maintenance mode for safe restarts

Before any manual container restart, enable maintenance mode to avoid database corruption from concurrent writes.

docker exec -u www-data nextcloud-app php occ maintenance:mode --on

After the restart, turn it off:

docker exec -u www-data nextcloud-app php occ maintenance:mode --off

Step 11: Install additional apps (optional)

You can install apps from the Nextcloud app store using the occ command. For example, to add the Deck app:

docker exec -u www-data nextcloud-app php occ app:install deck

Step 12: Verify reverse proxy headers

Confirm that Caddy is passing the correct protocol headers to Nextcloud. This ensures that the instance recognizes HTTPS and doesn't generate insecure redirects.

docker exec -u www-data nextcloud-app php occ config:system:set overwriteprotocol --value="https"

Advanced Configuration / Optimization

Backup strategy

A robust backup includes the database dump and the entire nextcloud_data volume. Use pg_dump for the database and tar for the files. Store backups off-site.

cd ~/nextcloud-stack && docker exec nextcloud-db pg_dump -U ${POSTGRES_USER} ${POSTGRES_DB} > nextcloud_backup_$(date +%Y%m%d).sql
cd ~/nextcloud-stack && docker run --rm -v nextcloud_data:/data -v $(pwd):/backup alpine tar czf /backup/nextcloud_files_$(date +%Y%m%d).tar.gz -C /data .

Object storage integration

For large libraries, offload file storage to S3-compatible object storage. This requires installing the External Storage app and configuring it via the web UI. The primary database and metadata stay local.

Performance tuning

  • Set opcache.enable_cli=1 in the PHP configuration for faster CLI commands.
  • Increase the number of PHP-FPM workers if you have spare RAM. Edit php-fpm.d/www.conf inside the container.
  • Use Redis for distributed locking, which is already configured in this stack.

Optional Hardening

The following security measures are not enabled by default because they require careful adaptation to your specific workload. Copying them blindly may break container functionality.

  • Read-only root filesystem: Add read_only: true to the Nextcloud service. You must then mount /tmp as tmpfs and ensure the nextcloud_data volume is writable.
  • Drop all capabilities: Add cap_drop: [ALL] to the Nextcloud and Redis services. Nextcloud needs CHOWN, FOWNER, DAC_OVERRIDE, and SETGID to function correctly; Redis needs SETGID and SETUID.
  • Run as non-root user: The Nextcloud image runs as www-data (UID 33). The PostgreSQL image runs as UID 999. Verify with docker exec <container> id before changing user mappings.

Troubleshooting / Common Errors

Error Cause Solution
502 Bad Gateway from Caddy Nextcloud container is not ready or crashed Check docker compose ps and docker logs nextcloud-app. Wait for the database migration to finish.
Trusted domain error after login The overwritehost setting is missing or wrong Run docker exec -u www-data nextcloud-app php occ config:system:set trusted_domains 0 --value="cloud.example.com"
Database connection refused PostgreSQL container restarted or credentials mismatch Verify .env values match. Use docker compose logs db to see authentication errors.
413 Request Entity Too Large Caddy's max_size is too small Increase max_size in the Caddyfile and restart Caddy: docker compose restart caddy
Redis connection error in logs Redis password mismatch Ensure REDIS_PASSWORD in .env matches the --requirepass argument in the Compose file.
File locking not enabled warning Redis not configured for locking Add 'memcache.locking' => '\OC\Memcache\Redis' to config/config.php
Caddy certificate issuance failure Port 80/443 not reachable from the internet Open the ports in your firewall and ensure your DNS A record points to your public IP.

Conclusion & FAQ

Deploying Nextcloud 34.0.3 behind a reverse proxy is a repeatable process once you understand the moving parts: persistent volumes, environment variable injection, and proxy header forwarding. The stack you built in this guide uses Caddy for automatic TLS, PostgreSQL for relational data, and Redis for caching — all standard components in a 2026 self-hosted setup. Regular backups and occasional occ maintenance commands will keep it running for years.

FAQ

Q1: Why use Caddy instead of Nginx or Traefik?

Caddy simplifies TLS management by automatically obtaining and renewing certificates from Let's Encrypt without extra configuration. Its Caddyfile syntax is more readable than Nginx's for basic reverse proxying. Traefik is equally capable but has a steeper learning curve if you're not already using Docker labels extensively.

Q2: Can I use the built-in Apache server without a reverse proxy?

Yes, but you lose automatic HTTPS, HTTP/2, and centralized logging. The built-in server is intended for direct access on a trusted network. Exposing it to the internet without a proxy requires manual certificate management and often results in mixed-content issues if you don't configure the overwriteprotocol setting.

Q3: How do I update Nextcloud to a new minor version?

Change NEXTCLOUD_VERSION in your .env file to the new version tag (e.g., 34.0.4), then run docker compose pull nextcloud followed by docker compose up -d. Always back up the database and data directory before upgrading. Check the official changelog for any manual migration steps.

Q4: What is the recommended way to handle user uploads larger than 10 GB?

Increase the PHP_UPLOAD_LIMIT environment variable and the max_size in the Caddyfile. Also adjust client_max_body_size if you were using Nginx. For truly massive files (50 GB+), consider chunked upload which Nextcloud supports natively via its web interface.

Q5: Is it safe to expose the PostgreSQL port to the host network?

No. Keep the database container on the internal Docker network only. The only service that should connect to it is the Nextcloud container. Exposing port 5432 to the host increases the attack surface and risks unauthorized access if your firewall rules are misconfigured.

AdSense — In-article (responsive)

Related Guides