Nextcloud migration: Linux to FreeBSD (Bastille jail build log)

Overview

Nextcloud on FreeBSD.

This document is the exact build log for bringing up a production-grade Nextcloud instance inside a Bastille jail on TrashProBSD (FreeBSD 14.3-RELEASE) using:

  • nginx
  • php-fpm (PHP 8.2)
  • PostgreSQL 17
  • Redis
  • APCu

This reflects the real environment now serving Nextcloud behind a reverse proxy, with ZFS snapshots for rollback.

Environment

  • Jail: cloud
  • Release: 14.3-RELEASE
  • Jail IP: 192.168.0.230
  • Host interface: bge1
  • vnet MAC: 25:7c:58:9e:9d:40
  • Web stack: nginx + php-fpm on 127.0.0.1:9000
  • Database: PostgreSQL 17
  • Cache/locking: Redis + APCu

0) Jail creation and vnet MAC (Bastille)

Create the Bastille jail with vnet networking and pin a stable MAC for DHCP/ARP consistency.

bastille create -V cloud 14.3-RELEASE 0.0.0.0 bge1

Bastille handled all vnet/epair wiring automatically; the only manual networking change was pinning a static MAC on the jail-side interface.

Config: /usr/local/bastille/jails/cloud/jail.conf

Minimal jail.conf (vnet + MAC + IP)

This is the actual /usr/local/bastille/jails/cloud/jail.conf. Bastille manages the vnet/epair plumbing; the only manual change is the pinned jail-side MAC.

cloud {
  enforce_statfs = 2;
  devfs_ruleset = 13;
  exec.clean;
  exec.consolelog = /var/log/bastille/cloud_console.log;
  exec.start = '/bin/sh /etc/rc';
  exec.stop = '/bin/sh /etc/rc.shutdown';
  host.hostname = cloud;
  mount.devfs;
  mount.fstab = /usr/local/bastille/jails/cloud/fstab;
  path = /usr/local/bastille/jails/cloud/root;
  securelevel = 2;
  osrelease = 14.3-RELEASE;

  allow.raw_sockets = 1;
  allow.sysvipc = 1;

  vnet;
  vnet.interface = e0b_cloud;
  exec.prestart += "jib addm cloud bge1";
  exec.prestart += "ifconfig e0a_cloud description \"vnet0 host interface for Bastille jail cloud\"";
  exec.prestart += "ifconfig e0b_cloud ether 25:7c:58:9e:9d:40";
  exec.poststop += "ifconfig e0a_cloud destroy";
}

Notes:

  • e0a_cloud (host) / e0b_cloud (jail) are Bastille-managed epairs.
  • jib addm cloud bge1 attaches the host-side epair to bge1.
  • The MAC on e0b_cloud is intentionally pinned for DHCP/ARP stability.

1) Packages (nginx + PHP 8.2 + modules)

Install the full PHP 8.2 runtime and extensions required by Nextcloud (image handling, DB, intl, caching).

pkg install -y \
  nginx \
  php82 php82-extensions \
  php82-gd php82-curl php82-mbstring php82-xml php82-zip php82-intl \
  php82-bcmath php82-fileinfo php82-pecl-imagick php82-opcache \
  php82-pdo php82-pdo_pgsql php82-pgsql

Additional PHP modules installed later (Nextcloud feature deps)

These were missing at first and were installed later as Nextcloud’s checks/features complained (LDAP, mail, EXIF, etc.). Keeping this list here prevents the “why is this feature broken” loop later.

pkg install -y \
  php82-ldap \
  php82-imap \
  php82-exif \
  php82-gmp \
  php82-bz2 \
  php82-ftp \
  php82-sockets

service php_fpm restart

2) php-fpm

Enable and start PHP-FPM, which nginx proxies to on 127.0.0.1:9000.

sysrc php_fpm_enable="YES"
service php_fpm start
sockstat -4l | egrep ':9000'

3) nginx

Enable nginx and validate the base configuration before adding the Nextcloud vhost.

sysrc nginx_enable="YES"
service nginx start
nginx -t

4) nginx vhost for Nextcloud

Define the PHP-enabled vhost for Nextcloud; TLS termination is handled by the upstream reverse proxy.

server {
    listen 80 default_server;
    server_name cloud.example.com;

    root /usr/local/www/nextcloud;
    index index.php index.html;

    client_max_body_size 10G;

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

    location ~ \.php(?:$|/) {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass 127.0.0.1:9000;
    }
}

5) PostgreSQL 17 + Redis

Bring up the database and Redis used by Nextcloud for metadata, sessions, and locking.

pkg install -y postgresql17-server postgresql17-client redis
sysrc redis_enable="YES"
service redis start

sysrc postgresql_enable="YES"
service postgresql initdb
service postgresql start
su -m postgres -c "createuser -P nextcloud"
su -m postgres -c "createdb -O nextcloud nextcloud"

6) Nextcloud files + data dir

Install Nextcloud under the web root and place user data outside the document root.

cd /usr/local/www
fetch -o nextcloud.tar.bz2 https://download.nextcloud.com/server/releases/latest.tar.bz2
tar -xjf nextcloud.tar.bz2

mkdir -p /usr/local/www/nextcloud-data
chown -R www:www /usr/local/www/nextcloud /usr/local/www/nextcloud-data
chmod 750 /usr/local/www/nextcloud-data

7) Nextcloud install (occ)

Perform a non-interactive Nextcloud install wired to PostgreSQL and the external data directory.

su -m www -c 'cd /usr/local/www/nextcloud && php occ maintenance:install \
  --database "pgsql" \
  --database-host "127.0.0.1" \
  --database-name "nextcloud" \
  --database-user "nextcloud" \
  --database-pass "DB_PASSWORD_HERE" \
  --admin-user "admin" \
  --admin-pass "ADMIN_PASSWORD_HERE" \
  --data-dir "/usr/local/www/nextcloud-data"'

8) APCu + Redis caching

Enable local memory caching (APCu) and Redis locking to reduce DB contention.

pkg install -y php82-pecl-APCu php82-pecl-redis
cd /usr/local/etc/php
cp -n ext-20-APCu.ini.sample ext-20-APCu.ini
cp -n ext-30-redis.ini.sample ext-30-redis.ini
service php_fpm restart
su -m www -c 'php /usr/local/www/nextcloud/occ config:system:set memcache.local --value="\\OC\\Memcache\\APCu"'
su -m www -c 'php /usr/local/www/nextcloud/occ config:system:set memcache.locking --value="\\OC\\Memcache\\Redis"'
su -m www -c 'php /usr/local/www/nextcloud/occ config:system:set redis host --value="127.0.0.1"'
su -m www -c 'php /usr/local/www/nextcloud/occ config:system:set redis port --value="6379" --type=integer'

9) Cron jobs

Move Nextcloud background jobs to system cron for reliability and performance.

su -m www -c 'php /usr/local/www/nextcloud/occ background:cron'

cat > /etc/cron.d/nextcloud <<'EOF'
*/5 * * * * www /usr/local/bin/php -f /usr/local/www/nextcloud/cron.php
EOF

10) Domains, proxy trust, canonical URLs

Configure trusted domains and reverse-proxy awareness for external access.

su -m www -c 'php /usr/local/www/nextcloud/occ config:system:set trusted_domains 1 --value="192.168.0.230"'
su -m www -c 'php /usr/local/www/nextcloud/occ config:system:set trusted_domains 2 --value="cloud.comtoso.com"'
su -m www -c 'php /usr/local/www/nextcloud/occ config:system:set trusted_domains 3 --value="icloud.comtoso.com"'
su -m www -c 'php /usr/local/www/nextcloud/occ config:system:set trusted_domains 4 --value="nextcloud.comtoso.com"'
su -m www -c 'php /usr/local/www/nextcloud/occ config:system:set trusted_proxies 0 --value="192.168.0.218"'
su -m www -c 'php /usr/local/www/nextcloud/occ config:system:set overwritehost --value="icloud.comtoso.com"'
su -m www -c 'php /usr/local/www/nextcloud/occ config:system:set overwrite.cli.url --value="https://nextcloud.comtoso.com"'
su -m www -c 'php /usr/local/www/nextcloud/occ config:system:set overwriteprotocol --value="https"'

11) ZFS snapshots

Create a rollback point before risky changes or migrations.

TAG="manual-pre-change-$(date +%F-%H%M)"
zfs snapshot -r zroot/bastille/jails/cloud@"$TAG"

Rollback:

bastille stop cloud
zfs rollback -r zroot/bastille/jails/cloud@manual-pre-change-YYYY-MM-DD-HHMM
bastille start cloud

Fixes / tuning applied after first run

These were required to clear Nextcloud warnings and stabilize performance under real load.

PHP memory limits (uploads, previews, large sync jobs)

# /usr/local/etc/php.ini
memory_limit = 1024M
upload_max_filesize = 10G
post_max_size = 10G
max_execution_time = 3600
max_input_time = 3600
service php_fpm restart

OPcache tuning (interned strings exhaustion)

# /usr/local/etc/php/ext-20-opcache.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=20000
opcache.revalidate_freq=60
service php_fpm restart

PHP getenv() / PATH visibility (Nextcloud warning)

Ensure php-fpm inherits a sane PATH so Nextcloud environment checks pass:

# /usr/local/etc/php-fpm.d/www.conf
env[PATH] = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
service php_fpm restart

Optional: Redis persistence hardening

# /usr/local/etc/redis.conf
appendonly yes
appendfsync everysec
service redis restart

State of the Build

At this point nginx + php-fpm serve Nextcloud, PostgreSQL 17 is live, Redis + APCu are active, cron runs, HTTPS proxying works, and ZFS snapshots protect the jail. Next step is cold migration from TrueNAS into this jail.

Nextcloud on FreeBSD.