Nextcloud Behind Traefik: A Complete Docker Compose Guide (2026)
Step-by-step guide to deploy Nextcloud v34.0.3 behind Traefik with Docker Compose, including hardening, backups, and troubleshooting.
Introduction
Self-hosting Nextcloud is one of the most rewarding projects for a homelab enthusiast. It gives you full control over your files, calendar, contacts, and collaborative editing — all without relying on third-party cloud providers. When paired with Traefik as a reverse proxy, you gain automatic SSL certificates, easy service discovery, and a single entry point for all your self-hosted apps. This guide walks you through deploying Nextcloud v34.0.3 (the latest official release as of August 2026) behind Traefik using Docker Compose. You will learn how to set up the database, configure Traefik labels, enable HTTPS, and avoid common pitfalls that trip up even experienced users. By the end, you'll have a production-ready Nextcloud instance that respects your privacy and runs entirely on your hardware.
Why Traefik? Unlike nginx or Caddy, Traefik integrates natively with Docker's API. It watches for new containers and automatically routes traffic based on labels you define. This makes scaling and adding new services trivial. Combined with Let's Encrypt via the HTTP-01 challenge, Traefik handles SSL certificate issuance and renewal transparently. For a homelab, this means you don't need to manually edit proxy configuration files every time you spin up a new container.
This guide assumes you are comfortable with the command line, have Docker and Docker Compose installed, and own a domain name that points to your server's public IP (or you're using a dynamic DNS service). We'll cover everything from prerequisites to advanced hardening, and we'll also address common errors with solutions so you can troubleshoot confidently.
Important: Always verify the latest Nextcloud version before pinning. The version mentioned here (v34.0.3) was current as of August 2026, but newer releases may exist. Check the official Nextcloud release page before proceeding.
Prerequisites / Requirements
Before you start, ensure your homelab server meets the following minimum requirements. These are typical values based on community reports; your actual usage may vary depending on the number of users and enabled features.
| Component | Minimum | Recommended | Notes |
|---|---|---|---|
| CPU | 2 cores | 4+ cores | Nextcloud performs background tasks like preview generation and cron jobs. More cores speed these up. |
| RAM | 2 GB | 4-8 GB | The database (MariaDB/PostgreSQL) and PHP-FPM consume memory. 4 GB is comfortable for a small household. |
| Storage | 20 GB free | SSD, 1TB+ | The OS, Docker images, and Nextcloud data. Use an SSD for database performance. |
| Software | Docker 24+ | Docker Compose v2 | Install via official Docker docs. Also need curl, wget, and a text editor. |
| Domain | A record | DNS management | A domain or subdomain (e.g., cloud.example.com) pointing to your server's public IP. |
| Network | Stable internet | - | For SSL certificate issuance and updates. |
Optional but recommended: A static IP or dynamic DNS (e.g., DuckDNS) for consistent access.
Step-by-Step Installation
Step 1: Prepare the Environment
First, create a directory for the project and set up a .env file to store secrets. Never commit this file to Git.
mkdir -p ~/nextcloud && cd ~/nextcloud && touch .env && chmod 600 .env
Edit the .env file with your preferred editor and add the following variables. Replace the placeholders with strong passwords and your actual domain.
POSTGRES_PASSWORD=your_strong_db_password_here
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=your_strong_admin_password_here
DOMAIN=cloud.example.com
Security warning: Do not use the same password as your email or any other service. Keep this file secure and never push it to a public repository.
Step 2: Create the Network and External Volumes
Traefik and Nextcloud need to share a Docker network so Traefik can route traffic to the container. Create a dedicated network and also two volumes: one for the database and one for Nextcloud's data.
docker network create traefik-public && docker volume create nextcloud_db_data && docker volume create nextcloud_data
Step 3: Set Up the Traefik Container (If Not Already Running)
If you don't have Traefik running yet, create a docker-compose.traefik.yml file with the following content. This sets up Traefik with the Docker provider and enables the HTTP-01 challenge.
version: "3.8"
services:
traefik:
image: traefik:v3.1
container_name: traefik
restart: unless-stopped
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=your_email@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "traefik-data:/letsencrypt"
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
volumes:
traefik-data:
Replace your_email@example.com with a valid email for Let's Encrypt expiration notices. Then start Traefik:
docker compose -f docker-compose.traefik.yml up -d
Step 4: Create the Nextcloud Docker Compose File
Now create docker-compose.yml for Nextcloud. We'll use PostgreSQL as the database because it's well-supported and performs better than SQLite for multi-user setups. The following compose file includes environment variables from .env.
version: "3.8"
services:
db:
image: postgres:16-alpine
container_name: nextcloud_db
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
volumes:
- nextcloud_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "nextcloud"]
interval: 10s
timeout: 5s
retries: 5
app:
image: nextcloud:34.0.3
container_name: nextcloud_app
restart: unless-stopped
environment:
POSTGRES_HOST: db
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: ${DOMAIN}
depends_on:
db:
condition: service_healthy
volumes:
- nextcloud_data:/var/www/html
labels:
- "traefik.enable=true"
- "traefik.http.routers.nextcloud.rule=Host(`${DOMAIN}`)"
- "traefik.http.routers.nextcloud.entrypoints=websecure"
- "traefik.http.routers.nextcloud.tls.certresolver=letsencrypt"
- "traefik.http.services.nextcloud.loadbalancer.server.port=80"
- "traefik.http.routers.nextcloud.middlewares=nextcloud-secure-headers"
- "traefik.http.middlewares.nextcloud-secure-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.nextcloud-secure-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.nextcloud-secure-headers.headers.stsPreload=true"
- "traefik.http.middlewares.nextcloud-secure-headers.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.nextcloud-secure-headers.headers.frameDeny=true"
volumes:
nextcloud_db_data:
external: true
nextcloud_data:
external: true
Note: The image nextcloud:34.0.3 is pinned. If you prefer to use a variable for easier updates, change it to image: nextcloud:${NEXTCLOUD_VERSION:-latest} and add NEXTCLOUD_VERSION=34.0.3 to your .env. Always check the official release page before pinning a new version.
Step 5: Start the Stack
Now that the compose file is ready, start the containers. The first run will pull the images and initialize the database, which may take a few minutes.
docker compose up -d
After it finishes, check the logs to ensure everything started correctly:
docker compose logs app && docker compose logs db
Step 6: Verify Nextcloud is Accessible
Open your browser and navigate to https://${DOMAIN} (replace with your actual domain). You should see the Nextcloud setup page. Log in with the admin username and password you set in the .env file. If you see a security warning, it may be because your domain isn't recognized yet — we'll fix that in the advanced settings.
Step 7: Configure Nextcloud's Trusted Domains and Overwrite Protocol
Even though we set NEXTCLOUD_TRUSTED_DOMAINS, we need to adjust config.php to handle the reverse proxy correctly. Edit the config file inside the container:
docker exec -it nextcloud_app bash -c "cat > /var/www/html/config/config.php" <<EOF
<?php
\$CONFIG = array (
'trusted_domains' =>
array (
0 => 'localhost',
1 => '${DOMAIN}',
),
'overwriteprotocol' => 'https',
'overwritehost' => '${DOMAIN}',
'overwrite.cli.url' => 'https://${DOMAIN}',
);
EOF
Note: The above command overwrites the entire config.php. If you already have settings, merge them instead. After making changes, restart the app container:
docker restart nextcloud_app
Step 8: Enable Background Jobs (Cron)
Nextcloud needs cron jobs for background tasks like file scanning and notifications. By default, it uses AJAX, which is not reliable. Switch to cron by running the following command inside the container:
docker exec -u www-data nextcloud_app php occ background:cron
Then add a cron job on the host that runs every 5 minutes:
(crontab -l 2>/dev/null; echo "*/5 * * * * docker exec -u www-data nextcloud_app php -f /var/www/html/cron.php") | crontab -
Step 9: Set Up Redis for Caching (Recommended)
Redis improves performance for file locking and caching. Add a Redis service to your compose file and configure Nextcloud to use it.
Edit docker-compose.yml and add the following service:
redis:
image: redis:7-alpine
container_name: nextcloud_redis
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
Add REDIS_PASSWORD=your_strong_redis_password_here to your .env. Then add the environment variable and volume to the app service:
environment:
REDIS_HOST: redis
REDIS_PASSWORD: ${REDIS_PASSWORD}
Restart the stack:
docker compose up -d && docker restart nextcloud_app
Step 10: Configure Nextcloud to Use Redis
Run the following command to set Redis as the caching backend:
docker exec -u www-data nextcloud_app php occ config:system:set memcache.local --value '\OC\Memcache\Redis' && docker exec -u www-data nextcloud_app php occ config:system:set memcache.distributed --value '\OC\Memcache\Redis' && docker exec -u www-data nextcloud_app php occ config:system:set memcache.locking --value '\OC\Memcache\Redis' && docker exec -u www-data nextcloud_app php occ config:system:set redis host --value 'redis' && docker exec -u www-data nextcloud_app php occ config:system:set redis password --value '${REDIS_PASSWORD}'
Step 11: Verify Everything is Working
Run Nextcloud's built-in status check:
docker exec -u www-data nextcloud_app php occ status && docker exec -u www-data nextcloud_app php occ maintenance:mode --off
Visit your domain again and confirm that the admin page shows no warnings. If you see any, address them in the next section.
Step 12: Set Up Automated Backups
Backups are critical. Use occ to export the database and copy the config and data directories. Create a backup script backup.sh:
#!/bin/bash
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p ~/nextcloud/backups/$TIMESTAMP
docker exec nextcloud_db pg_dump -U nextcloud nextcloud > ~/nextcloud/backups/$TIMESTAMP/db.sql
docker cp nextcloud_app:/var/www/html/config ~/nextcloud/backups/$TIMESTAMP/config
docker cp nextcloud_app:/var/www/html/data ~/nextcloud/backups/$TIMESTAMP/data
echo "Backup completed at $TIMESTAMP"
Make it executable and run it:
chmod +x backup.sh && ./backup.sh
Add a cron job to run it daily.
Advanced Configuration / Optimization
Reverse Proxy and SSL
You already have Traefik handling SSL. To enforce HTTPS-only, add a redirect middleware to your Traefik router:
labels:
- "traefik.http.routers.nextcloud.middlewares=nextcloud-secure-headers,nextcloud-redirect"
- "traefik.http.middlewares.nextcloud-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.nextcloud-redirect.redirectscheme.permanent=true"
Backup Strategy
Use the script from Step 12 and also consider off-site backups using rclone. Test your backups regularly by restoring them in a temporary container.
Security Hardening (Optional)
The following are advanced hardening measures. They may break your setup if copied blindly — adapt them to your environment and test thoroughly.
- Run containers with read-only root filesystems: Add
read_only: trueto theappservice. Nextcloud writes to/var/www/html, so you'll need to mount a tmpfs for writable directories like/tmp. - Drop Linux capabilities: Add
cap_drop: [ALL]andcap_add: [NET_BIND_SERVICE]to theappservice. This limits the container's privileges. Test carefully. - Use a dedicated user: In the
appservice, setuser: "${PUID}:${PGID}"where PUID/PGID are your host user IDs. Check your host withid -u && id -g. The default user in the Nextcloud image iswww-data(UID 33). Verify against the image documentation.
Troubleshooting Common Errors
| Error | Cause | Solution |
|---|---|---|
502 Bad Gateway from Traefik |
The Nextcloud container is not ready or the port is wrong | Check docker compose ps and logs. Ensure the loadbalancer port is 80. |
Trusted domain error |
The domain isn't listed in config.php |
Edit config.php and add your domain to trusted_domains. Restart. |
Database connection refused |
The DB container isn't healthy or password mismatch | Verify .env has correct POSTGRES_PASSWORD and that db is healthy with docker compose ps. |
Unable to write to config directory |
Permissions issue in the nextcloud_data volume |
Run docker exec -u root nextcloud_app chown -R www-data:www-data /var/www/html and restart. |
SSL certificate error |
Let's Encrypt validation failed | Check Traefik logs. Ensure port 80 is reachable from the internet and the domain points to your server. |
Redis connection error |
Redis password mismatch or host unreachable | Verify REDIS_PASSWORD in .env and that the redis service is running. |
Cron job not running |
The cron entry is wrong or container name changed | Use docker exec -u www-data nextcloud_app php -f /var/www/html/cron.php manually to test. |
Conclusion & FAQ
You now have a fully functional Nextcloud instance behind Traefik with automatic SSL, a PostgreSQL database, Redis caching, and cron-based background jobs. This setup provides a solid foundation for a privacy-respecting cloud service. Remember to keep your system updated, monitor logs, and perform regular backups.
FAQ
Q1: Can I use SQLite instead of PostgreSQL? Yes, but SQLite is only recommended for testing or single-user setups. PostgreSQL handles concurrent writes much better and is the recommended database for production Nextcloud deployments.
Q2: How do I update Nextcloud to a new version?
Pull the new image, update the version tag in your compose file, and run docker compose up -d. Then execute docker exec -u www-data nextcloud_app php occ upgrade to complete the database migration.
Q3: What if I don't have a domain name?
You can use a dynamic DNS service like DuckDNS or use a self-signed certificate with Traefik's insecureskipverify setting, but that is not recommended for anything beyond local testing.
Q4: How can I access Nextcloud from outside my home network? Ensure your router forwards ports 80 and 443 to your server, and that your domain points to your public IP. Consider using a VPN for additional security.
Q5: Can I add more storage later?
Yes, you can mount additional disks to the nextcloud_data volume. Use the external volume feature and mount the new disk to the same path, then run occ files:scan --all to index the new files.