Files
nextcloud-docker/README.md
2026-04-17 10:33:46 +05:30

7.4 KiB

Nextcloud on Ubuntu via Docker for nxt.bhatfamily.in

This repository deploys Nextcloud behind Nginx using Docker Compose.

Exposed ports:

  • HTTP: 8082
  • HTTPS: 8446

Target hostname:

  • nxt.bhatfamily.in

What changed

The stack now includes:

  • Fixed Nginx mount path (nginx/nginx.conf mapped correctly)
  • Fixed MariaDB command (mariadbd)
  • Nginx reverse proxy mode for nextcloud:apache (no FastCGI mismatch)
  • Production TLS provisioning using Let's Encrypt DNS-01 with Cloudflare
  • Automated TLS renewal job support (cron)
  • Nextcloud app startup fixes for Apache ServerName and writable Fontconfig cache
  • Persistent web-updater enablement (upgrade-disable-web.config.php forced to false on app startup)
  • Reverse-proxy trust configuration in Nextcloud (trusted_proxies, forwarded_for_headers)
  • Nginx hardening (server_tokens off, stronger HSTS, hide X-Powered-By, TLS session hardening)
  • Brute-force protection explicitly enabled and maintenance window configured
  • Scripted Nextcloud Hub upgrade workflow: scripts/update-nextcloud-hub.sh
  • Automated Hub update scheduler: scripts/setup-hub-update-cron.sh

Prerequisites

  • Ubuntu host with Docker + Docker Compose plugin (or docker-compose)
  • Domain nxt.bhatfamily.in in Cloudflare DNS
  • DNS A record for nxt pointing to your server public IP (DNS-only)
  • Router/firewall forwarding for ports 8082 and 8446

Initial setup

  1. Create runtime env file:
cp .env.example .env
  1. Edit .env with strong values.
  2. Start stack with bootstrap TLS:
./scripts/install.sh
  1. Validate:
./scripts/test.sh

Enable web updater (one-time verification)

Web updater is now enabled by design for this deployment.

Check values:

docker exec --user www-data nextcloud-app php occ config:list system | grep -E "upgrade.disable-web|updater.release.channel"

Expected:

  • upgrade.disable-web: false
  • updater.release.channel: stable

If you need to enforce immediately without restart, run:

./scripts/update-nextcloud-hub.sh

This script also rewrites config/upgrade-disable-web.config.php inside the app container volume.

Update Nextcloud Hub (scripted)

Use the upgrade helper script to pull new images, apply the upgrade, run post-upgrade repairs, and validate endpoints.

Run update:

./scripts/update-nextcloud-hub.sh

Optional flags:

  • Skip app marketplace updates:
RUN_APP_UPDATES=0 ./scripts/update-nextcloud-hub.sh
  • Require strict TLS validation during smoke tests (no -k):
STRICT_TLS=1 ./scripts/update-nextcloud-hub.sh

What the script does:

  • pulls latest db, app, and web images
  • recreates services via Compose
  • ensures web updater is enabled and release channel is configured
  • ensures maintenance mode is off before occ upgrade (fixes prior deadlock)
  • runs occ upgrade
  • runs occ app:update --all (unless disabled)
  • runs schema and repair commands (db:add-missing-*, maintenance:repair)
  • runs occ status, occ setupchecks, and scripts/test.sh

Recommendation: take a filesystem/database backup before major Hub upgrades.

Automate Hub updates (cron)

Install/update a weekly cron job (default: Sunday 02:30):

./scripts/setup-hub-update-cron.sh

Optional custom schedule:

HUB_UPDATE_CRON_SCHEDULE="15 2 * * 6" ./scripts/setup-hub-update-cron.sh

This setup script will:

  • create/update .hub-update.env (local only, not committed)
  • install/refresh cron entry for scripts/update-nextcloud-hub.sh
  • write logs to logs/hub-update.log

Defaults are tracked in .hub-update.env.example:

  • RUN_APP_UPDATES=1
  • STRICT_TLS=0
  • ENABLE_WEB_UPDATER=1
  • UPDATER_RELEASE_CHANNEL=stable

Move Nextcloud data directory to external storage

Use the migration helper to move existing data to a host path and switch the app to a bind mount.

Default target:

  • /media/rbhat/DATA/nextcloud/NextCloudData

Run migration:

./scripts/migrate-data-directory.sh /media/rbhat/DATA/nextcloud/NextCloudData

What the script does:

  • enables maintenance mode
  • copies current /var/www/html/data content to target directory
  • applies owner/group and permissions for Nextcloud (www-data)
  • updates docker-compose.yml app volume with ...:/var/www/html/data
  • recreates app and web services
  • disables maintenance mode and verifies mount

Rollback (if needed):

  1. Remove the :/var/www/html/data bind mount line from app volumes in docker-compose.yml.
  2. docker compose up -d app web
  3. Confirm status:
docker exec --user www-data nextcloud-app php occ status

Production TLS (Let's Encrypt + Cloudflare DNS-01)

  1. Export credentials in shell:
export CF_DNS_API_TOKEN={{CF_DNS_API_TOKEN}}
export LETSENCRYPT_EMAIL={{LETSENCRYPT_EMAIL}}
  1. Issue/renew and install production cert:
./scripts/provision-production-tls.sh
  1. Reload Nginx container:
docker compose restart web
  1. Verify cert:
echo | openssl s_client -connect nxt.bhatfamily.in:8446 -servername nxt.bhatfamily.in 2>/dev/null | openssl x509 -noout -subject -issuer -dates

Automated renewal job (cron)

  1. Ensure your Cloudflare token export script exists (default path used by renewal wrapper):
  • ~/bin/cloudflare-api-usertoken.sh
  1. Install/update renewal cron entry:
./scripts/setup-renewal-cron.sh

This script will:

  • create/update .tls-renewal.env (local only, not committed)
  • install a daily cron job (03:17 by default)
  • write logs to logs/tls-renew.log
  1. Manual renewal run (same path cron uses):
./scripts/renew-production-tls.sh

Admin password reset

List existing users:

docker exec --user www-data nextcloud-app php occ user:list

Reset password using helper script (interactive prompt):

./scripts/reset-admin-password.sh admin

Reset password non-interactively (for automation):

NEW_NEXTCLOUD_PASSWORD={{NEW_NEXTCLOUD_PASSWORD}} ./scripts/reset-admin-password.sh admin

You can target a different username by passing it as the first argument.

Operational/security changes applied (Apr 2026)

Applied and validated in this deployment:

  • docker-compose.yml (app service):
    • startup command ensures writable /var/cache/fontconfig
    • sets Apache ServerName nxt.bhatfamily.in
    • sets XDG_CACHE_HOME=/tmp/.cache
    • forces config/upgrade-disable-web.config.php to upgrade.disable-web=false
  • nginx/nginx.conf:
    • server_tokens off
    • HSTS set to max-age=63072000; includeSubDomains; preload
    • proxy_hide_header X-Powered-By
    • ssl_session_cache, ssl_session_timeout, ssl_session_tickets off
  • Nextcloud occ settings:
    • trusted_proxies configured to Docker network subnet
    • forwarded_for_headers set to HTTP_X_FORWARDED_FOR
    • auth.bruteforce.protection.enabled=true
    • maintenance_window_start=1
    • updater.release.channel=stable
    • weather_status app disabled to remove repeated PHP warning noise
  • Host security hygiene:
    • .env permission reduced to 600

Useful commands

Start/update containers:

docker compose up -d

Restart all services:

docker compose restart

Restart web only:

docker compose restart web

Stop and remove containers/volumes:

./scripts/uninstall.sh

Security notes

  • .env, .tls-renewal.env, .hub-update.env, and runtime cert material under nginx/ssl are intentionally ignored by Git.
  • Keep .env mode restricted (chmod 600 .env).
  • If secrets were ever committed earlier, rotate them.