This guide covers deploying Nextcloud using Docker Compose with an nginx reverse proxy for SSL/TLS termination. The configuration separates concerns by having nginx handle external HTTPS traffic while Nextcloud runs in isolated Docker containers.
Replace nextcloud.yourdomain.com with your actual subdomain.
docker --versiondocker compose versionnginx -vcertbot --versionThe deployment consists of four main services:
Following Linux Filesystem Hierarchy Standard (FHS) best practices, the deployment uses the following locations:
/opt/nextcloud/ - Docker Compose configuration and application files/etc/nginx/sites-available/ - nginx server configuration/etc/letsencrypt/ - SSL/TLS certificates managed by certbot/var/backups/nextcloud/ - Database and data backups/etc/systemd/system/ - systemd service files for container managementCreate an A record pointing nextcloud.yourdomain.com to your server's IP address.
If using Cloudflare, ensure SSL/TLS encryption mode is set to "Full (strict)" to properly validate the certificate chain between Cloudflare and your origin server.
Install certbot if not already present.
sudo snap install --classic certbot sudo ln -s /snap/bin/certbot /usr/bin/certbot
Request a certificate for your Nextcloud subdomain.
sudo certbot certonly --standalone -d nextcloud.yourdomain.com
The SSL certificates will be stored in /etc/letsencrypt/live/nextcloud.yourdomain.com/
Set up automatic renewal with a systemd timer.
sudo systemctl enable --now certbot.timer
Update the package index and install required dependencies.
sudo apt-get update sudo apt-get install ca-certificates curl
Add Docker's official GPG key.
sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc
Add the Docker repository to apt sources.
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Install Docker Engine and Docker Compose.
sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Add your user to the docker group to run Docker commands without sudo.
sudo usermod -aG docker $USER
Log out and back in for group changes to take effect.
Create a directory structure for the Nextcloud deployment. Following best practices, Docker Compose projects should reside in /opt/ for production deployments.
sudo mkdir -p /opt/nextcloud sudo chown $USER:$USER /opt/nextcloud cd /opt/nextcloud
Create a database environment file db.env to store PostgreSQL credentials.
POSTGRES_DB=nextcloud POSTGRES_USER=nextcloud POSTGRES_PASSWORD=your_secure_database_password_here
Ensure the file has restricted permissions to prevent unauthorized access.
chmod 600 db.env
Create the Docker Compose configuration file compose.yaml
volumes: db: nextcloud: services: db: image: postgres:17.6-alpine3.22 restart: always volumes: - db:/var/lib/postgresql/data env_file: - db.env redis: image: redis:alpine restart: always app: image: nextcloud:fpm-alpine restart: always volumes: - nextcloud:/var/www/html environment: - POSTGRES_HOST=db - REDIS_HOST=redis - PHP_MEMORY_LIMIT=-1 - PHP_UPLOAD_LIMIT=32G env_file: - db.env depends_on: - db - redis web: build: ./web restart: always ports: - 127.0.0.1:4430:443 volumes: - nextcloud:/var/www/html:ro - /etc/letsencrypt:/etc/letsencrypt:ro - /etc/ssl:/etc/ssl:ro depends_on: - app cron: image: nextcloud:fpm-alpine restart: always volumes: - nextcloud:/var/www/html entrypoint: /cron.sh depends_on: - db - redis
Create a directory for the nginx web service.
mkdir -p web
Create a Dockerfile in the web/ directory.
FROM nginx:alpine COPY nginx.conf /etc/nginx/nginx.conf
Create the nginx configuration file web/nginx.conf
worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; keepalive_timeout 65; upstream php-handler { server app:9000; } server { listen 443 ssl http2; ssl_certificate /etc/letsencrypt/live/nextcloud.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/nextcloud.yourdomain.com/privkey.pem; # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header Referrer-Policy "no-referrer" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; # Allow large file uploads client_max_body_size 32G; client_body_timeout 300s; fastcgi_buffers 64 4K; gzip on; gzip_vary on; gzip_comp_level 4; gzip_min_length 256; gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; gzip_types application/atom+xml text/javascript application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; root /var/www/html; location = /robots.txt { allow all; log_not_found off; access_log off; } location ^~ /.well-known { location = /.well-known/carddav { return 301 /remote.php/dav/; } location = /.well-known/caldav { return 301 /remote.php/dav/; } location ^~ /.well-known { return 301 /index.php$uri; } try_files $uri $uri/ =404; } location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; } location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; } location ~ \.php(?:$|/) { rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode(_arm64)?\/proxy) /index.php$request_uri; fastcgi_split_path_info ^(.+?\.php)(/.*)$; set $path_info $fastcgi_path_info; try_files $fastcgi_script_name =404; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $path_info; fastcgi_param HTTPS on; fastcgi_param modHeadersAvailable true; fastcgi_param front_controller_active true; fastcgi_pass php-handler; fastcgi_intercept_errors on; fastcgi_request_buffering off; fastcgi_max_temp_file_size 0; } location ~ \.(?:css|js|svg|gif|png|jpg|ico|wasm|tflite|map|ogg|flac)$ { try_files $uri /index.php$request_uri; expires 6M; access_log off; } location ~ \.woff2?$ { try_files $uri /index.php$request_uri; expires 7d; access_log off; } location /remote { return 301 /remote.php$request_uri; } location / { try_files $uri $uri/ /index.php$request_uri; } } }
Ensure the certificate paths in web/nginx.conf match your domain.
Build and start all services.
docker compose up -d
Check the status of all containers.
docker compose ps
All containers should show as "Up" and healthy.
View container logs if troubleshooting is needed.
docker compose logs -f app
For production deployments, create a systemd service to manage the Nextcloud containers. This ensures Nextcloud starts automatically on system boot and can be managed with standard systemd commands.
Create the systemd service file /etc/systemd/system/nextcloud.service
[Unit] Description=Nextcloud Docker Compose Service Requires=docker.service After=docker.service network-online.target Wants=network-online.target [Service] Type=oneshot RemainAfterExit=yes WorkingDirectory=/opt/nextcloud ExecStart=/usr/bin/docker compose up -d ExecStop=/usr/bin/docker compose down TimeoutStartSec=0 [Install] WantedBy=multi-user.target
Reload systemd to recognize the new service.
sudo systemctl daemon-reload
Enable the service to start on boot.
sudo systemctl enable nextcloud.service
The Nextcloud service can now be managed with standard systemd commands.
sudo systemctl start nextcloud sudo systemctl stop nextcloud sudo systemctl restart nextcloud sudo systemctl status nextcloud
Container logs are accessible through Docker Compose or systemd journal.
View Nextcloud application logs.
cd /opt/nextcloud docker compose logs -f app
View all container logs.
docker compose logs -f
View systemd service logs.
sudo journalctl -u nextcloud.service -f
The Nextcloud containers are now running and accessible on 127.0.0.1:4430. Configure the system's nginx to proxy external HTTPS requests to the Nextcloud container.
Create an nginx server configuration file /etc/nginx/sites-available/nextcloud.yourdomain.com
# nextcloud.yourdomain.com - Nextcloud File Storage & Collaboration # HTTP to HTTPS redirect server { listen 80; listen [::]:80; server_name nextcloud.yourdomain.com; return 301 https://$host$request_uri; } # HTTPS server server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name nextcloud.yourdomain.com; # SSL certificates ssl_certificate /etc/letsencrypt/live/nextcloud.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/nextcloud.yourdomain.com/privkey.pem; # SSL configuration ssl_session_timeout 5m; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Allow large file uploads client_max_body_size 0; # Proxy to Nextcloud container location / { proxy_set_header Host $host; proxy_pass_request_headers on; proxy_buffers 64 4k; proxy_pass https://127.0.0.1:4430; } }
Enable the site by creating a symbolic link.
sudo ln -s /etc/nginx/sites-available/nextcloud.yourdomain.com /etc/nginx/sites-enabled/
Verify nginx configuration file permissions are secure.
sudo chmod 644 /etc/nginx/sites-available/nextcloud.yourdomain.com
Test the nginx configuration for syntax errors.
sudo nginx -t
Reload nginx to apply the changes.
sudo systemctl reload nginx
Ensure proper ownership and permissions for the Nextcloud deployment directory.
sudo chown -R $USER:$USER /opt/nextcloud sudo chmod 755 /opt/nextcloud sudo chmod 600 /opt/nextcloud/db.env
Navigate to https://nextcloud.yourdomain.com in a web browser.
Create an administrator account by providing a username and password.
Configure the database connection:
nextclouddb.env)nextclouddb:5432Click "Install" to complete the setup.
Navigate to the Nextcloud deployment directory.
cd /opt/nextcloud
Access the Nextcloud container shell.
docker compose exec -u www-data app php occ
Configure trusted domains to include your domain name.
docker compose exec -u www-data app php occ config:system:set trusted_domains 1 --value=nextcloud.yourdomain.com
Enable Redis caching for improved performance.
docker compose exec -u www-data app php occ config:system:set redis host --value=redis docker compose exec -u www-data app php occ config:system:set redis port --value=6379 docker compose exec -u www-data app php occ config:system:set memcache.locking --value='\OC\Memcache\Redis'
Configure background jobs to use cron.
docker compose exec -u www-data app php occ background:cron
Navigate to the Nextcloud directory for maintenance operations.
cd /opt/nextcloud
Update Nextcloud containers by pulling the latest images.
docker compose pull docker compose up -d
Alternatively, use the systemd service.
sudo systemctl restart nextcloud
Create a backup directory to store database dumps.
sudo mkdir -p /var/backups/nextcloud
Back up the PostgreSQL database.
docker compose exec -T db pg_dump -U nextcloud nextcloud | sudo tee /var/backups/nextcloud/nextcloud_db_$(date +%Y%m%d_%H%M%S).sql > /dev/null
Back up the Nextcloud data volume. The data is stored in a Docker volume which can be inspected with the following command.
docker volume inspect nextcloud_nextcloud
Create a compressed archive of the Nextcloud volume data.
sudo docker run --rm -v nextcloud_nextcloud:/data -v /var/backups/nextcloud:/backup alpine tar czf /backup/nextcloud_data_$(date +%Y%m%d_%H%M%S).tar.gz -C /data .
Automated backups can be configured using a systemd timer or cron job.
The Nextcloud installation is now complete and accessible at https://nextcloud.yourdomain.com with proper SSL/TLS encryption, security headers, and optimized caching.