Files
nextcloud/README.md

4.7 KiB

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}/<service>/{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

# 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

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):

'dbtype'   => 'mysql',
'dbhost'   => '127.0.0.1:3306',
'dbname'   => '<MYSQL_DATABASE>',
'dbuser'   => '<MYSQL_USER>',
'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.