Self-host Nextcloud, Properly
Nextcloud is the open-source alternative to Dropbox + Google Docs + Google Calendar. Install instructions on the official site will get you to "it loads." This is the additional config that gets you to "production-ready" — fast, supervised, observable, and with backups that work.
The stack
nginx (web), PHP-FPM 8.3 (application), PostgreSQL 16 (database), Redis (file locking + sessions), APCu (local cache), Certbot (TLS). All Debian-packaged.
apt install -y nginx postgresql redis-server \
php8.3-fpm php8.3-pgsql php8.3-redis php8.3-apcu \
php8.3-gd php8.3-curl php8.3-zip php8.3-xml php8.3-mbstring \
php8.3-bcmath php8.3-gmp php8.3-imagick certbot python3-certbot-nginx
Database
sudo -u postgres createuser nextcloud --pwprompt
sudo -u postgres createdb -O nextcloud nextcloud
Filesystem layout
mkdir -p /var/www/nextcloud /srv/nextcloud-data
chown -R www-data:www-data /var/www/nextcloud /srv/nextcloud-data
chmod 0750 /srv/nextcloud-data
Keep the data directory outside the web root. nginx must never be able to serve user files directly.
nginx config snippet
upstream php-handler { server unix:/run/php/php8.3-fpm.sock; }
server {
listen 443 ssl http2;
server_name cloud.example.com;
root /var/www/nextcloud;
client_max_body_size 10G;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy no-referrer always;
location = /robots.txt { allow all; log_not_found off; access_log off; }
location / {
rewrite ^ /index.php;
}
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ {
deny all;
}
location ~ \.php(?:$|/) {
include fastcgi_params;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTPS on;
fastcgi_pass php-handler;
fastcgi_read_timeout 600s;
}
}
config.php tuning
'memcache.local' => '\OC\Memcache\APCu',
'memcache.distributed' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => [
'host' => '/var/run/redis/redis-server.sock',
'port' => 0,
],
'default_phone_region' => 'US',
'maintenance_window_start' => 1, // run heavy jobs at 1 AM
'log_type' => 'file',
'logfile' => '/var/log/nextcloud/nextcloud.log',
Background jobs
Nextcloud has a cron job runner. The web-based AJAX runner is fine for tiny installs; for anything real, use system cron:
(crontab -u www-data -l 2>/dev/null; \
echo "*/5 * * * * php -f /var/www/nextcloud/cron.php") | crontab -u www-data -
PHP-FPM pool sizing
; /etc/php/8.3/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 30 ; total ≈ available_RAM / 80MB
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
pm.max_requests = 500
Backups
Three things to back up: the database, the data directory, and config/config.php. Put the install into maintenance mode first to ensure a consistent snapshot:
occ() { sudo -u www-data php /var/www/nextcloud/occ "$@"; }
occ maintenance:mode --on
pg_dump -U nextcloud nextcloud | gzip > /backups/nc-$(date +%F).sql.gz
rsync -a /srv/nextcloud-data/ /backups/nextcloud-data/
occ maintenance:mode --off