Installing Nextcloud on your own server
From an empty server to an overview page without warnings: web server, PHP modules, database, data directory permissions, trusted_domains, upload limits and background jobs via cron.
Unpacking Nextcloud takes seconds. The part that costs time comes afterwards: on a fresh installation, the overview page under Administration settings almost always shows a list of yellow and red warnings, uploads stop at 2 MB, and opening the site by IP address ends with "You are accessing the site from an untrusted domain.". This article walks the whole way through and slows down at exactly the points where most guides stop.
Decide up front: PHP version, database, storage location
Nextcloud depends on the PHP version more than most other server software. The current series 33 and 34 require at least PHP 8.2, series 32 still runs from PHP 8.1 upwards. That means your distribution decides whether the stock packages are enough:
| System | PHP from the distribution | Database from the distribution | Verdict |
|---|---|---|---|
| Debian 13 (trixie) | 8.4 | MariaDB 11.8 | fine without a third-party repository |
| Debian 12 (bookworm) | 8.2 | MariaDB 10.11 | fine, but right at the lower edge |
| Ubuntu 24.04 LTS | 8.3 | MariaDB 10.11, MySQL 8.0 | fine without a third-party repository |
| Ubuntu 22.04 LTS | 8.1 | MariaDB 10.6, MySQL 8.0 | too old for Nextcloud 33 and 34 |
| Debian 11 (bullseye) | 7.4 | MariaDB 10.5 | not an option |
Debian 11 is the harshest case, and it still tends to surface late, because the package installation runs through without a single error. The php metapackage pulls in PHP 7.4 there, and the current Nextcloud version dies on the first browser request with HTTP 500 and the message "This version of Nextcloud requires at least PHP 8.2". Debian 11 has left regular support anyway, so it is the wrong basis for a new installation. If it really has to be Debian 11, add the Sury repository first and install explicitly versioned packages, so php8.2-fpm, php8.2-cli, php8.2-mysql and so on, instead of the unversioned metapackages.
On Ubuntu 22.04 you run into the same wall the moment you install the current Nextcloud version. Either you deliberately stay on series 32, or you pull PHP from the well known PPA:
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:ondrej/php
sudo apt-get update
sudo apt-get install -y php8.3-fpm php8.3-cli php8.3-mysql
Two more points that you decide at the beginning and can only change with pain later: Debian ships no mysql-server package at all, MariaDB is the given choice there. And the data directory does not belong under /var/www/nextcloud/data, it belongs outside the web server root, for example in /var/nextcloud-data. The default path is dangerous for one reason only: a broken web server configuration will otherwise hand out every user file. Nextcloud does warn about that with "Your data directory and files are probably accessible from the internet", but only once the mistake already exists.
Setting up the web server, PHP and the database
We use nginx with PHP-FPM. If you prefer Apache with mod_php, that route is described in Apache, PHP and MySQL on Debian, and the PHP topics further down apply unchanged. A basic nginx installation is covered in installing nginx.
sudo apt-get update
sudo apt-get install -y nginx mariadb-server
sudo apt-get install -y php-fpm php-cli php-mysql php-gd php-curl php-mbstring php-intl php-gmp php-bcmath php-xml php-zip php-imagick php-apcu
This list is deliberately longer than the bare minimum. Nextcloud needs bcmath and gmp for passwordless login, intl for correct sorting of accented and special characters, imagick for preview images, apcu for the local cache. If one of the mandatory modules is missing, the setup wizard will not let you continue at all, and the page then lists the missing modules by name.
Afterwards, check what is actually loaded:
php -v
php -m
The second common stumbling block: there are two separate PHP configurations, one for the command line and one for FPM. php --ini shows you the command line one, the web server one lives in /etc/php/<version>/fpm/php.ini. Changes to the wrong file have no effect, and in practice that costs more time than anything else.
Now the database. Harden MariaDB first, see securing MariaDB and MySQL, then:
sudo mariadb -e "CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
sudo mariadb -e "CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'HierEinLangesPasswort';"
sudo mariadb -e "GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'localhost';"
sudo mariadb -e "FLUSH PRIVILEGES;"
The utf8mb4 in the first command is not a detail. Create the database with utf8 and Nextcloud will later report "MySQL is used as database but does not support 4-byte characters", and converting a running instance is considerably more unpleasant than getting the CREATE DATABASE right at the start. If the database user cannot log in, fixing Access denied for user helps.
Unpacking and setting permissions
sudo apt-get install -y wget unzip
wget https://download.nextcloud.com/server/releases/latest.zip
sudo unzip -q latest.zip -d /var/www
sudo mkdir -p /var/nextcloud-data
sudo chown -R www-data:www-data /var/www/nextcloud
sudo chown -R www-data:www-data /var/nextcloud-data
sudo chmod 750 /var/nextcloud-data
Permissions are where corners get cut most often. Three error messages and what causes them:
- "Cannot write into config directory":
/var/www/nextcloud/configis not owned by the web server user. On Debian and Ubuntu that iswww-data, on AlmaLinux and Rocky it isapacheornginxinstead. A blindly copiedchown www-dataachieves nothing on the Red Hat family. - "Can't create or write into the data directory": the path does not exist, it is not an absolute path, or a parent directory cannot be traversed by
www-data. - "Your data directory is readable by other users": permissions are too wide.
chmod 750on the data directory is enough.
Resist the temptation to solve the problem with chmod -R 777. Nextcloud answers that with exactly the warning you wanted to get rid of, and along the way you have made every file readable for every local user.
The nginx configuration
Nextcloud needs more than a standard PHP block, among other things rewrites for service discovery under /.well-known/ and blocks on internal directories. The following is the shortened version of the official template and works as it is:
upstream php-handler {
server unix:/run/php/php8.3-fpm.sock;
}
server {
listen 80;
server_name cloud.example.com;
root /var/www/nextcloud;
client_max_body_size 10G;
client_body_timeout 300s;
fastcgi_buffers 64 4K;
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;
index index.php index.html /index.php$request_uri;
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; }
return 301 /index.php$request_uri;
}
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\/.+) /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 front_controller_active true;
fastcgi_pass php-handler;
fastcgi_request_buffering off;
fastcgi_max_temp_file_size 0;
}
location ~ \.(?:css|js|mjs|svg|gif|ico|jpg|png|webp|wasm|map|woff2)$ {
try_files $uri /index.php$request_uri;
expires 6M;
access_log off;
}
location / {
try_files $uri $uri/ /index.php$request_uri;
}
}
You have to adapt the path to the FPM socket to your PHP version. On Debian 13 it is called php8.4-fpm.sock, on Debian 12 php8.2-fpm.sock, on Ubuntu 24.04 php8.3-fpm.sock and on Ubuntu 22.04 php8.1-fpm.sock. If the name does not match, you get a 502 Bad Gateway. The actual name is shown by:
systemctl enable --now php8.4-fpm
ls /run/php/
The first line belongs there, because the socket file only appears once the service starts. Before that, /run/php/ is either empty or does not exist at all, and ls answers with No such file or directory. On a normal server the package starts the service during installation, but after a rebuild or inside a container that is not guaranteed. Adapt the version number in the service name to your installation. After that, run nginx -t and reload.
Setup, HTTPS and trusted_domains
You can click through the setup in the browser or do it on the command line right away. The second option is reproducible and can go straight into a script:
cd /var/www/nextcloud
sudo -u www-data php occ maintenance:install --database "mysql" --database-name "nextcloud" --database-user "nextcloud" --database-pass "HierEinLangesPasswort" --admin-user "admin" --admin-pass "EinAnderesLangesPasswort" --data-dir "/var/nextcloud-data"
Two traps here. First, the command has to run from inside the Nextcloud directory, otherwise PHP aborts with a fatal error. Second, occ must never run as root, otherwise you get "Console has to be executed with the user that owns the file config/config.php", and in the worst case freshly created files end up owned by the wrong user afterwards.
Now HTTPS. Without a certificate the mobile apps refuse to connect and Nextcloud warns you in the overview:
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d cloud.example.com
For several subdomains a wildcard certificate is worth it. Afterwards, add the header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; to the TLS server block, otherwise the notice "The Strict-Transport-Security HTTP header is not configured to at least 15552000 seconds" stays where it is.
The classic one at the end: you open the site under a different name than the one used during installation and see nothing but "You are accessing the site from an untrusted domain." Nextcloud accepts host names only if they are listed in trusted_domains. To add one without editing the file by hand:
sudo -u www-data php occ config:system:set trusted_domains 1 --value=cloud.example.com
sudo -u www-data php occ config:system:get trusted_domains
The index starts counting at 0, and 0 is usually taken already. Use the same index twice and you overwrite the existing entry, which may lock you out of your own instance. If that happens: config/config.php is a perfectly ordinary PHP file, so you can correct the entry there in an editor. Also set overwrite.cli.url to the final HTTPS address, otherwise the background jobs generate links with the wrong host name.
Upload size and memory limit
The PHP defaults are too tight for Nextcloud. upload_max_filesize is typically set to 2M, memory_limit to 128M. Nextcloud recommends at least 512M of memory and otherwise warns with "The PHP memory limit is below the recommended value of 512MB".
There are three places involved here, and touching just one of them is not enough:
- The FPM configuration in
/etc/php/<version>/fpm/php.ini:memory_limit = 512M,upload_max_filesize = 10G,post_max_size = 10G,max_execution_time = 3600. Then restart FPM, reloading nginx is not enough. - The file
.user.iniin the Nextcloud directory: Nextcloud ships its own values, and because.user.iniapplies per directory, it wins against the globalphp.ini. That is precisely where troubleshooting goes wrong again and again. Adjust the values there as well. PHP caches this file, by default for five minutes, so your change takes effect with a delay. - The nginx directive
client_max_body_size: if it is missing or too small, the upload fails with "413 Request Entity Too Large" before PHP is even asked.
To check what actually arrives at the end, use the Administration settings page, which shows the limit that is really in effect. On the command line:
grep -E '^(memory_limit|upload_max_filesize|post_max_size)' /etc/php/8.4/fpm/php.ini
Query the FPM file explicitly, not the command line. A php -r "echo ini_get('memory_limit');" reads the CLI SAPI, and on every distribution we checked that reports -1, meaning unlimited. Anyone who trusts that assumes the value is sufficient and still runs into memory errors later, because the FPM file still says memory_limit = 128M. What really arrives in the browser is shown by php-fpm8.4 -i or by a briefly placed info.php with phpinfo() that you delete again immediately afterwards.
If the kernel kills the process while large files are being uploaded, you are simply out of memory. In that case setting up swap helps as a stopgap, more RAM is the better answer.
Switching background jobs to cron
After the installation Nextcloud runs in AJAX mode: background jobs only run while somebody has the interface open. That is why full text search, cleanup work and notifications seem to never happen on lightly used instances. Switch to real cron, Nextcloud expects a run every five minutes. The basics are covered in setting up a cron job on Linux.
sudo crontab -u www-data -e
Enter this there:
*/5 * * * * php -f /var/www/nextcloud/cron.php
Then tell Nextcloud about the change of mode:
cd /var/www/nextcloud
sudo -u www-data php occ background:cron
If you prefer to work without a cron daemon, use a systemd service with a timer. The unit nextcloudcron.service calls /usr/bin/php -f /var/www/nextcloud/cron.php as user www-data, and the matching timer sets OnBootSec=5min and OnUnitActiveSec=5min.
If the warning "Last background job execution ran X hours ago. Something seems wrong" stays anyway, check in this order: is the job running as the correct user? Does the path really exist? And most importantly: can cron.php run at all with the command line PHP configuration, or is a module missing there that was only installed for FPM? The most honest test is calling it by hand, because that way you see every error message in plain text:
sudo -u www-data php -f /var/www/nextcloud/cron.php
Working through the warnings in the overview
The list under Administration settings and Overview is not decoration, every line has a concrete reason. The most common ones and how to fix them:
- "No memory cache has been configured": APCu is installed but not registered.
occ config:system:set memcache.local --value='\OC\Memcache\APCu'. So thatoccand the cron runs benefit from it too, also setapc.enable_cli=1in/etc/php/<version>/mods-available/apcu.ini. - "Transactional file locking is disabled": install Redis (
apt-get install -y redis-server php-redis) and setmemcache.lockingto\OC\Memcache\Redis. Optional on single user instances, no longer optional as soon as several users sync at the same time. - "Your web server is not properly set up to resolve /.well-known/caldav": the rewrites in the
location ^~ /.well-knownblock are missing. You can test this directly withcurl -I https://cloud.example.com/.well-known/caldav, a 301 to/remote.php/dav/is expected. - "Your installation has no default phone region set":
occ config:system:set default_phone_region --value="AT", orDEfor Germany. The value is a country code according to ISO 3166-1. - "Server has no maintenance window start time configured":
occ config:system:set maintenance_window_start --type=integer --value=1. The value is the start hour in UTC, so expensive daily jobs run at night instead of in the middle of business hours. - "The database is missing some indexes":
occ db:add-missing-indices, together withocc db:add-missing-columnsandocc db:add-missing-primary-keys. These commands are slow on large instances, but harmless. - "PHP does not seem to be setup properly to query system environment variables": in the FPM pool file
/etc/php/<version>/fpm/pool.d/www.conf, uncomment the lineenv[PATH] = /usr/local/bin:/usr/bin:/binand restart FPM. - "Module php-imagick in this instance has no SVG support": this is not a Nextcloud bug, it is a missing delegate library in ImageMagick. If you do not need SVG previews, you can simply leave the notice standing.
How to tell that it really works
Four checks that are meaningful when taken together:
cd /var/www/nextcloud
sudo -u www-data php occ status
sudo -u www-data php occ check
sudo -u www-data php occ config:app:get core lastcron
occ status has to report installed: true and the expected version, occ check must not produce any output. The third command returns a Unix timestamp. Convert it: it must not be older than five minutes, and only then is your cron really working.
From the outside:
curl -s https://cloud.example.com/status.php
The answer is a JSON object with "installed":true, "maintenance":false and the version number. If HTML comes back instead, one of your location rules matches too broadly. If you get a redirect to the login page, everything is fine, but you have hit the wrong URL.
Finally, the practical test that no status page can replace: upload a file of several gigabytes through the web interface and then sync it with the desktop client. Only then does it become clear whether client_max_body_size, post_max_size, timeouts and the free disk space fit together. If everything suddenly grinds to a halt, take a look at full disks, because Nextcloud creates previews and versions that grow noticeably.
When things go wrong
Nextcloud logs to /var/nextcloud-data/nextcloud.log, so into your data directory, not to /var/log. That is the first file you should look at, not the nginx log. For readable output:
sudo -u www-data php occ log:watch
If the instance is stuck in maintenance mode after a failed update, occ maintenance:mode --off brings it back. If the interface is no longer reachable at all, set 'maintenance' => false directly in config/config.php.
Before you start tinkering with the database or the configuration, back up both. A directory backup on its own is not enough, Nextcloud is worthless without the matching database:
sudo -u www-data php occ maintenance:mode --on
sudo mariadb-dump --single-transaction nextcloud > /root/nextcloud-db.sql
sudo -u www-data php occ maintenance:mode --off
And one piece of general advice: finish building the server before you make it publicly reachable. A firewall, a hardened SSH access and the points from the checklist for new root servers belong before the first login, not after it. A Nextcloud instance with a default password is found within hours.
Frequently asked questions
Which PHP version do I need for Nextcloud?
Why do I get "You are accessing the site from an untrusted domain"?
Why do uploads still fail after raising the php.ini values?
Why are my background jobs not running?
What permissions does the data directory need?
How do I get rid of the missing memory cache warning?
Can I run Nextcloud with PostgreSQL as well?
2026 KernelHost GmbH. All rights reserved. This guide is protected by copyright. Republishing it on other websites, in whole, in part or in edited form, is not permitted without our written consent. Quoting with a source credit and a link is expressly welcome.

