Nextcloud 34 on Ubuntu 26.04 LTS is the smoothest manual install this project has had in years. Ubuntu 26.04 ships PHP 8.5 in its default repos, which is exactly the version Nextcloud 34 recommends, so for the first time in a while you don't need a PPA or any third-party packages. This guide builds the full production stack: Apache with PHP-FPM, MariaDB, Redis with APCu, Let's Encrypt for HTTPS and the data directory kept outside the webroot.
I've run this exact stack for a personal-plus-small-team instance since around Nextcloud 24, and on a VPS you control I still pick the manual route over AIO.
When something breaks at midnight you want to know every moving part, and a hand-built stack means you do. That said, if you'd rather skip the next forty minutes, the one-click Nextcloud template (picked during ordering, deploys in seconds on Ubuntu 22.04) hands you a running instance, and this guide still works as the map of what's inside it.
What you need before starting
A fresh Ubuntu 26.04 LTS server with at least 2 GB of RAM (4 GB is comfortable, and it's what I run for two dozen users and around 400 GB of files), a sudo user and a domain with an A record pointing at the server. If the box is brand new, do the basics first, the way I lay out in the Ubuntu 26.04 LTS VPS setup guide: updates, SSH keys, a firewall that allows 22, 80 and 443. Any Ubuntu VPS with those ports open will do.
Step 1: install Apache, PHP-FPM 8.5 and MariaDB
Everything comes from the default repos:
sudo apt update && sudo apt upgrade -y
sudo apt install -y apache2 mariadb-server redis-server bzip2 \
php8.5-fpm php8.5-mysql php8.5-gd php8.5-curl php8.5-xml \
php8.5-zip php8.5-intl php8.5-mbstring php8.5-bcmath php8.5-gmp \
php-apcu php-redis php-imagick
I use PHP-FPM instead of mod_php because it isolates PHP from Apache workers and makes memory tuning sane. Wire the two together and enable the modules Nextcloud's .htaccess relies on:
sudo a2enmod proxy_fcgi setenvif rewrite headers env dir mime
sudo a2enconf php8.5-fpm
sudo systemctl restart apache2 php8.5-fpm
Verify with php-fpm8.5 -v, which should print PHP 8.5.x. While you're in PHP land, open /etc/php/8.5/fpm/php.ini and set memory_limit = 512M. Anything lower and the admin page will later greet you with "The PHP memory limit is below the recommended value of 512MB". On my 4 GB box I also cap pm.max_children at 12 in /etc/php/8.5/fpm/pool.d/www.conf, which keeps a photo-sync burst from eating the whole machine.
Why Apache and not nginx? Nextcloud ships its own .htaccess and the system requirements page treats Apache as the default environment. nginx works fine, but you maintain the rewrite config yourself forever. I'd rather not.
Step 2: create the MariaDB database and user
Harden the fresh MariaDB first, then create the database. Nextcloud wants utf8mb4 and the READ COMMITTED isolation level:
sudo mariadb-secure-installation
sudo mariadb
Inside the MariaDB shell:
CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'choose-a-real-password';
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'localhost';
FLUSH PRIVILEGES;
EXIT;
For the isolation level, add transaction-isolation = READ-COMMITTED under the [mysqld] section of /etc/mysql/mariadb.conf.d/50-server.cnf and run sudo systemctl restart mariadb. Skipping this works until two clients sync the same folder at once and you start seeing deadlock warnings in the log.
MariaDB over PostgreSQL for a small instance, in my view. Postgres 14 through 18 is fully supported and the Postgres crowd has a point about query planning, but MariaDB is what the docs default to and what every guide assumes, so when something goes wrong you'll find the answer faster.
Step 3: download Nextcloud 34 and verify the archive
Grab the current release and its checksum straight from the official mirror:
cd /tmp
wget https://download.nextcloud.com/server/releases/nextcloud-34.0.3.tar.bz2
wget https://download.nextcloud.com/server/releases/nextcloud-34.0.3.tar.bz2.sha256
sha256sum -c nextcloud-34.0.3.tar.bz2.sha256
You want to see nextcloud-34.0.3.tar.bz2: OK before going any further. Then unpack into the webroot and create a data directory outside it:
sudo tar -xjf nextcloud-34.0.3.tar.bz2 -C /var/www/
sudo chown -R www-data:www-data /var/www/nextcloud
sudo mkdir -p /srv/nextcloud-data
sudo chown www-data:www-data /srv/nextcloud-data
Keeping data in /srv/nextcloud-data instead of the default /var/www/nextcloud/data means a webserver misconfiguration can never serve your files raw, and an upgrade that replaces the application directory can't touch them. It's a two-line decision now and a painful migration later, so take it now.
Step 4: configure the Apache virtual host
Create /etc/apache2/sites-available/nextcloud.conf:
<VirtualHost *:80>
ServerName cloud.example.com
DocumentRoot /var/www/nextcloud
<Directory /var/www/nextcloud/>
Require all granted
AllowOverride All
Options FollowSymLinks MultiViews
</Directory>
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php8.5-fpm.sock|fcgi://localhost"
</FilesMatch>
</VirtualHost>
Enable it and drop the default site:
sudo a2ensite nextcloud
sudo a2dissite 000-default
sudo apachectl configtest
sudo systemctl reload apache2
configtest must say Syntax OK. If the site later answers with a 502 or a blank page, the socket path in SetHandler is the usual culprit; check the actual path with ls /run/php/.
Step 5: run the installer with occ
You can open the domain in a browser and use the install wizard, but I prefer occ maintenance:install. It's scriptable, it can't be raced by a stranger finding your fresh install first and it sets the data directory in the same breath. The source installation docs describe both paths.
cd /var/www/nextcloud
sudo -u www-data php occ maintenance:install \
--database mysql --database-name nextcloud \
--database-user nextcloud --database-pass 'choose-a-real-password' \
--admin-user admin --admin-pass 'a-long-admin-password' \
--data-dir /srv/nextcloud-data
Then tell Nextcloud which hostname it may be reached on:
sudo -u www-data php occ config:system:set trusted_domains 1 --value=cloud.example.com
sudo -u www-data php occ config:system:set overwrite.cli.url --value=https://cloud.example.com
Skip that first command and the browser will show "Access through untrusted domain" the moment you visit by name. It's a common enough trip-up that I wrote a separate piece on fixing the untrusted domain error. Confirm the install with sudo -u www-data php occ status, which should report installed: true and version 34.0.3.
Step 6: add Redis and APCu caching to config.php
Out of the box Nextcloud runs with no memory cache and database-backed file locking, and it feels like it. The docs put it plainly: a combination of APCu and Redis is the best choice for new installations. Add this block to /var/www/nextcloud/config/config.php, inside the existing array:
'memcache.local' => '\OC\Memcache\APCu',
'memcache.distributed' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => [
'host' => '127.0.0.1',
'port' => 6379,
],
Redis handles transactional file locking, and past one user I consider it non-negotiable. Reload the page after saving; a typo in config.php produces an immediate internal server error, so if the page dies you know exactly which file to reopen. There's more squeeze available here (a Unix socket for Redis, OPcache sizing, preview settings), which I cover in the Nextcloud performance tuning guide.
Step 7: switch background jobs to system cron
The default AJAX mode only runs jobs while someone has the web UI open, which on a small instance means jobs mostly don't run. Give www-data a real crontab:
sudo crontab -u www-data -e
Add one line:
*/5 * * * * php -f /var/www/nextcloud/cron.php
Then flip the mode to Cron under Administration settings, Basic settings. There are a couple of gotchas around php-cli and APCu that bite people here, and I've collected them in the Nextcloud cron and background jobs guide.
Step 8: enable HTTPS with Let's Encrypt
With DNS already pointing at the server, certbot does the whole dance, including the redirect from port 80:
sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache -d cloud.example.com
sudo certbot renew --dry-run
The dry run confirms auto-renewal works, so you never think about the certificate again. The same flow, plus the first-login checklist, is in our Nextcloud initial setup and HTTPS guide if you want screenshots.
Step 9: clear the admin overview warnings
Log in, open Administration settings, then Overview. This page is the closest thing Nextcloud has to a health check and I read it before touching anything on my own instance. A fresh 34.0.3 install on this stack typically shows one or two items. If it mentions missing database indices, run:
sudo -u www-data php occ db:add-missing-indices
I also set a maintenance window so the heavier nightly jobs run at 01:00 instead of during the workday:
sudo -u www-data php occ config:system:set maintenance_window_start --type=integer --value=1
When the Overview page shows all green checks, you're done. Mine has stayed green through every 34.x point release, and on the one morning it wasn't, that page told me about a stale cron before any user noticed. Upload something from the mobile app, watch it land in /srv/nextcloud-data, and start moving your files in.

