1. Introduction#

Setup Guide

Back in 2019, I was searching for an automated way to sync photos from our phones—something seamless that my wife and I could rely on. I started experimenting with AWS and stumbled across OwnCloud, a solution I had heard of but never used. Curious, I spun up a VM and tested it out. It didn’t take long for me to realize that this was exactly what I had been looking for—a self-hosted alternative to the ever-growing ecosystem of cloud storage services like Google Drive, iCloud, and Dropbox.

Now, I get the irony here. I wanted to ditch cloud services… by setting up something in the cloud. But hear me out. I was using AWS Marketplace as a testing environment — running a VM for a few hours or days at minimal cost. (Of course, costs depend on what you’re working on, so always double-check before committing to anything!)

Nextcloud

Shortly after testing OwnCloud, I dug into its history and, as an advocate of open-source solutions, I discovered Nextcloud — a fork of OwnCloud that had evolved into something even more powerful. That led me to an incredibly detailed blog post that became my go-to guide for setting up Nextcloud, not just for myself, but also for my office and even some side clients.

Once I had everything up and running, the next challenge was converting my wife into a believer. Her backup system? A ziplock bag full of USB thumb drives ranging from 4GB to 64GB, each packed with random file dumps. For someone in IT, this was both comical and mildly horrifying. Even funnier? If a random stranger gave her a tech tip, she’d excitedly tell me about the “right” way to do things—completely ignoring the fact that, you know, this is literally my field.

Eventually, though, I won her over. She now fully embraces Nextcloud, using it daily for photo and video backups, document storage, and even text/chat when we’re out.

Which brings me to the reason for this guide. While I’ve referenced that original blog post many times, I wanted to create my own version—both to preserve the information and to reinforce my own understanding of the setup process. Plus, sharing knowledge is what open-source is all about.


2. Prerequisites on installing Nextcloud on TrueNAS in an iocage Jail with Hardened Security#

The goal of this post is to leverage your TrueNAS system that is running FreeBSD v13 so that you can utilize Nextcloud to increase the effectiveness of your network storage.

TrueNAS

Before diving in, here’s what you’ll want on deck. This isn’t just about having the hardware—it’s about setting yourself up to avoid hair-pulling later.

🔧 Core Requirements

  • TrueNAS Core 13.x installed, updated, and behaving.

  • Access via SSH or local terminal.

  • Root privileges or the ability to sudo like a boss.

  • At least 2 CPU cores and 2GB RAM allocated to the jail (the more the merrier, especially for previews and heavy sync usage).

  • A static IP or reserved DHCP lease for your jail. Avoid dynamic IPs like they’re malware. You do not want your HTTPS certs borked because your router decided to have a personality.

  • A registered domain name (or use Dynamic DNS for residential ISPs).

🌟 Strongly Recommended

  • A dedicated ZFS dataset for Nextcloud data (/mnt/tank/nextcloud_data for example).

  • DNS records already pointing your domain to your public IP if you’re planning to use Let’s Encrypt.

  • An external SSD or NVMe for jail storage (performance will thank you).

  • Coffee, tea, Red Bull, yerba mate—whatever you need to fuel the session.

⚠️ NOTE: If you’re behind CGNAT or a carrier-grade firewall, you’ll need to configure port forwarding or a VPN to get external access working.


3. Creating the iocage Jail#

We’re using the CLI for jail creation—because the GUI is cute until something goes sideways. CLI is how you stay in control, know what actually happened, and can repeat the process reliably.

Fire up the following:

iocage create -n nextcloud \
ip4_addr="vnet0|192.168.1.99/24" \
defaultrouter="192.168.1.1" \
vnet="on" \
allow_raw_sockets="1" \
boot="on" \
host_hostname="nextcloud.local" \
dhcp="off" \
bpf="yes" \
-r 13.2-RELEASE

Remember to replace the IP and gateway values with something that makes sense for your environment.

Next, you’ll want to start the newly created jail:

iocage start nextcloud
iocage console nextcloud

This drops you into your shiny new jail, where you’ll do most of the heavy lifting. Welcome to the belly of the beast.


3.1 Setting Up ZFS Datasets and Mounting into the Jail#

Before we start installing packages inside the jail, let’s talk about architecture. Specifically, let’s split the Nextcloud app/config files from your actual user data. This is critical for one very important reason: when—not if—you eventually want to rebuild this jail, your data and Nextcloud configuration should survive the blast radius.

💡 Why This Matters

Most people who build jails the first time think everything lives in one tight little box. It doesn’t. It shouldn’t. When you store your app data inside the jail’s internal filesystem, you’re setting yourself up for a very bad time if the jail breaks. By offloading your data and config folders to external ZFS datasets and mounting them into the jail using nullfs, you gain:

  • Snapshottable and restorable data independent of the jail

  • Config preservation even if the jail is rebuilt

  • Easier upgrades or jail migrations

This is about separation of concerns and resilience. Treat your jail like cattle, not pets. Your data? That’s sacred.

We’re going to create two dedicated datasets:

  • One for Nextcloud’s data folder (all the uploaded files, user content, etc)

  • One for Nextcloud’s config folder (this stores your Nextcloud app settings, installed apps, etc)

Before we start installing packages inside the jail, let’s talk about architecture. Specifically, let’s split the Nextcloud app/config files from your actual user data. This is critical for one very important reason: when—not if—you eventually want to rebuild this jail, your data and Nextcloud configuration should survive the blast radius.

We’re going to create two dedicated datasets:

  • One for Nextcloud’s data folder (all the uploaded files, user content, etc)

  • One for Nextcloud’s config folder (this stores your Nextcloud app settings, installed apps, etc)

🧱 Step 1: Create ZFS Datasets on the Host SSH into your TrueNAS host (not the jail), then:

  • zfs create tank/nextcloud_data
  • zfs create tank/nextcloud_config

Adjust pool name (tank) as needed.

🔐 Step 2: Set Permissions We want the jail’s web server user (www) to own these directories inside the jail, but we’ll start by setting safe ownership on the host:

  • chown -R root:wheel /mnt/tank/nextcloud_data
  • chown -R root:wheel /mnt/tank/nextcloud_config
  • chmod 755 /mnt/tank/nextcloud_data
  • chmod 755 /mnt/tank/nextcloud_config

🧩 Step 3: Mount the Datasets into the Jail

This is where it gets fun.

You’ll add mountpoints using the fstab for the jail:

  • iocage fstab -a nextcloud /mnt/tank/nextcloud_data /usr/local/www/nextcloud/data nullfs rw 0 0

  • iocage fstab -a nextcloud /mnt/tank/nextcloud_config /usr/local/www/nextcloud/config nullfs rw 0 0

This will mount your ZFS datasets into the jail’s filesystem at the right spots for Nextcloud.

Step 4: Confirm It Worked

Start or restart the jail:

  • iocage restart nextcloud

Then console in and verify:

  • iocage console nextcloud
  • ls -ld /usr/local/www/nextcloud/data
  • ls -ld /usr/local/www/nextcloud/config

You should see the expected mountpoints. If not, triple-check your pool names and paths.

🎯 Pro Tip: You won’t actually need the data and config folders until the Nextcloud install later—but mounting them now saves us future pain.


4. Installing Packages and Enabling services#

Let’s begin setting up the critical components that will make your Nextcloud jail more than just a hollow shell. This includes the web server, PHP, database server, caching engine, and a handful of supporting packages to keep the show running smoothly.

Update your package repository and upgrade anything already installed (just to be safe):

pkg update && pkg upgrade -y

Install all the things:

pkg install -y apache24 \
mariadb106-server mariadb106-client \
php82 php82-extensions php82-mysqli php82-gd php82-curl \
php82-json php82-mbstring php82-zip php82-zlib php82-xml \
php82-session php82-pdo php82-pdo_mysql php82-filter \
php82-openssl php82-dom php82-fileinfo php82-iconv \
php82-simplexml php82-ctype php82-pecl-APCu \
redis sudo nano curl git unzip

Now enable the services so they’ll auto-start with the jail:

sysrc apache24_enable=YES
sysrc mysql_enable=YES
sysrc redis_enable=YES

Start each of them:

service apache24 start
service redis start

If you see any errors, now is the time to stop and debug before moving forward. It’s always easier to fix things before configs get more complex.

What Did We Just Install? (aka The Sanity Check)

  • apache24: The tried-and-true web server that’ll serve Nextcloud’s pages and assets to your browser. It’s battle-tested, well-documented, and defaults nicely with PHP on FreeBSD.

  • mariadb106-server and mariadb106-client: A drop-in replacement for MySQL that’s more open and just as capable. It handles all the structured data your Nextcloud instance will use (users, file metadata, app settings, etc).

  • php82 + its essential extensions: Nextcloud is a PHP app. These modules let PHP do the things Nextcloud expects—like talking to databases, managing sessions, uploading files, zipping stuff, processing images, and not throwing fatal errors at you.

  • php82-pecl-APCu: Speeds up PHP by caching opcode (think: reusable snippets of compiled PHP). Great for performance.

  • redis: An in-memory key-value store. Here, it’ll be used to cache locks and file metadata. This helps avoid collisions when multiple clients access the same files.

  • sudo, nano, curl, git, unzip: These are your utility tools. They’re not just helpful—they’re essential when you start fetching files, editing configs, or installing helper scripts like acme.sh.

🧠 Knowledge is power. Knowing why you installed something = superpower.

If you see any errors, now is the time to stop and debug before moving forward. It’s always easier to fix things before configs get more complex.


5. MariaDB Configuration and Creating the Nextcloud Database#

Now that the services are installed and running, let’s dive into setting up the database layer of our Nextcloud stack. This is where all your metadata, user credentials, app configurations, and other structured data will live.

🧠 Why MariaDB? MariaDB is a fork of MySQL that’s fully open source and 100% compatible with Nextcloud. It tends to perform better in some cases and offers improved licensing clarity. We’re rolling with version 10.6, which hits the sweet spot of stability and compatibility.

Step 1: Secure the Root Account First, let’s lock down the default database install:

sh mysql_secure_installation

You’ll be prompted to:

  • Set a root password (Do it!)

  • Remove anonymous users (Yes)

  • Disallow root login remotely (Yes)

  • Remove test database (Yes)

  • Reload privilege tables (Yes)

This script is basically the equivalent of closing your front door, locking it, and putting the chain on.

Step 2: Log into MariaDB

After securing the install, log into the MySQL CLI:

mysql -u root -p

Enter the password you just created.

Step 3: Create the Database and User Run the following SQL commands to create the Nextcloud database and a user account with appropriate permissions. Replace the password with something secure (and store it somewhere safe—you’re going to need it for the Nextcloud web installer).

CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

CREATE USER 'nc_user'@'localhost' IDENTIFIED BY 'your_secure_password_here';

GRANT ALL PRIVILEGES ON nextcloud.* TO 'nc_user'@'localhost';

FLUSH PRIVILEGES;

Then exit the MySQL shell with: EXIT;

🧠 What Did We Just Do? Let’s recap:

  • You created a fresh database called nextcloud using UTF-8 for full character support (important for emojis, international characters, and filenames).

  • You created a dedicated user (nc_user) that Nextcloud will use to connect to that database.

  • You granted only the necessary privileges to that user—standard best practice for database access.

  • We’re now one step closer to actually installing Nextcloud. Next, we’ll configure PHP to play nicely with Apache and enable all the bells and whistles Nextcloud expects.


6. PHP Configuration for Nextcloud#

PHP is the engine that runs Nextcloud under the hood. By default, its config is tuned for general use—not for heavy file uploads, sync-heavy clients, or real-time collaboration. So we’ll tweak a few key values to make sure your instance doesn’t choke the minute you upload a 500MB video of your cat.

🛠 Step 1: Locate and Prepare the php.ini

  • cd /usr/local/etc
  • cp php.ini-production php.ini

Now open it with your favorite editor:

nano php.ini

Search for and modify the following lines (uncomment them if necessary):

date.timezone = America/New_York
memory_limit = 512M
upload_max_filesize = 1024M
post_max_size = 1024M
max_execution_time = 360

💬 Adjust date.timezone to your region. If this is incorrect or blank, Nextcloud will scream at you during the install.

🧠 Why These Settings Matter

  • memory_limit: PHP’s working memory per script. Bump this to avoid memory errors when installing apps or handling large uploads.

  • upload_max_filesize & post_max_size: Controls how big uploads can be—key for large files, videos, etc.

  • max_execution_time: Some Nextcloud operations (like thumbnail generation or big sync batches) take time. Don’t let PHP time out too early.

🧠 Opcode Caching = Performance

PHP gets faster with caching. Let’s configure opcache to improve performance and reduce CPU load:

Create a file:

nano /usr/local/etc/php/ext-20-opcache.ini

Paste in:

[opcache]
opcache.enable=1
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.memory_consumption=128
opcache.save_comments=1
opcache.revalidate_freq=1

Save and exit. These values are tuned for decent performance without overloading modest systems.

🧠 While we’re here: Nextcloud will also benefit from APCu (you installed it earlier with php82-pecl-APCu). It doesn’t require config right now, but we’ll enable it in Nextcloud’s config later.

Next, we’ll configure Apache to process PHP files using mod_php, and point everything to the right Nextcloud directory.


7. Apache Configuration for Nextcloud#

Apache is already installed and running—but by default, it’s not yet equipped to handle .php files or serve up your Nextcloud web interface. We need to get it properly configured so it can:

  • Load the correct PHP module

  • Serve the Nextcloud web directory

  • Apply some basic hardening for security

Let’s walk through it.

Step 1: Enable Required Apache Modules

Apache uses modular configuration. To run PHP, we’ll need mod_php loaded. We also recommend enabling rewrite, headers, and env for good measure:

echo 'LoadModule php_module /usr/local/libexec/apache24/libphp.so' >> /usr/local/etc/apache24/httpd.conf
sed -i '' 's|#LoadModule rewrite_module|LoadModule rewrite_module|' /usr/local/etc/apache24/httpd.conf
sed -i '' 's|#LoadModule headers_module|LoadModule headers_module|' /usr/local/etc/apache24/httpd.conf
sed -i '' 's|#LoadModule env_module|LoadModule env_module|' /usr/local/etc/apache24/httpd.conf

Step 2: Set Directory Index to Prefer index.php

Ensure Apache knows that .php files can be the main entry point:

sed -i '' 's|DirectoryIndex index.html|DirectoryIndex index.php index.html|' /usr/local/etc/apache24/httpd.conf

Step 3: Add PHP Handling to the Configuration

At the bottom of your httpd.conf, add the following block:

<IfModule php_module>
    AddType application/x-httpd-php .php
    AddType application/x-httpd-php-source .phps
</IfModule>

This tells Apache how to handle PHP files so they’re executed, not served as raw code.

Step 4: Create a Virtual Host for Nextcloud

Let’s set up a dedicated virtual host config for Nextcloud so your main httpd.conf doesn’t get messy:

Create a new file:

nano /usr/local/etc/apache24/Includes/nextcloud.conf

Paste in the following template:

<VirtualHost *:80>
    ServerName cloud.yourdomain.com
    DocumentRoot /usr/local/www/nextcloud

    <Directory /usr/local/www/nextcloud>
        Require all granted
        AllowOverride All
        Options FollowSymLinks MultiViews
    </Directory>

    ErrorLog "/var/log/httpd-nextcloud-error.log"
    CustomLog "/var/log/httpd-nextcloud-access.log" common
</VirtualHost>

🔒 We’ll update this later to use HTTPS after setting up SSL. Right now, we’re focusing on the basics.

Step 5: Restart Apache

service apache24 restart

Visit http://<your-jail-ip> or http://cloud.yourdomain.com to confirm Apache is alive and talking.

If you get the classic “It works!” or an Apache test page, you’re good. If not, check the logs in /var/log and re-read each step above.

Once Apache is configured and serving the Nextcloud directory, we can move into downloading and deploying the Nextcloud application itself.

Coffee Break!

🎉 That’s a Wrap for Part 1: Infrastructure & Stack Prep#

So, if you’ve made it this far, I suggest you take a well-earned coffee break.

You’ve accomplished a ton:

  • Configured TrueNAS datasets for data resilience
  • Created a hardened iocage jail
  • Installed and configured all critical services
  • Tuned PHP and Apache to be Nextcloud-ready

Now is a perfect time to pause, breathe, and admire the terminal scrolling behind your eyelids.


☕ What’s Coming in Part 2?#

In Part 2 of this guide, we’ll cover:

  • Downloading and deploying the Nextcloud source
  • Finishing the web-based install
  • Configuring Redis, APCu, and background jobs
  • Setting up SSL with Let’s Encrypt + acme.sh
  • Hardening your setup with best practices
  • Optionally enabling Pretty URLs, theming, and external storage support

📦 Part 2 is where the magic happens—and your Nextcloud instance will finally come to life.


See you on the flip side of the shell prompt.