How to Install Nextcloud on FreeBSD
Nextcloud gives you a self-hosted alternative to Google Drive, Dropbox, and Office 365 -- file sync, calendar, contacts, video calls, and collaborative document editing under your own control. FreeBSD is an excellent platform for running it: ZFS provides checksummed, snapshot-capable storage; jails let you isolate the service cleanly; and the ports tree keeps every dependency a pkg install away.
This guide walks through a complete, production-grade Nextcloud deployment on FreeBSD using NGINX, PHP-FPM, PostgreSQL, Redis, and Let's Encrypt. By the end you will have a fully working instance with caching, background jobs, and a solid backup strategy.
Why Self-Host Nextcloud on FreeBSD
Three reasons stand out over managed cloud storage:
Privacy and data sovereignty. Your files live on hardware you control. No third party scans your documents, trains models on your data, or changes terms of service on you. For businesses handling client data or anyone subject to GDPR, this matters.
ZFS for data integrity. FreeBSD's first-class ZFS support means every block of your Nextcloud data is checksummed. Silent bit rot -- the kind that corrupts a file without any error message -- simply does not happen. ZFS snapshots also give you instant, space-efficient rollback points before upgrades or bulk changes.
Stability and performance. FreeBSD's network stack and I/O subsystem are battle-tested in hosting environments. Combined with NGINX and PHP-FPM, you get a lean, predictable server that does not surprise you with unattended reboots or forced updates.
Prerequisites
Before starting, make sure you have:
- A FreeBSD 14.x server with root access (a fresh install is ideal).
- A registered domain name (e.g.,
cloud.example.com) with DNS A/AAAA records pointing to your server's IP. - Ports for HTTP (80) and HTTPS (443) open in your firewall.
- At least 1 GB of RAM and 20 GB of disk space (more for heavy use).
This guide assumes a clean FreeBSD 14.2-RELEASE system. All commands run as root unless noted otherwise.
Installing Dependencies
Nextcloud requires a web server, PHP with several extensions, and a database. We use NGINX, PHP 8.3 with FPM, and PostgreSQL 16.
shpkg update && pkg upgrade -y pkg install -y \ nginx \ php83 php83-fpm php83-extensions \ php83-curl php83-dom php83-fileinfo php83-gd php83-iconv \ php83-intl php83-mbstring php83-opcache php83-pdo_pgsql \ php83-pecl-APCu php83-pecl-redis php83-pgsql php83-session \ php83-simplexml php83-xml php83-xmlreader php83-xmlwriter \ php83-zip php83-zlib php83-bcmath php83-gmp php83-exif \ postgresql16-server postgresql16-client \ redis \ certbot py311-certbot-nginx \ sudo
Enable all services at boot:
shsysrc nginx_enable=YES sysrc php_fpm_enable=YES sysrc postgresql_enable=YES sysrc redis_enable=YES
Initialize and start PostgreSQL:
sh/usr/local/etc/rc.d/postgresql initdb service postgresql start
Start Redis:
shservice redis start
For a deeper look at the web server layer, see our NGINX setup guide. For PostgreSQL specifics, the PostgreSQL on FreeBSD guide covers tuning and maintenance.
Database Setup
Create a dedicated PostgreSQL user and database for Nextcloud. Do not use the default postgres superuser for the application.
shsudo -u postgres psql
Inside the PostgreSQL shell:
sqlCREATE USER nextcloud WITH PASSWORD 'a-strong-random-password-here'; CREATE DATABASE nextcloud OWNER nextcloud; GRANT ALL PRIVILEGES ON DATABASE nextcloud TO nextcloud; \q
Replace a-strong-random-password-here with a real password. Use openssl rand -base64 32 to generate one.
Downloading and Installing Nextcloud
Grab the latest stable release from the official site and extract it to the web root.
shcd /tmp fetch https://download.nextcloud.com/server/releases/latest.tar.bz2 fetch https://download.nextcloud.com/server/releases/latest.tar.bz2.sha256 # Verify the checksum sha256 -c latest.tar.bz2.sha256 tar xjf latest.tar.bz2 -C /usr/local/www/ chown -R www:www /usr/local/www/nextcloud
Create the data directory outside the web root for security:
shmkdir -p /var/nextcloud/data chown www:www /var/nextcloud/data chmod 750 /var/nextcloud/data
PHP-FPM Pool Configuration
Create a dedicated FPM pool for Nextcloud. This isolates it from other PHP applications and lets you tune resources independently.
Edit /usr/local/etc/php-fpm.d/nextcloud.conf:
ini[nextcloud] user = www group = www listen = /var/run/php-fpm-nextcloud.sock listen.owner = www listen.group = www listen.mode = 0660 pm = dynamic pm.max_children = 16 pm.start_servers = 4 pm.min_spare_servers = 2 pm.max_spare_servers = 8 pm.max_requests = 500 env[HOSTNAME] = $HOSTNAME env[PATH] = /usr/local/bin:/usr/bin:/bin env[TMP] = /tmp env[TMPDIR] = /tmp env[TEMP] = /tmp php_admin_value[memory_limit] = 512M php_admin_value[upload_max_filesize] = 16G php_admin_value[post_max_size] = 16G php_admin_value[max_execution_time] = 3600 php_admin_value[max_input_time] = 3600 php_admin_value[output_buffering] = 0 php_admin_value[opcache.enable] = 1 php_admin_value[opcache.memory_consumption] = 128 php_admin_value[opcache.interned_strings_buffer] = 16 php_admin_value[opcache.max_accelerated_files] = 10000 php_admin_value[opcache.revalidate_freq] = 1 php_admin_value[opcache.save_comments] = 1
Remove or rename the default pool to avoid conflicts:
shmv /usr/local/etc/php-fpm.d/www.conf /usr/local/etc/php-fpm.d/www.conf.bak
Copy the production PHP configuration:
shcp /usr/local/etc/php.ini-production /usr/local/etc/php.ini
Start PHP-FPM:
shservice php-fpm start
NGINX Virtual Host Configuration
Create /usr/local/etc/nginx/conf.d/nextcloud.conf. This configuration follows the Nextcloud hardening recommendations and includes proper headers, request size limits, and PHP-FPM pass-through.
First, make sure your main nginx.conf includes the conf.d directory. Add this inside the http block if it is not already there:
nginxinclude /usr/local/etc/nginx/conf.d/*.conf;
Now create the virtual host:
nginxupstream php-handler { server unix:/var/run/php-fpm-nextcloud.sock; } server { listen 80; listen [::]:80; server_name cloud.example.com; # Redirect all HTTP to HTTPS return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name cloud.example.com; # SSL configuration -- managed by certbot (see next section) ssl_certificate /usr/local/etc/letsencrypt/live/cloud.example.com/fullchain.pem; ssl_certificate_key /usr/local/etc/letsencrypt/live/cloud.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # Nextcloud root root /usr/local/www/nextcloud; # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header Referrer-Policy "no-referrer" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Download-Options "noopen" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Permitted-Cross-Domain-Policies "none" always; add_header X-Robots-Tag "noindex, nofollow" always; add_header X-XSS-Protection "1; mode=block" always; # Remove X-Powered-By header fastcgi_hide_header X-Powered-By; # Max upload size client_max_body_size 16G; client_body_timeout 3600s; fastcgi_buffers 64 4K; # Disable gzip to avoid the BREACH attack gzip off; # Pagespeed is not supported # pagespeed off; # The settings below effectively turn off the floc tracking add_header Permissions-Policy "interest-cohort=()"; # Rule borrowed from `.htaccess` to handle Microsoft DAV clients location = / { if ( $http_user_agent ~ ^DavClnt ) { return 302 /remote.php/webdav/$is_args$args; } } location = /robots.txt { allow all; log_not_found off; access_log off; } # Make a regex exception for `/.well-known` so that clients can still # access it despite the existence of the `location ~` block below location ^~ /.well-known { # The rules in this block are an adaptation of the rules # having `/.well-known` in the Nextcloud `.htaccess` location = /.well-known/carddav { return 301 /remote.php/dav/; } location = /.well-known/caldav { return 301 /remote.php/dav/; } location /.well-known/acme-challenge { try_files $uri $uri/ =404; } location /.well-known/pki-validation { try_files $uri $uri/ =404; } return 301 /index.php$request_uri; } # Rules borrowed from `.htaccess` location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; } location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; } # Ensure this block, which passes PHP files to the PHP process, # is above the blocks which handle static assets. location ~ \.php(?:$|/) { rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode(_arm64)?\/proxy) /index.php$request_uri; fastcgi_split_path_info ^(.+?\.php)(/.*)$; set $path_info $fastcgi_path_info; try_files $fastcgi_script_name =404; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $path_info; fastcgi_param HTTPS on; fastcgi_param modHeadersAvailable true; fastcgi_param front_controller_active true; fastcgi_pass php-handler; fastcgi_intercept_errors on; fastcgi_request_buffering off; fastcgi_read_timeout 3600; } # Serve static files location ~ \.(?:css|js|mjs|svg|gif|png|jpg|ico|wasm|tflite|map|ogg|flac)$ { try_files $uri /index.php$request_uri; add_header Cache-Control "public, max-age=15778463, immutable"; access_log off; } location ~ \.woff2?$ { try_files $uri /index.php$request_uri; expires 7d; access_log off; } # Rule borrowed from `.htaccess` location /remote { return 301 /remote.php$request_uri; } location / { try_files $uri $uri/ /index.php$request_uri; } }
Replace every instance of cloud.example.com with your actual domain. Test and start NGINX:
shnginx -t service nginx start
SSL with Let's Encrypt
Obtain a free TLS certificate using certbot. Before running certbot, temporarily comment out the SSL lines in the NGINX config (or use the HTTP-only server block) so NGINX can start without the certificates that do not exist yet.
A simpler approach: use certbot in standalone mode first, then switch to the full config.
sh# Stop NGINX temporarily service nginx stop # Obtain the certificate certbot certonly --standalone -d cloud.example.com \ --agree-tos --no-eff-email -m admin@example.com
Certificates land in /usr/local/etc/letsencrypt/live/cloud.example.com/. Now start NGINX with the full configuration:
shservice nginx start
Set up automatic renewal with a cron job:
shcrontab -e
Add:
shell0 3 * * * /usr/local/bin/certbot renew --quiet --deploy-hook "service nginx reload"
For a more detailed walkthrough of certificate management, see our Let's Encrypt on FreeBSD guide.
Nextcloud Web Installer
Open https://cloud.example.com in your browser. Nextcloud's setup wizard appears and asks for:
- Admin account -- choose a username and strong password. Do not use "admin" as the username.
- Data directory -- enter
/var/nextcloud/data(the directory you created earlier). - Database -- select PostgreSQL and fill in:
- Database user:
nextcloud - Database password: the password you set earlier
- Database name:
nextcloud - Database host:
localhost
Click "Install" and wait. Nextcloud creates all the tables and generates the initial configuration. This takes a minute or two depending on disk speed.
After installation completes, you land on the Nextcloud dashboard. Before doing anything else, set up background jobs and caching.
Cron Job Setup
Nextcloud needs to run background tasks regularly -- clearing caches, sending notifications, running file scans. The default AJAX method is unreliable. Switch to system cron.
First, go to Settings > Administration > Basic settings in the Nextcloud web interface. Under "Background jobs", select Cron.
Then add the cron job for the www user:
shcrontab -u www -e
Add this line:
shell*/5 * * * * /usr/local/bin/php -f /usr/local/www/nextcloud/cron.php
This runs Nextcloud's background tasks every 5 minutes. Verify it works after a few minutes by checking the "Last job ran" timestamp in the admin settings.
Essential config.php Tuning
Nextcloud's configuration lives in /usr/local/www/nextcloud/config/config.php. After the web installer runs, edit this file to add caching, trusted proxies, and other production settings.
shee /usr/local/www/nextcloud/config/config.php
Add or modify these entries inside the $CONFIG array:
php'memcache.local' => '\OC\Memcache\APCu', 'memcache.distributed' => '\OC\Memcache\Redis', 'memcache.locking' => '\OC\Memcache\Redis', 'redis' => [ 'host' => '/var/run/redis/redis.sock', 'port' => 0, 'timeout' => 1.5, ], 'overwrite.cli.url' => 'https://cloud.example.com', 'overwriteprotocol' => 'https', 'default_phone_region' => 'US', 'default_locale' => 'en_US', 'maintenance_window_start' => 1, 'trashbin_retention_obligation' => 'auto, 30', 'log_type' => 'file', 'logfile' => '/var/log/nextcloud.log', 'loglevel' => 2, 'log_rotate_size' => 104857600,
Create the log file:
shtouch /var/log/nextcloud.log chown www:www /var/log/nextcloud.log
Redis for Caching and File Locking
Redis dramatically improves Nextcloud performance by caching database queries, user sessions, and file lock states in memory. You already installed and enabled it earlier. Now configure it to use a Unix socket for lower latency.
Edit /usr/local/etc/redis.conf:
sh# Find and modify these settings: port 0 unixsocket /var/run/redis/redis.sock unixsocketperm 770
Add the www user to the redis group so PHP-FPM can access the socket:
shpw groupmod redis -m www
Restart Redis:
shservice redis restart
Verify the socket exists and has correct permissions:
shls -la /var/run/redis/redis.sock
You should see srwxrwx--- with owner redis and group redis. Since www is now in the redis group, PHP-FPM can connect.
Recommended Apps
Nextcloud's app store has hundreds of add-ons. These are the essentials worth installing immediately from Settings > Apps:
Calendar -- CalDAV calendar with sharing, reminders, and iCal import. Replaces Google Calendar.
Contacts -- CardDAV address book. Syncs with mobile devices and desktop clients via standard protocols.
Nextcloud Talk -- Video calls, screen sharing, and chat. Works in the browser and through dedicated mobile apps. A solid replacement for Zoom or Teams for small teams.
Nextcloud Office (Collabora or ONLYOFFICE) -- Collaborative editing of documents, spreadsheets, and presentations directly in the browser. Requires a separate document server container or the built-in CODE app for small deployments.
Notes -- Simple markdown note-taking that syncs across devices.
Deck -- Kanban-style project boards. Lightweight alternative to Trello.
Install apps from the command line if you prefer:
shsudo -u www php /usr/local/www/nextcloud/occ app:install calendar sudo -u www php /usr/local/www/nextcloud/occ app:install contacts sudo -u www php /usr/local/www/nextcloud/occ app:install spreed sudo -u www php /usr/local/www/nextcloud/occ app:install notes sudo -u www php /usr/local/www/nextcloud/occ app:install deck
Backup Strategy
A proper backup covers three things: the Nextcloud files, the database, and the configuration. ZFS makes the file backup trivial.
ZFS Snapshots for Files and Data
If your Nextcloud data lives on a ZFS dataset (and it should), take regular snapshots:
sh# Create a snapshot zfs snapshot zroot/var/nextcloud@$(date +%Y-%m-%d_%H%M) # List snapshots zfs list -t snapshot | grep nextcloud # Roll back to a snapshot (destructive -- removes newer data) zfs rollback zroot/var/nextcloud@2026-03-29_0300
Automate daily snapshots with a cron job:
sh0 2 * * * /sbin/zfs snapshot zroot/var/nextcloud@auto-$(date +\%Y-\%m-\%d)
Add a weekly cleanup to remove snapshots older than 30 days:
sh0 4 * * 0 /sbin/zfs list -H -t snapshot -o name | grep 'zroot/var/nextcloud@auto-' | head -n -30 | xargs -n 1 zfs destroy
For more on ZFS snapshot management and send/receive for offsite replication, see our ZFS on FreeBSD guide.
PostgreSQL Database Backup
ZFS snapshots do not guarantee a consistent database state. Use pg_dump for database backups:
shsudo -u postgres pg_dump -Fc nextcloud > /var/backups/nextcloud-db-$(date +%Y-%m-%d).dump
Automate it:
shmkdir -p /var/backups chown postgres:postgres /var/backups
Add to the postgres user's crontab:
shell0 2 * * * /usr/local/bin/pg_dump -Fc nextcloud > /var/backups/nextcloud-db-$(date +\%Y-\%m-\%d).dump
To restore:
shsudo -u postgres pg_restore -d nextcloud --clean /var/backups/nextcloud-db-2026-03-29.dump
Configuration Backup
Back up the Nextcloud config directory and NGINX configs:
shtar czf /var/backups/nextcloud-config-$(date +%Y-%m-%d).tar.gz \ /usr/local/www/nextcloud/config/ \ /usr/local/etc/nginx/conf.d/ \ /usr/local/etc/php-fpm.d/
Putting It Together
Enable Nextcloud maintenance mode before backing up for full consistency:
shsudo -u www php /usr/local/www/nextcloud/occ maintenance:mode --on # Snapshot, dump, tar zfs snapshot zroot/var/nextcloud@backup-$(date +%Y-%m-%d) sudo -u postgres pg_dump -Fc nextcloud > /var/backups/nextcloud-db-$(date +%Y-%m-%d).dump sudo -u www php /usr/local/www/nextcloud/occ maintenance:mode --off
Performance Tuning
PHP OPcache
OPcache is already configured in the PHP-FPM pool above. Verify it is active:
shsudo -u www php -i | grep opcache.enable
You should see opcache.enable => On. The settings in the FPM pool config give OPcache 128 MB of shared memory and room for 10,000 cached scripts, which is plenty for Nextcloud with apps.
For additional PHP tuning, enable JIT compilation in /usr/local/etc/php.ini:
iniopcache.jit = 1255 opcache.jit_buffer_size = 128M
Restart PHP-FPM after changes:
shservice php-fpm restart
NGINX FastCGI Cache
For multi-user installations where many people access the same shared files, a FastCGI cache can reduce PHP load significantly. Add this to the http block in nginx.conf:
nginxfastcgi_cache_path /var/cache/nginx/nextcloud levels=1:2 keys_zone=nextcloud_cache:100m inactive=60m; fastcgi_cache_key "$scheme$request_method$host$request_uri";
Create the cache directory:
shmkdir -p /var/cache/nginx/nextcloud chown www:www /var/cache/nginx/nextcloud
Then in the PHP location block of your Nextcloud server config, add:
nginxfastcgi_cache nextcloud_cache; fastcgi_cache_valid 200 60m; fastcgi_cache_use_stale error timeout updating http_500 http_503; fastcgi_cache_bypass $http_cookie; fastcgi_no_cache $http_cookie;
Be cautious with this: Nextcloud serves personalized content per-user, so the $http_cookie bypass is important. Test thoroughly. For most small-to-medium deployments, Redis caching alone is sufficient and the FastCGI cache adds complexity without proportional benefit. Consider it only if you see high PHP-FPM process counts under load.
System-Level Tuning
A few sysctl values help with busy Nextcloud servers:
sh# /etc/sysctl.conf kern.ipc.shmmax=2147483648 kern.ipc.shmall=524288 net.inet.tcp.sendbuf_max=16777216 net.inet.tcp.recvbuf_max=16777216
Apply without reboot:
shsysctl -f /etc/sysctl.conf
Updating Nextcloud
Run updates through the command line for reliability:
sh# Enable maintenance mode sudo -u www php /usr/local/www/nextcloud/occ maintenance:mode --on # Take a snapshot before updating zfs snapshot zroot/var/nextcloud@pre-update-$(date +%Y-%m-%d) sudo -u postgres pg_dump -Fc nextcloud > /var/backups/nextcloud-pre-update-$(date +%Y-%m-%d).dump # Run the updater sudo -u www php /usr/local/www/nextcloud/updater/updater.phar # Disable maintenance mode sudo -u www php /usr/local/www/nextcloud/occ maintenance:mode --off # Run any pending migrations sudo -u www php /usr/local/www/nextcloud/occ upgrade sudo -u www php /usr/local/www/nextcloud/occ db:add-missing-indices sudo -u www php /usr/local/www/nextcloud/occ db:add-missing-columns
Troubleshooting
Check the Nextcloud log for application errors:
shtail -f /var/log/nextcloud.log
Check NGINX error log:
shtail -f /var/log/nginx/error.log
Check PHP-FPM log:
shtail -f /var/log/php-fpm.log
Run the built-in Nextcloud diagnostics:
shsudo -u www php /usr/local/www/nextcloud/occ status sudo -u www php /usr/local/www/nextcloud/occ check
Common issues:
- "Permission denied" errors -- ensure all files under
/usr/local/www/nextcloudand/var/nextcloud/dataare owned bywww:www. - "504 Gateway Timeout" -- increase
fastcgi_read_timeoutin NGINX andmax_execution_timein the PHP-FPM pool. - "Redis connection refused" -- verify the socket path and that
wwwis in theredisgroup. Rungroups wwwto check. - Large file uploads fail -- confirm
client_max_body_size,upload_max_filesize, andpost_max_sizeall match (16G in this guide).
FAQ
Can I use MySQL/MariaDB instead of PostgreSQL?
Yes. Replace the php83-pdo_pgsql and php83-pgsql packages with php83-pdo_mysql and php83-mysqli, install mariadb106-server, and select MySQL in the web installer. PostgreSQL generally performs better with Nextcloud at scale and has better support for the RETURNING clause used in newer Nextcloud versions. The Nextcloud developers recommend PostgreSQL for new installations.
How much RAM does Nextcloud need?
A single-user instance runs fine on 1 GB of RAM. For 10-50 users with active file syncing, allocate 4 GB minimum. Redis, PostgreSQL, and PHP-FPM each want their own share. On a 4 GB server, expect roughly 512 MB for PostgreSQL, 256 MB for Redis, and the rest shared between PHP-FPM workers and the OS.
Can I run Nextcloud in a FreeBSD jail?
Absolutely, and it is the recommended approach for production. Create a jail with VNET networking, install everything inside it, and use nullfs mounts or ZFS datasets for persistent data. Jails give you clean separation from other services and make migration straightforward -- just snapshot the jail's ZFS dataset and send it to a new host.
How do I enable two-factor authentication?
Install the "Two-Factor TOTP Provider" app from the Nextcloud app store. Each user can then enable TOTP (time-based one-time passwords) in their personal security settings. This works with apps like Aegis, andOTP, or any standard TOTP authenticator. For enforcement, admins can require 2FA for all users via Settings > Administration > Security.
How do I sync files from desktop and mobile?
Download the official Nextcloud client for Windows, macOS, Linux, iOS, or Android from nextcloud.com/install. Enter your server URL (https://cloud.example.com), log in, and choose which folders to sync. The desktop client supports selective sync, virtual files (on-demand download), and end-to-end encryption for sensitive folders.
How do I move the data directory to a different ZFS dataset?
Enable maintenance mode, stop PHP-FPM, then move the data:
shsudo -u www php /usr/local/www/nextcloud/occ maintenance:mode --on service php-fpm stop # Create a new dataset and move data zfs create zroot/nextcloud-data mv /var/nextcloud/data/* /nextcloud-data/ chown -R www:www /nextcloud-data # Update config.php # Change 'datadirectory' => '/nextcloud-data', service php-fpm start sudo -u www php /usr/local/www/nextcloud/occ maintenance:mode --off
Is Nextcloud Office usable for real work?
For basic document editing and collaboration, yes. Nextcloud integrates with Collabora Online (LibreOffice-based) or ONLYOFFICE. Both handle Word, Excel, and PowerPoint files. Complex formatting may not render perfectly compared to Microsoft Office, but for internal documents and team collaboration, it works well. For small deployments (1-5 concurrent editors), use the built-in CODE server. Larger teams should run a dedicated Collabora or ONLYOFFICE container.
Summary
You now have a production-ready Nextcloud instance on FreeBSD with:
- NGINX serving the frontend with TLS and security headers.
- PHP-FPM running a dedicated pool with optimized OPcache settings.
- PostgreSQL as the database backend.
- Redis handling distributed caching and file locking.
- System cron running background tasks every 5 minutes.
- Let's Encrypt providing automatic certificate renewal.
- ZFS snapshots and pg_dump for comprehensive backups.
This stack is stable, fast, and maintainable. FreeBSD's quarterly package updates and Nextcloud's built-in updater make ongoing maintenance straightforward. The combination of ZFS snapshots with database dumps means you can always roll back if something goes wrong during an upgrade.
For related guides, see our NGINX production setup, PostgreSQL on FreeBSD, Let's Encrypt on FreeBSD, and ZFS on FreeBSD guide.