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— mainhttp{}block: tuning, real-ip, gzip, headers-moreconf.d/header.conf— HSTS and the Nextcloud-recommended hardening headersconf.d/ssl.conf— TLS 1.2, AEAD ciphers, OCSP staplingsites-available/drive.mulas.me— the vhostcustom.d/— mime types, cors, error pages, charset mapsfcgi.d/— fastcgi / scgi / uwsgi paramssnippets/— 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 usesmore_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.meand adhparams.pemat/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.1if you're not in that situation. set_real_ip_frominnginx.conftrusts127.0.0.1and192.168.1.0/24. Change it for your network.ssl.confis TLS 1.2 only. There's a commented-out line to flip on TLS 1.3 when you're ready.client_max_body_sizeis 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.