# nextcloud Config and compose files for my self-hosted Nextcloud at `drive.net.mulas.me`. Two pieces: - `nginx/` — the vhost, TLS profile, and header set I wrote and hardened for Nextcloud back in **2019** and have been using in production since then. I revised it in 2026 to match new Nextcloud docs directives. - `docker/` — a small Compose stack with MariaDB, Redis, and Memcached. Nextcloud itself runs on the host, served by NGINX and PHP-FPM, because I had an abysmal experience with Nextcloud's containerised setup, in particular on updating and adding modules. The containers only provide the backing services. ## Layout `docker/docker-compose.yml` — the MariaDB + Redis + Memcached stack. Everything under `nginx/` mirrors `/etc/nginx/` on the server: - `nginx.conf` — main `http{}` block: tuning, real-ip, gzip, headers-more - `conf.d/header.conf` — HSTS and the Nextcloud-recommended hardening headers - `conf.d/ssl.conf` — TLS 1.2, AEAD ciphers, OCSP stapling - `sites-available/drive.mulas.me` — the vhost - `custom.d/` — mime types, cors, error pages, charset maps - `fcgi.d/` — fastcgi / scgi / uwsgi params - `snippets/` — letsencrypt ACME challenge, snakeoil ## Docker stack Images are pinned, all overridable via env: | Service | Default tag | Env override | |-------------|--------------------|---------------------------| | `db` | `mariadb:11.8-ubi9`| `ENV_MARIADB_VERSION` | | `redis` | `redis:7.4.9-alpine`| `ENV_REDIS_VERSION` | | `memcached` | `memcached:alpine3.23`| `ENV_MEMCACHED_VERSION` | State, configs, and sockets land under `${ENV_BASE_DIR}//{data,config,socket}`. In production `ENV_BASE_DIR=/opt/nextcloud`. MariaDB credentials are mounted as Docker secrets — files on the host, never in the repo: ``` /opt/nextcloud/secrets/mariadb_root_password.txt /opt/nextcloud/secrets/mariadb_app_password.txt ``` `MYSQL_USER` and `MYSQL_DATABASE` are intentionally blank in the compose file; fill them in (or move them to an `.env`) before bringing the stack up. ## Bringing it up ```bash # host paths sudo mkdir -p /opt/nextcloud/{mariadb,redis,memcached}/{data,config,socket} sudo mkdir -p /opt/nextcloud/secrets && sudo chmod 700 /opt/nextcloud/secrets # secrets openssl rand -base64 48 | sudo tee /opt/nextcloud/secrets/mariadb_root_password.txt openssl rand -base64 48 | sudo tee /opt/nextcloud/secrets/mariadb_app_password.txt sudo chmod 600 /opt/nextcloud/secrets/*.txt # env echo 'ENV_BASE_DIR=/opt/nextcloud' > docker/.env # go ( cd docker && docker compose up -d ) ``` Wait for the `db` healthcheck (it pings `mysqladmin`) to come up green before pointing Nextcloud at it. ## Nginx install ```bash sudo cp nginx/nginx.conf /etc/nginx/nginx.conf sudo cp -r nginx/conf.d/* /etc/nginx/conf.d/ sudo cp -r nginx/custom.d nginx/fcgi.d /etc/nginx/ sudo cp -r nginx/snippets/* /etc/nginx/snippets/ sudo cp nginx/sites-available/drive.mulas.me /etc/nginx/sites-available/ sudo ln -sf /etc/nginx/sites-available/drive.mulas.me /etc/nginx/sites-enabled/drive.mulas.me sudo nginx -t && sudo systemctl reload nginx ``` What you need on the host for this to work: - Nginx built with `headers-more` (the config uses `more_set_headers`) - PHP-FPM 8.5 with a socket at `/var/run/php/php8.5-fpm.sock` - A Let's Encrypt cert for `drive.net.mulas.me` and a `dhparams.pem` at `/etc/ssl/private/dhparams.pem` - Nextcloud unpacked at `/ssda1/www/drive.net.mulas.me/` ## Wiring Nextcloud to the stack In `config/config.php` (not in this repo): ```php 'dbtype' => 'mysql', 'dbhost' => '127.0.0.1:3306', 'dbname' => '', 'dbuser' => '', 'dbpassword' => trim(file_get_contents('/opt/nextcloud/secrets/mariadb_app_password.txt')), 'memcache.local' => '\OC\Memcache\APCu', 'memcache.distributed' => '\OC\Memcache\Memcached', 'memcache.locking' => '\OC\Memcache\Redis', 'redis' => ['host' => '127.0.0.1', 'port' => 6379], 'memcached_servers' => [['127.0.0.1', 11211]], ``` ## Things worth knowing before reusing this elsewhere - The compose file publishes 3306/6379/11211 on all interfaces — fine when Nextcloud runs on the same host and the box has a firewall, but bind them to `127.0.0.1` if you're not in that situation. - `set_real_ip_from` in `nginx.conf` trusts `127.0.0.1` and `192.168.1.0/24`. Change it for your network. - `ssl.conf` is TLS 1.2 only. There's a commented-out line to flip on TLS 1.3 when you're ready. - `client_max_body_size` is 50 GB and the proxy/fastcgi timeouts are essentially infinite — that's deliberate for large WebDAV uploads and long sync sessions, not an oversight. ## License BSD 3-clause — see [LICENSE](LICENSE).