# 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`) - 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` - Redis service for transactional file locking and distributed cache - Floating app image tag enabled: `nextcloud:apache` (major upgrades supported with staged path) - Setup warning remediation integrated (missing indices, mimetype migrations, log-noise cleanup) - Nextcloud Talk high-performance backend service (`talk-hpb`) with signaling endpoint - Nextcloud desktop client push via `notify_push` app and `notify-push` service ## Current baseline (Apr 2026) - App image: `nextcloud:apache` - DB image: `mariadb:11.4` - Cache/locking: `redis:7-alpine` - Nextcloud version at last validation: `33.0.2` - Talk HPB: `ghcr.io/nextcloud-releases/aio-talk:latest` via `/standalone-signaling/` - Client push: `notify_push` app + `nextcloud-notify-push` service via `/push/` ## 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: ```bash cp .env.example .env ``` 2. Edit `.env` with strong values. 3. Start stack with bootstrap TLS: ```bash ./scripts/install.sh ``` 4. Validate: ```bash ./scripts/test.sh ``` ## Enable web updater (one-time verification) Web updater is enabled by design for this deployment. Check values: ```bash 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` ## Update Nextcloud Hub (scripted) Use the upgrade helper script to pull images, apply upgrades, run repairs, and validate endpoints. Run update: ```bash ./scripts/update-nextcloud-hub.sh ``` Optional flags: - Skip app marketplace updates: ```bash RUN_APP_UPDATES=0 ./scripts/update-nextcloud-hub.sh ``` - Skip expensive repairs/mimetype migration pass: ```bash RUN_EXPENSIVE_REPAIR=0 ./scripts/update-nextcloud-hub.sh ``` - Require strict TLS validation during smoke tests (no `-k`): ```bash STRICT_TLS=1 ./scripts/update-nextcloud-hub.sh ``` What the script does: - pulls latest `db`, `app`, and `web` images - recreates services and restarts `web` to refresh upstream resolution - ensures web updater is enabled and release channel is configured - ensures maintenance mode is off before `occ upgrade` - runs `occ upgrade` - runs `occ app:update --all` (unless disabled) - runs schema and repair commands (`db:add-missing-*`, `maintenance:repair`) - optionally runs `maintenance:repair --include-expensive` - runs `occ status`, `occ setupchecks`, and `scripts/test.sh` ### Important: major upgrades are one-at-a-time Nextcloud only supports upgrading one major version at a time. If the floating `nextcloud:apache` tag is more than one major ahead of your installed version, do staged upgrades first, for example: 1. `nextcloud:31-apache` → run updater script 2. `nextcloud:32-apache` → run updater script 3. `nextcloud:33-apache` → run updater script 4. switch back to `nextcloud:apache` ## Automate Hub updates (cron) Install/update a weekly cron job (default: Sunday 02:30): ```bash ./scripts/setup-hub-update-cron.sh ``` Optional custom schedule: ```bash 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 tracked in `.hub-update.env.example`: - `RUN_APP_UPDATES=1` - `RUN_EXPENSIVE_REPAIR=1` - `STRICT_TLS=0` - `ENABLE_WEB_UPDATER=1` - `UPDATER_RELEASE_CHANNEL=stable` ## Setup warning remediation notes The following warning-focused fixes are now part of the deployed configuration: - **Transactional File Locking**: Redis-backed locking enabled (`memcache.locking=Redis`) - **Mimetype migrations**: addressed via `maintenance:repair --include-expensive` - **Missing optional indices**: addressed via `occ db:add-missing-indices` - **AppAPI deploy daemon errors**: `app_api` disabled (not used in this deployment) - **Internet connectivity check log spam**: `has_internet_connection=false` set intentionally for this environment If you want AppAPI external apps later: - re-enable app: `docker exec --user www-data nextcloud-app php occ app:enable app_api` - configure a reachable deploy daemon from Settings > AppAPI ## Deploy Talk HPB and client push This deployment includes Talk HPB and desktop client push support in Docker Compose. Required secrets in `.env` (already templated in `.env.example`): - `TALK_TURN_SECRET` - `TALK_SIGNALING_SECRET` - `TALK_INTERNAL_SECRET` 1. Start/update services: ```bash docker compose up -d app talk-hpb notify-push web ``` 2. Configure Talk signaling/STUN/TURN in Nextcloud: ```bash set -a; source .env; set +a docker exec --user www-data nextcloud-app php occ talk:signaling:add --verify https://nxt.bhatfamily.in:8446/standalone-signaling "$TALK_SIGNALING_SECRET" docker exec --user www-data nextcloud-app php occ talk:stun:add nxt.bhatfamily.in:3478 docker exec --user www-data nextcloud-app php occ talk:turn:add --secret="$TALK_TURN_SECRET" turn nxt.bhatfamily.in:3478 udp,tcp ``` 3. Configure Client Push (`notify_push`): ```bash docker exec --user www-data nextcloud-app php occ app:install notify_push docker exec --user www-data nextcloud-app php occ notify_push:setup https://nxt.bhatfamily.in:8446/push docker exec --user www-data nextcloud-app php occ notify_push:self-test ``` If `notify_push` is already installed, skip `app:install` and run `notify_push:setup` + `self-test`. 4. Verify endpoints and setup checks: ```bash curl -k https://nxt.bhatfamily.in:8446/standalone-signaling/api/v1/welcome docker exec --user www-data nextcloud-app php occ setupchecks ``` ## One-time setup/security hardening commands These commands were used to clear remaining setup/security notices in this deployment: ```bash docker exec --user www-data nextcloud-app php occ twofactorauth:enforce --on docker exec --user www-data nextcloud-app php occ config:system:set default_phone_region --value=IN docker exec --user www-data nextcloud-app php occ config:system:set serverid --type=integer --value=1 docker exec --user www-data nextcloud-app php occ config:system:set mail_smtpmode --value=null ``` Adjust `default_phone_region` to your country code as needed. ## 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: ```bash ./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: ```bash docker exec --user www-data nextcloud-app php occ status ``` ## Production TLS (Let's Encrypt + Cloudflare DNS-01) 1. Export credentials in shell: ```bash export CF_DNS_API_TOKEN={{CF_DNS_API_TOKEN}} export LETSENCRYPT_EMAIL={{LETSENCRYPT_EMAIL}} ``` 2. Issue/renew and install production cert: ```bash ./scripts/provision-production-tls.sh ``` 3. Reload Nginx container: ```bash docker compose restart web ``` 4. Verify cert: ```bash 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` 2. Install/update renewal cron entry: ```bash ./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` 3. Manual renewal run (same path cron uses): ```bash ./scripts/renew-production-tls.sh ``` ## Admin password reset List existing users: ```bash docker exec --user www-data nextcloud-app php occ user:list ``` Reset password using helper script (interactive prompt): ```bash ./scripts/reset-admin-password.sh admin ``` Reset password non-interactively (for automation): ```bash 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. ## Useful commands Start/update containers: ```bash docker compose up -d ``` Restart all services: ```bash docker compose restart ``` Restart web only: ```bash docker compose restart web ``` Stop and remove containers/volumes: ```bash ./scripts/uninstall.sh ``` ## Security notes - `.env`, `.tls-renewal.env`, `.hub-update.env`, and runtime cert material under `nginx/ssl` are intentionally ignored by Git. - `backups/` is ignored and used for local database/log snapshots before risky changes. - Keep `.env` mode restricted (`chmod 600 .env`). - If secrets were ever committed earlier, rotate them.