Goals

Operating a local Nextcloud server is actually quite simple and perfectly adequate for local or private use. Once this part is complete, a complete Nextcloud server can be accessed on the local network. In summary, the following steps are necessary to achieve the goal of this first part:

NEW: For the very impatient I have a console only section. There are only commands, no explanations.

This simplest expansion stage of the first part is NOT suitable for public operation!

Last updated:

  • 17.01.2026: Nextcloud 32 tested and control
  • 23.12.2025: Upgrade to FreeBSD 15. PostgreSQL 17. Rewrite to a pure FreeBSD jail installation
  • 05.02.2024: Console section added
  • 19.01.2024: Major overhaul. External directories, bug fixes, PostgreSQL 15
  • 05.08.2023: Update to FreeBSD 13.2 / PHP 8.2 and various adjustments
  • 26.03.2023: Initial document

Basic requirements

In general, such services should be set up in a separate environment. This makes not only the security but also the maintenance much easier. Jails can be backed up relatively easily and only the packages that are absolutely necessary are installed.

Begrifflichkeiten

  • USERNAME = SSH Benutzeraccount (root Login ist nicht direkt möglich) mit SU Rechten
  • NEXTCLOUDIP = IP Adresse des lokalen NEXTCLOUD Servers
  • NEXTCLOUDHOSTNAME = Hostname des lokalen NEXTCLOUD Servers
  • NEXTCLOUDDB = Name der Nextcloud Datenbank (z.B. nextcloud_db )
  • NEXTCLOUDDBUSER = Nextcloud Datenbank Benutzer (z.B. nextcloud_user )
  • NEXTCLOUDDBPASS = Kennwort des Nextcloud Datenbank Benutzers (z.B. N3XtCloud )

Diagram

The setup, including all optional possibilities, looks like this:

                  ┌────────────────────────┐
                  │ FreeBSD                │
                  │ ┌─────────────────────┐│
                  │ │ jails/nc            ││
LAN: 0.0.0.0:443 ─┼─┼─► nginx/php         ││
                  │ │   │                 ││
                  │ │   └─► jail/pgsql    ││
                  │ │         postgresql  ││
                  │ └─────────────────────┘│
                  └────────────────────────┘

Create jail

A separate jail is required if further web applications are to be built on Nginx/PHP.
Here we use web as the jail name.

Set up jail

Either use bastile console git to start a console in the created jail, or log in via SSH (if activated) with ssh USERNAME@IP or ssh USERNAME@HOSTNAME to then gain root rights with su.

Customise package source

Package sources should be customised, see separate article.

Install packages & activate services

Now update the package source with pkg update and install the required packages: pkg install -y nextcloud-php84 nextcloud-appointments-php84 nextcloud-calendar-php84 nextcloud-contacts-php84 nextcloud-deck-php84 nextcloud-forms-php84 nextcloud-groupfolders-php84 nextcloud-end_to_end_encryption-php84 nextcloud-mail-php84 nextcloud-news-php84 nextcloud-notes-php84 nextcloud-talk-php84 nextcloud-tasks-php84 nextcloud-twofactor_admin-php84 nextcloud-twofactor_webauthn-php84 postgresql17-client php84-pdo_pgsql php84-pgsql php84-pecl-redis php84-pecl-imagick redis Enable services so that they start automatically when the jail is started: service redis enable.

Databse

PostgreSQL User

Please ensure that NEXTCLOUDDBNAME, NEXTCLOUDDBUSER and NEXTCLOUDDBPASS are replaced with your specifications.
If you have your own PostgreSQL jail, the database and database user must be created there..

su -m postgres -c "createuser -s NEXTCLOUDDBUSER --pwprompt"
su -m postgres -c "createdb -O NEXTCLOUDDBUSER -E Unicode -T template1 NEXTCLOUDDBNAME"

Enable Redis

Redis is also a database that can be used optionally for caching and session management.
This reduces the load on the main database (and thus also on the backup) and speeds up operation, especially when a few more users are created.

The Redis user must first be added to the www group with pw groupmod redis -m www.

cp /usr/local/etc/redis.conf.sample /usr/local/etc/redis.conf

sed -i '' 's/port 6379/port 0/' /mnt/conf/redis.conf
sed -i '' 's/# unixsocket \/run\/redis.sock/unixsocket \/var\/run\/redis\/redis.sock/' /usr/local/etc/redis.conf
sed -i '' 's/# unixsocketperm 700/unixsocketperm 770/' /usr/local/etc/redis.conf

PHP

In /usr/local/etc/php.ini, there are several options that need to be adjusted for Nextcloud.
The clearest way to do this is with a separate ini file.

cat > /usr/local/etc/php/99-nextcloud.ini <<'EOF'
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=1
opcache.save_comments=1
redis.session.locking_enabled = 1
redis.session.lock_retries = -1
redis.session.lock_wait_time = 10000
EOF
sed -i '' 's/application\/javascript                           js;/application\/javascript                           js mjs;/' /usr/local/etc/nginx/mime.types

NGINX

NGINX together with PHP forms the web server and thus also the foundation for Nextcloud.

Extend configuration

  • /usr/local/etc/nginx/conf.d/nextcloud.conf = Nextcloud configuration
cat > /usr/local/etc/nginx/conf.d/nextcloud.conf <<'EOF'
server {
 listen 80;
 listen 443 ssl;
# listen [::]:80;
# listen [::]:443 ssl;
 http2  on;
 server_tokens off;
 root /usr/local/www/nextcloud/;
 client_max_body_size 10G;
 client_body_timeout 300s;
 fastcgi_buffers 64 4K;
 client_body_buffer_size 512k;

 ssl_certificate /usr/local/etc/ssl/nextcloud/cert.pem;
 ssl_certificate_key /usr/local/etc/ssl/nextcloud/key.pem;
 ssl_dhparam /usr/local/etc/nginx/dhparam.pem;

 gzip on;
 gzip_vary on;
 gzip_comp_level 4;
 gzip_min_length 256;
 gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
 gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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;
 fastcgi_hide_header X-Powered-By;

 index index.php index.html /index.php$request_uri;

 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;
 }

 location ^~ /.well-known {
  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;
 }

 location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
 location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }
 location ~ \.php(?:$|/) {
  rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/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 $request_filename;
  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_max_temp_file_size 0;
 }

 location ~ \.(?:css|js|svg|gif|png|jpg|ico|wasm|tflite|map)$ {
  try_files $uri /index.php$request_uri;
  add_header Cache-Control "public, max-age=15778463";
  access_log off;    
  location ~ \.wasm$ {
  default_type application/wasm;
  }
 }

 location ~ \.woff2?$ {
  try_files $uri /index.php$request_uri;
  expires 7d;
  access_log off;
 }

 location /remote {
  return 301 /remote.php$request_uri;
 }

 location / {
  try_files $uri $uri/ /index.php$request_uri;
 }
}
EOF

HTTPS certificates

Encryption should also be used internally.

mkdir -p /usr/local/etc/ssl/nextcloud
openssl req -x509 -nodes -days 3652 -sha512 -subj "/C=DE/CN=nextcloud" -newkey rsa:2048 -keyout "/usr/local/etc/ssl/nextcloud/key.pem" -out "/usr/local/etc/ssl/nextcloud/cert.pem"

Nextcloud

Prepare configuration

Even without these adjustments, the Nextcloud installation could theoretically start, but it would encounter an error after entering the database information. Redis would then remain unused. Therefore, the supplied configuration file is expanded with a few additional points beforehand.

cat > /usr/local/www/nextcloud/config/config.php <<'EOF'
<?php

/** This is the bare minimum configuration for the bundled installer
  * to function properly.
  */

$CONFIG = array (

// Set defaultphone region (https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements)
 'default_phone_region' => 'DE',

/** The FreeBSD package separates apps into bundled apps and user-
  * installed apps. If this 'apps_paths' array is missing from
  * your config, your Nextcloud installation is broken
  */
  'apps_paths' =>
  array (
    0 =>
    array (
     'path' => '/usr/local/www/nextcloud/apps',
     'url' => '/apps',
     'writable' => true,
    ),
    1 =>
    array (
     'path' => '/usr/local/www/nextcloud/apps-pkg',
     'url' => '/apps-pkg',
     'writable' => false,
    ),
  ),

// Log-files belong in the appropriate location
 'logfile' => '/var/log/nextcloud/nextcloud.log',

// Enable user caching when option is enabled
  'memcache.local' => '\OC\Memcache\APCu',

// Redis caching and session handling
 'filelocking.enabled' => true,
 'memcache.local' => '\OC\Memcache\Redis',
 'memcache.locking' => '\OC\Memcache\Redis',
 'distributed' => '\\OC\\Memcache\\Redis',
 'redis' => 
  array(
   'host' => '/var/run/redis/redis.sock',
   'port' => 0,
   'timeout' => 0.0,
  ),
 );
EOF

Permissions

The Nextcloud Installer requires write access to the configuration file.

chown www:www /usr/local/www/nextcloud/config/config.php
chmod 775 /usr/local/www/nextcloud/config/config.php

Logging

The logging directory is created with install -d -o www -g www /var/log/nextcloud/ and the correct permissions.

Start services

This brings us to the end of the preparations
and all services can now be started with service redis restart && service php_fpm restart && service nginx restart.

Finnish installation

The Nextcloud Installer can also be accessed via a web page, but it is quicker to use occ.
Only database-host, database-name and database-user need to be adjusted, and the installation will be completed automatically.
su -m www -c "php /usr/local/www/nextcloud/occ maintenance:install --database-host 'localhost' --database 'pgsql' --database-name 'nextcloud_db' --database-user 'nextcloud_user' --database-pass 'N3XtCloud' --admin-user 'admin' --admin-pass 'N3XtCloud'"

Finally, a few last refinements that are sent directly afterwards via shell.
A few final database settings are made and the trusted_domain is set to ‘all’ to make setup easier, especially at the beginning.
This can be changed again later (if you want to use your own real domain).

su -m www -c "php /usr/local/www/nextcloud/occ app:enable admin_audit appointments calendar contacts deck encryption end_to_end_encryption files_external forms groupfolders mail news notes spreed suspicious_login tasks twofactor_admin twofactor_nextcloud_notification twofactor_totp twofactor_webauthn"
su -m www -c "php /usr/local/www/nextcloud/occ app:disable app_api"
su -m www -c "php /usr/local/www/nextcloud/occ db:add-missing-primary-keys"
su -m www -c "php /usr/local/www/nextcloud/occ db:add-missing-indices"
su -m www -c "php /usr/local/www/nextcloud/occ db:add-missing-columns"
su -m www -c "php /usr/local/www/nextcloud/occ db:convert-filecache-bigint --no-interaction"
su -m www -c "php /usr/local/www/nextcloud/occ maintenance:mimetype:update-db"
su -m www -c "php /usr/local/www/nextcloud/occ maintenance:repair --include-expensive"
su -m www -c "php /usr/local/www/nextcloud/occ config:system:set trusted_domains 1 --value=*"
su -m www -c "php /usr/local/www/nextcloud/occ config:system:set maintenance_window_start --value=17 --type=integer"

Cron

Nextcloud has its own task management system, which relies on tasks being triggered regularly in the system.

mkdir /usr/local/etc/cron.d/
echo "SHELL=/bin/sh" > /usr/local/etc/cron.d/nextcloud
echo "PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin" >> /usr/local/etc/cron.d/nextcloud
echo "*/5 * * * * www /usr/local/bin/php /usr/local/www/nextcloud/cron.php" >> /usr/local/etc/cron.d/nextcloud

Console

Voilá

To ensure that the whole thing can also be used on mobile phones and other devices while on the move, this will be explained in part 2.

Instructions and lots more information can be found here.