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
- A fresh Ubuntu 24.04 LTS server with root or sudo access
- A domain name pointing to the server's public IP (e.g.
cloud.example.com) - Ports 80 and 443 open in your firewall
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:
- Admin username and password — choose a strong password; this is the super-admin account
- Data folder — set to
/var/nextcloud-data - Database type — MySQL/MariaDB
- Database user —
nextcloud - Database password — the password you set in Step 4
- Database name —
nextcloud - Database host —
localhost
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:
- PHP memory limit — should be 512M or higher (set in Step 3)
- OPcache settings — should match the values in Step 3
- HSTS header — should be set in the Apache virtual host (Step 7)
- No missing indices — run
sudo -u www-data php /var/www/nextcloud/occ db:add-missing-indices - Cron not running — verify the crontab entry from Step 10
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:
- The NextCloud data directory:
/var/nextcloud-data - The NextCloud configuration:
/var/www/nextcloud/config - The MySQL database
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.