What You Get

NextCloud is a self-hosted file sync and collaboration platform — an alternative to Dropbox, Google Drive and Microsoft 365 that keeps your data on your own hardware. This guide walks through a production-grade installation on Ubuntu 24.04 LTS with Apache, PHP 8.3, MySQL 8, Redis for caching, and Let's Encrypt for TLS.

Every step is hardened. By the end you have a deployment that scores A+ on the NextCloud security scan.

Prerequisites

Step 1: System Preparation

Update the system and install prerequisites:

sudo apt update && sudo apt upgrade -y
sudo apt install -y unzip curl wget gnupg2 software-properties-common

Set the timezone to UTC:

sudo timedatectl set-timezone UTC

Step 2: Install Apache

sudo apt install -y apache2
sudo systemctl enable apache2
sudo systemctl start apache2

Enable the modules NextCloud needs:

sudo a2enmod rewrite headers env dir mime ssl setenvif
sudo systemctl restart apache2

Step 3: Install PHP 8.3

sudo apt install -y php8.3 php8.3-fpm php8.3-cli php8.3-common \
  php8.3-mysql php8.3-zip php8.3-gd php8.3-mbstring php8.3-curl \
  php8.3-xml php8.3-bcmath php8.3-intl php8.3-imagick \
  php8.3-redis php8.3-apcu php8.3-gmp php8.3-bz2 \
  libmagickcore-6.q16-7-extra

Enable PHP-FPM with Apache:

sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2

Harden PHP

Edit /etc/php/8.3/fpm/php.ini:

memory_limit = 512M
upload_max_filesize = 16G
post_max_size = 16G
max_execution_time = 3600
max_input_time = 3600
output_buffering = Off
date.timezone = UTC
opcache.enable = 1
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 10000
opcache.memory_consumption = 256
opcache.save_comments = 1
opcache.revalidate_freq = 1
expose_php = Off

Restart PHP-FPM:

sudo systemctl restart php8.3-fpm

Step 4: Install MySQL 8

sudo apt install -y mysql-server
sudo systemctl enable mysql
sudo systemctl start mysql

Secure the Installation

sudo mysql_secure_installation

Answer Y to all prompts: set a root password, remove anonymous users, disallow remote root login, remove the test database, and reload privileges.

Create the NextCloud Database

sudo mysql -u root -p
CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'YOUR_STRONG_PASSWORD_HERE';
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Replace YOUR_STRONG_PASSWORD_HERE with a genuinely strong password. Generate one with:

openssl rand -base64 32

Harden MySQL

Edit /etc/mysql/mysql.conf.d/mysqld.cnf and add under [mysqld]:

bind-address = 127.0.0.1
local-infile = 0
skip-symbolic-links = yes
log_error = /var/log/mysql/error.log

Restart MySQL:

sudo systemctl restart mysql

Step 5: Install Redis

Redis provides in-memory caching that significantly improves NextCloud performance.

sudo apt install -y redis-server

Edit /etc/redis/redis.conf:

bind 127.0.0.1 ::1
port 6379
unixsocket /run/redis/redis-server.sock
unixsocketperm 770
maxmemory 256mb
maxmemory-policy allkeys-lru

Add the web server user to the redis group:

sudo usermod -aG redis www-data

Restart Redis:

sudo systemctl restart redis-server
sudo systemctl enable redis-server

Step 6: Download and Install NextCloud

Check the NextCloud installation page for the latest version. At the time of writing:

cd /tmp
wget https://download.nextcloud.com/server/releases/latest.zip
wget https://download.nextcloud.com/server/releases/latest.zip.sha256
sha256sum -c latest.zip.sha256

Extract and move to the web root:

sudo unzip latest.zip -d /var/www/
sudo chown -R www-data:www-data /var/www/nextcloud

Create the data directory outside the web root:

sudo mkdir -p /var/nextcloud-data
sudo chown www-data:www-data /var/nextcloud-data
sudo chmod 750 /var/nextcloud-data

Step 7: Configure Apache Virtual Host

Create /etc/apache2/sites-available/nextcloud.conf:

<VirtualHost *:80>
    ServerName cloud.example.com
    RewriteEngine On
    RewriteRule ^(.*)$ https://%{SERVER_NAME}$1 [R=301,L]
</VirtualHost>

<VirtualHost *:443>
    ServerName cloud.example.com
    DocumentRoot /var/www/nextcloud

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/cloud.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/cloud.example.com/privkey.pem

    <Directory /var/www/nextcloud>
        Require all granted
        AllowOverride All
        Options FollowSymlinks MultiViews

        <IfModule mod_dav.c>
            Dav off
        </IfModule>
    </Directory>

    <IfModule mod_headers.c>
        Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        Header always set X-Content-Type-Options "nosniff"
        Header always set X-Frame-Options "SAMEORIGIN"
        Header always set X-Robots-Tag "noindex, nofollow"
        Header always set X-Permitted-Cross-Domain-Policies "none"
        Header always set Referrer-Policy "no-referrer"
    </IfModule>

    ErrorLog ${APACHE_LOG_DIR}/nextcloud_error.log
    CustomLog ${APACHE_LOG_DIR}/nextcloud_access.log combined
</VirtualHost>

Enable the site and disable the default:

sudo a2ensite nextcloud.conf
sudo a2dissite 000-default.conf
sudo systemctl reload apache2

Step 8: Obtain a TLS Certificate

Install Certbot and obtain a Let's Encrypt certificate:

sudo apt install -y certbot python3-certbot-apache
sudo certbot certonly --apache -d cloud.example.com

Set up automatic renewal:

sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer

Verify the timer is active:

sudo systemctl list-timers | grep certbot

Reload Apache after each renewal. Edit /etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh:

#!/bin/bash
systemctl reload apache2
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh

Step 9: Run the NextCloud Installer

Open https://cloud.example.com in your browser. The installer asks for:

Click Install and wait. This takes a minute or two.

Command-Line Installation (Alternative)

sudo -u www-data php /var/www/nextcloud/occ maintenance:install \
  --database "mysql" \
  --database-name "nextcloud" \
  --database-user "nextcloud" \
  --database-pass "YOUR_STRONG_PASSWORD_HERE" \
  --database-host "localhost" \
  --admin-user "admin" \
  --admin-pass "YOUR_ADMIN_PASSWORD" \
  --data-dir "/var/nextcloud-data"

Step 10: Configure NextCloud

Edit /var/www/nextcloud/config/config.php and add or update:

'trusted_domains' =>
  array (
    0 => 'cloud.example.com',
  ),
'overwrite.cli.url' => 'https://cloud.example.com',
'overwriteprotocol' => 'https',
'default_phone_region' => 'GB',
'memcache.local' => '\OC\Memcache\APCu',
'memcache.distributed' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => array(
    'host' => '/run/redis/redis-server.sock',
    'port' => 0,
    'timeout' => 0.0,
),
'maintenance_window_start' => 1,

The maintenance_window_start tells NextCloud to run background jobs between 01:00 and 05:00 UTC.

Set Up the Cron Job

NextCloud needs a background job to run every 5 minutes:

sudo crontab -u www-data -e

Add:

*/5 * * * * php -f /var/www/nextcloud/cron.php

Then set the background job mode to cron in the admin panel: Administration Settings > Basic Settings > Background Jobs > Cron.

Step 11: Harden File Permissions

sudo find /var/www/nextcloud/ -type f -print0 | xargs -0 chmod 0640
sudo find /var/www/nextcloud/ -type d -print0 | xargs -0 chmod 0750
sudo chown -R root:www-data /var/www/nextcloud/
sudo chown -R www-data:www-data /var/www/nextcloud/config
sudo chown -R www-data:www-data /var/www/nextcloud/data
sudo chown -R www-data:www-data /var/www/nextcloud/themes
sudo chown -R www-data:www-data /var/www/nextcloud/apps
sudo chown root:www-data /var/www/nextcloud/.htaccess
sudo chmod 0644 /var/www/nextcloud/.htaccess

Step 12: Enable Brute-Force Protection

NextCloud has built-in brute-force protection. Verify it is enabled:

sudo -u www-data php /var/www/nextcloud/occ config:system:set auth.bruteforce.protection.enabled --value=true --type=boolean

Pair this with Fail2Ban for network-level blocking. Create /etc/fail2ban/filter.d/nextcloud.conf:

[Definition]
failregex = ^.*Login failed: .* \(Remote IP: <HOST>\).*$
            ^.*Brute force attempt detected: .* \(Remote IP: <HOST>\).*$
ignoreregex =
datepattern = %%Y-%%m-%%dT%%H:%%M:%%S

Add to /etc/fail2ban/jail.local:

[nextcloud]
enabled  = true
port     = http,https
filter   = nextcloud
logpath  = /var/nextcloud-data/nextcloud.log
maxretry = 5
bantime  = 1h

Restart Fail2Ban:

sudo systemctl restart fail2ban

Step 13: Final Security Checks

Run the Security Scan

Visit https://scan.nextcloud.com and enter your NextCloud URL. Aim for an A+ rating.

Check the Admin Overview

In NextCloud, go to Administration Settings > Overview. Address every warning listed. Common ones:

Disable Unnecessary Apps

Remove apps you do not use to reduce attack surface:

sudo -u www-data php /var/www/nextcloud/occ app:list
sudo -u www-data php /var/www/nextcloud/occ app:disable <app-name>

Step 14: Backups

None of this matters without backups. Set up a daily backup of:

sudo -u www-data php /var/www/nextcloud/occ maintenance:mode --on
sudo mysqldump --single-transaction -u nextcloud -p nextcloud > /var/backups/nextcloud-db-$(date +%F).sql
sudo rsync -a /var/nextcloud-data/ /var/backups/nextcloud-data/
sudo rsync -a /var/www/nextcloud/config/ /var/backups/nextcloud-config/
sudo -u www-data php /var/www/nextcloud/occ maintenance:mode --off

Put the database dump and rsync commands in a cron job running daily at a quiet hour. Store backups off-server — a local backup on the same machine is not a backup.

Summary

A hardened NextCloud deployment means: data directory outside the web root, MySQL bound to localhost, Redis on a Unix socket, strict file permissions, HSTS and security headers in Apache, Let's Encrypt for TLS with automatic renewal, Fail2Ban watching NextCloud's log, and daily off-server backups. Every component has a secure default; the work is in making sure none of them is left open.