Hardware Raspberry Pi
OS Raspberry Pi OS
Web server Apache2
Database SQLite
Stack LAMP
Access LAN + port forward
Status Running

Motivation

The goal was straightforward: a private cloud storage server with no third party involved. No Google Drive, no iCloud, no data sitting on someone else's hardware. Nextcloud is the standard open-source answer to this, and a Raspberry Pi is the obvious low-power, always-on platform to run it on.

What was not obvious was how much of the configuration stack you have to own yourself when you go this route. There is no managed service, no installer that handles everything, no support line. Every layer — web server, PHP runtime, database, file permissions, network routing, HTTPS — has to be understood and configured individually. That turned out to be the entire point.


Software Stack

OS
Raspberry Pi OS
Debian-based Linux. Headless install — no desktop environment, terminal only.
Web server
Apache2
Handles all HTTP/HTTPS traffic. mod_rewrite enabled for Nextcloud's URL routing.
Runtime
PHP
Nextcloud is a PHP application. Multiple extensions required — gd, curl, zip, xml, mbstring, sqlite3, intl, and others.
Database
SQLite
Chosen for simplicity on a single-user install. No separate database server process to manage. Tradeoffs documented below.
Application
Nextcloud
Open-source self-hosted cloud platform. File storage, sync clients, web interface.

Installation & Configuration

01

OS and base system

Raspberry Pi OS Lite flashed to the SD card, SSH enabled from the start. System updated fully before touching anything else. Swap size increased to prevent the Pi from locking up under the memory pressure of the PHP runtime and Apache running simultaneously.

02

LAMP stack installation

Apache2, PHP, and all required PHP extensions installed manually via apt. The extension list is longer than it looks — Nextcloud's setup wizard throws warnings for each missing module and won't proceed cleanly until all dependencies are satisfied.

# core PHP extensions required by Nextcloud
sudo apt install apache2 php php-sqlite3 php-gd \
  php-curl php-zip php-xml php-mbstring \
  php-intl php-bz2 php-bcmath libapache2-mod-php
03

Nextcloud download and extraction

Nextcloud archive downloaded and extracted to /var/www/html/nextcloud. File ownership then had to be recursively set to www-data — the user Apache runs as. Skipping this step or getting it wrong produces permission denied errors throughout the application, often with no obvious connection to the root cause.

sudo chown -R www-data:www-data /var/www/html/nextcloud
sudo find /var/www/html/nextcloud/ -type d -exec chmod 750 {} \;
sudo find /var/www/html/nextcloud/ -type f -exec chmod 640 {} \;
04

Apache virtual host and mod_rewrite

A virtual host configuration written for Nextcloud and Apache's mod_rewrite enabled. Apache will start successfully even when these rewrite rules are misconfigured, which makes this failure mode east to overlook. Without this, Nextcloud's internal URL routing breaks and most of the web interface returns 404s. AllowOverride All required in the Apache config for the .htaccess rules to take effect.

05

Database selection — SQLite

At the Nextcloud setup wizard, SQLite was chosen over MariaDB. For a single-user personal install with no concurrent access requirements, SQLite eliminates an entire service (the database daemon) and the associated configuration surface. The Nextcloud community widely discourages SQLite for multi-user installs but it is appropriate here. The tradeoffs are documented in the findings section.

06

Trusted domains — config.php

Nextcloud refuses to serve requests from any domain or IP not explicitly listed in config/config.php under trusted_domains. Accessing the instance from a different IP than the one used during setup — including from outside the local network — throws an "untrusted domain" error and blocks access entirely. Each address the server would be reached from had to be added explicitly.

# config/config.php — trusted_domains entry
'trusted_domains' => array (
  0 => '192.168.x.x',  // LAN IP
  1 => 'your.ddns.hostname',
),
07

PHP upload limits

PHP's default upload limit is 2 MB — essentially useless for a file storage server. The upload_max_filesize and post_max_size directives in php.ini had to be raised manually. Apache also has its own separate request size limit that needs to match.

08

Port forwarding for remote access

To reach the server from outside the local network, port 80 (HTTP) and 443 (HTTPS) forwarded through the router to the Pi's local IP. The Pi's local IP was also assigned as a static DHCP lease to prevent it from changing and silently breaking the port forward.


Challenges & Findings

Hard problem File permissions — the recurring failure mode

The single most common source of failure throughout the entire install was file ownership and permissions. Nextcloud's files must be owned by www-data — the Apache process user — or the application cannot write logs, create sessions, access the data directory, or modify its own config. The error messages this produces are not always obviously connected to permissions; many surface as generic 500 errors or blank pages.

Any time files were extracted, moved, or modified outside of Apache's context, ownership had to be reset with chown -R www-data:www-data or the application broke in a new and occasionally more confusing way than before. This was the most repeated fix throughout the entire installation process.

Hard problem Trusted domain blocking — access from any unexpected address

Nextcloud's trusted domain check is a hard block — it does not degrade gracefully or warn, it simply refuses the connection with a terse error page. The first time the server was accessed from a different device, or from outside the network, this hit immediately.

Every IP address and hostname the server would ever be reached from — local IP, router's external IP, any dynamic DNS hostname — had to be listed in config.php in advance. Forgetting to update this after any network change breaks remote access silently.

Hard problem PHP extensions — no single list that covers everything
Nextcloud's dependency on PHP extensions is extensive and not fully enumerated in one place. The setup wizard surfaces warnings one at a time, turning package installation into a scavenger hunt. The actual list of required and recommended modules — gd, curl, zip, xml, mbstring, intl, bz2, bcmath, sqlite3, imagick — was assembled iteratively by working through each warning in sequence. Installing all of them upfront and restarting Apache before running the wizard saves significant back-and-forth.
Design decision SQLite — appropriate for this use case, with known limits

Nextcloud's documentation and community consistently recommend MariaDB or PostgreSQL over SQLite, and for good reason — SQLite does not handle concurrent writes well, has no connection pooling, and can become a bottleneck under any meaningful load.

For a single-user personal install with no simultaneous access from multiple clients, SQLite is entirely adequate and removes one major configuration surface. The decision was deliberate, not uninformed. If usage patterns change, migrating to MariaDB is possible via Nextcloud's built-in database conversion tool.

Networking Port forwarding — ISP and router variables

Getting external access working required coordinating three things that each have their own failure modes: the router's port forwarding rules, the Pi's static local IP, and the trusted domains list in config.php. A misconfiguration in any one of them produces what looks like the same symptom — the server is unreachable from outside — but the root cause is different each time.

Some ISPs also block inbound connections on ports 80 and 443 at the modem level, which no amount of router configuration can fix. This is worth verifying early rather than after the rest of the stack is working.

Confirmed Running — accessible on LAN and remotely
The instance is up and accessible from both the local network and externally. File sync, web interface, and mobile client all functional. The Pi runs it at low power continuously with acceptable performance for personal use — not fast, but sufficient.

Reflection

This project had nothing to do with electronics or RF — it was a Linux and networking project from start to finish. The value of it was not Nextcloud itself but everything that had to be understood to make it run: how Apache processes requests, how PHP integrates with it, how Linux file ownership works at the process level, how port forwarding interacts with NAT, and how an application like Nextcloud uses its config file as a security boundary.

Every failed attempt — and there were many (more than 50) — was a forced education in a specific layer of the stack. That is what self-hosting on bare hardware without a managed platform actually is. The documentation tells you the happy path. The broken installs teach you everything else.