Nextcloud is a self-hosted platform for file sync and sharing, calendars, contacts and collaboration, which keeps your data on a server you control. In this tutorial you will install Nextcloud on Ubuntu 24.04 with Nginx, PHP-FPM 8.3, MariaDB and Redis, run the installation from the command line with occ, secure it with Let's Encrypt, and configure caching and background jobs so the admin overview shows no warnings.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS with at least 2 GB of RAM (4 GB for more than a handful of users), for example a CubePath VPS.
- Enough disk space for your files. The data directory in this guide is
/srv/nextcloud-data; mount a separate volume there if you expect a lot of data. - A non-root user with
sudoprivileges. - A subdomain with an A record pointing to the server, referred to as
cloud.your_domain. - Ports 80 and 443 open to the internet.
Step 1 - Installing Nginx, PHP-FPM and the required modules
Install Nginx, PHP-FPM and the PHP modules Nextcloud requires or recommends, plus Redis for file locking and bzip2 to unpack the release archive:
sudo apt update
sudo apt install nginx php-fpm php-cli php-mysql php-gd php-curl php-mbstring php-intl php-xml php-zip php-bcmath php-gmp php-imagick php-apcu php-redis redis-server bzip2
Ubuntu 24.04 installs PHP 8.3, which current Nextcloud releases support. Check the services:
systemctl is-active nginx php8.3-fpm redis-server
active
active
active
Open the firewall:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Step 2 - Tuning PHP for Nextcloud
Nextcloud needs more memory and a larger OPcache string buffer than the defaults. Create an override file for PHP-FPM:
sudo nano /etc/php/8.3/fpm/conf.d/90-nextcloud.ini
memory_limit = 512M
upload_max_filesize = 1G
post_max_size = 1G
max_execution_time = 360
output_buffering = Off
opcache.memory_consumption = 256
opcache.interned_strings_buffer = 16
The occ command line tool runs under PHP CLI, which disables APCu by default. Enable it for the CLI too, otherwise occ fails once APCu is configured as the cache in Step 7:
echo 'apc.enable_cli=1' | sudo tee /etc/php/8.3/cli/conf.d/90-nextcloud-apcu.ini
PHP-FPM also clears environment variables by default, and Nextcloud warns that it cannot read PATH. Open the pool configuration:
sudo nano /etc/php/8.3/fpm/pool.d/www.conf
Find the commented env[PATH] line and remove the leading ; so it reads:
env[PATH] = /usr/local/bin:/usr/bin:/bin
Restart PHP-FPM and confirm the memory limit:
sudo systemctl restart php8.3-fpm
sudo php-fpm8.3 -i | grep ^memory_limit
memory_limit => 512M => 512M
Step 3 - Creating the database
Install MariaDB:
sudo apt install mariadb-server
Open the MariaDB shell as root:
sudo mariadb
Create the database and user. Replace your_db_password with a long random password:
CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'your_db_password';
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Test the new account:
mariadb -u nextcloud -p -e "SELECT CURRENT_USER();"
+---------------------+
| CURRENT_USER() |
+---------------------+
| nextcloud@localhost |
+---------------------+
Step 4 - Downloading Nextcloud
Download the latest release and its checksum from the official server:
cd /tmp
wget https://download.nextcloud.com/server/releases/latest.tar.bz2
wget https://download.nextcloud.com/server/releases/latest.tar.bz2.sha256
Verify the archive before extracting it:
sha256sum -c latest.tar.bz2.sha256
latest.tar.bz2: OK
Extract it to /var/www, which creates /var/www/nextcloud, and create the data directory outside the web root so user files can never be served directly by Nginx:
sudo tar -xjf latest.tar.bz2 -C /var/www
sudo mkdir -p /srv/nextcloud-data
sudo chown -R www-data:www-data /var/www/nextcloud /srv/nextcloud-data
sudo chmod 750 /srv/nextcloud-data
Step 5 - Installing Nextcloud with occ
occ is Nextcloud's command line administration tool. It must always run as the web server user, www-data. Run the installation, replacing the database password, and choose an admin user name and a strong admin password:
cd /var/www/nextcloud
sudo -u www-data php occ maintenance:install \
--database mysql \
--database-name nextcloud \
--database-user nextcloud \
--database-pass 'your_db_password' \
--admin-user 'your_admin' \
--admin-pass 'your_admin_password' \
--data-dir /srv/nextcloud-data
Nextcloud was successfully installed
By default Nextcloud only accepts requests for localhost. Add your domain as a trusted domain and set the URL used when occ and background jobs generate links:
sudo -u www-data php occ config:system:set trusted_domains 1 --value=cloud.your_domain
sudo -u www-data php occ config:system:set overwrite.cli.url --value=https://cloud.your_domain
Check the installation status:
sudo -u www-data php occ status
- installed: true
- version: 32.0.0.13
- versionstring: 32.0.0
- edition:
- maintenance: false
- needsDbUpgrade: false
- productname: Nextcloud
- extendedSupport: false
Your version numbers will match the release you downloaded.
Step 6 - Getting a certificate and configuring Nginx
Request the certificate first, so the Nextcloud server block can point to it. The Certbot Nginx plugin answers the validation challenge through the default site that is still active:
sudo apt install certbot python3-certbot-nginx
sudo certbot certonly --nginx -d cloud.your_domain
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/cloud.your_domain/fullchain.pem
Key is saved at: /etc/letsencrypt/live/cloud.your_domain/privkey.pem
Now create the Nextcloud site. This configuration is based on the one in the Nextcloud administration manual, adapted to Nginx 1.24 and PHP-FPM 8.3 on Ubuntu 24.04:
sudo nano /etc/nginx/sites-available/nextcloud.conf
upstream php-handler {
server unix:/run/php/php8.3-fpm.sock;
}
# Mark versioned static assets as immutable
map $arg_v $asset_immutable {
"" "";
default ", immutable";
}
server {
listen 80;
listen [::]:80;
server_name cloud.your_domain;
server_tokens off;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name cloud.your_domain;
root /var/www/nextcloud;
ssl_certificate /etc/letsencrypt/live/cloud.your_domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cloud.your_domain/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
server_tokens off;
client_max_body_size 1G;
client_body_timeout 300s;
fastcgi_buffers 64 4K;
client_body_buffer_size 512k;
add_header Strict-Transport-Security "max-age=15768000" always;
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "noindex, nofollow" always;
fastcgi_hide_header X-Powered-By;
include mime.types;
types {
text/javascript mjs;
}
index index.php index.html /index.php$request_uri;
location = / {
if ( $http_user_agent ~ ^DavClnt ) {
return 302 /remote.php/webdav/$is_args$args;
}
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ^~ /.well-known {
location = /.well-known/carddav { return 301 /remote.php/dav/; }
location = /.well-known/caldav { return 301 /remote.php/dav/; }
location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
location /.well-known/pki-validation { try_files $uri $uri/ =404; }
return 301 /index.php$request_uri;
}
# Hide internal directories and files
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }
location ~ \.php(?:$|/) {
rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode(_arm64)?\/proxy) /index.php$request_uri;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
set $path_info $fastcgi_path_info;
try_files $fastcgi_script_name =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $path_info;
fastcgi_param HTTPS on;
fastcgi_param modHeadersAvailable true;
fastcgi_param front_controller_active true;
fastcgi_pass php-handler;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
fastcgi_max_temp_file_size 0;
fastcgi_read_timeout 360s;
}
location ~ \.(?:css|js|mjs|svg|gif|ico|jpg|png|webp|wasm|tflite|map|ogg|flac)$ {
try_files $uri /index.php$request_uri;
add_header Cache-Control "public, max-age=15778463$asset_immutable";
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "noindex, nofollow" always;
access_log off;
}
location ~ \.(otf|woff2?)$ {
try_files $uri /index.php$request_uri;
expires 7d;
access_log off;
}
location /remote {
return 301 /remote.php$request_uri;
}
location / {
try_files $uri $uri/ /index.php$request_uri;
}
}
The security headers are repeated in the static assets block because Nginx does not inherit add_header directives into a location that defines its own. client_max_body_size matches the PHP upload limit from Step 2.
Enable the site, disable the default one and test the configuration:
sudo ln -s /etc/nginx/sites-available/nextcloud.conf /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Reload Nginx and check that Nextcloud answers:
sudo systemctl reload nginx
curl -s https://cloud.your_domain/status.php
{"installed":true,"maintenance":false,"needsDbUpgrade":false,"version":"32.0.0.13","versionstring":"32.0.0","edition":"","productname":"Nextcloud","extendedSupport":false}
Certbot renews the certificate automatically with its systemd timer and reloads Nginx through the plugin. Test it with sudo certbot renew --dry-run.
Step 7 - Configuring caching and file locking
Nextcloud uses APCu as a fast local cache and Redis for transactional file locking, which prevents corruption when several clients sync the same files. Configure both with occ:
cd /var/www/nextcloud
sudo -u www-data php occ config:system:set memcache.local --value='\OC\Memcache\APCu'
sudo -u www-data php occ config:system:set memcache.locking --value='\OC\Memcache\Redis'
sudo -u www-data php occ config:system:set redis host --value=localhost
sudo -u www-data php occ config:system:set redis port --value=6379 --type=integer
Verify that the settings were stored:
sudo -u www-data php occ config:system:get memcache.locking
\OC\Memcache\Redis
Step 8 - Running background jobs with cron
Nextcloud runs maintenance tasks (cleanup, previews, notifications) as background jobs. The default AJAX mode only runs them when someone uses the web interface. Switch to system cron:
sudo -u www-data php occ background:cron
Open the crontab of the www-data user:
sudo crontab -u www-data -e
Add this line to run the job every five minutes:
*/5 * * * * php -f /var/www/nextcloud/cron.php
Also set a maintenance window, so heavy jobs run at night (the value is the hour in UTC), and the default phone region used to validate phone numbers in user profiles:
sudo -u www-data php occ config:system:set maintenance_window_start --type=integer --value=1
sudo -u www-data php occ config:system:set default_phone_region --value=ES
Replace ES with your ISO 3166-1 country code. After a few minutes, Administration settings > Basic settings shows that the last background job ran seconds ago.
Step 9 - Checking the setup warnings
Log in at https://cloud.your_domain with the admin account from Step 5 and open Administration settings > Overview. Nextcloud runs a series of security and setup checks there. Two common database items can be fixed immediately:
cd /var/www/nextcloud
sudo -u www-data php occ db:add-missing-indices
sudo -u www-data php occ maintenance:repair --include-expensive
Reload the overview page. The remaining warnings, if any, include a link to the relevant section of the documentation. Configure an email server under Administration settings > Basic settings so users receive share notifications and password resets.
Troubleshooting
"Access through untrusted domain". The host name in the browser is not in trusted_domains. List the current values with sudo -u www-data php occ config:system:get trusted_domains and add the missing one with a new index.
occ prints "Memcache \OC\Memcache\APCu not available". APCu is disabled for the CLI. Check that /etc/php/8.3/cli/conf.d/90-nextcloud-apcu.ini contains apc.enable_cli=1.
Large uploads fail with 413 or time out. Raise client_max_body_size in Nginx and upload_max_filesize and post_max_size in the PHP override together, then reload both services. The desktop and mobile clients upload in chunks and are not affected by these limits.
502 Bad Gateway. PHP-FPM is not running or the socket path is wrong. Check sudo systemctl status php8.3-fpm and that /run/php/php8.3-fpm.sock exists.
Conclusion
Nextcloud is now running on Ubuntu 24.04 with Nginx, PHP-FPM 8.3, MariaDB and Redis, with HTTPS, file locking, local caching and system cron. As next steps, install the desktop and mobile clients to sync files, enable apps such as Calendar and Contacts from the Apps page, and set up regular backups of /var/www/nextcloud/config, /srv/nextcloud-data and the database (with sudo -u www-data php occ maintenance:mode --on while the backup runs).
