A self-hosted private cloud server running on a Raspberry Pi — full LAMP stack, manual configuration from scratch, SQLite database, and remote access over the home network. Everything was configured by hand. Nothing came easy.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.