Finally, I’ll now cover the installation of Nextcloud on Debian!

At this point, is expected that you already had:

I’m currently using Debian 12, but these instructions may be equally valid for other versions of Debian and Ubuntu.

Download Nextcloud 27

To download Nextcloud 27, change into the /tmp folder, to keep things clean, and use wget to download the archive:

# cd /tmp
# wget

With the archive downloaded, now unzip it. We’ll also attempt to install unzip, in case we don’t have it installed already. The -d switch specifies the target directory, so the archive will be extracted to /var/www/nextcloud:

# sudo apt install unzip
# sudo unzip -d /var/www

Now we’ll have to change the owner of /var/www/nextcloud so Nginx can write to it:

# sudo chown www-data:www-data /var/www/nextcloud -R

Install PHP Required Modules

Beyond the ones we installed previously, Nextcloud requires some additional PHP modules. To install them, run the following command:

# sudo apt install php8.2-curl php8.2-gd php8.2-mbstring php8.2-xml php8.2-zip php8.2-bz2 php8.2-intl php8.2-ldap php8.2-imap php8.2-bcmath php8.2-gmp php8.2-imagick

Configure PHP

To meet the requirements of Nextcloud we need to make some changes in PHP configuration. The first one is changing the memory_limit. This setting is in php.ini, we can edit it running:

# sudo nano /etc/php/8.2/fpm/php.ini

Search for memory_limit and change it to 512M.

Once we’re editing php.ini you can take the opportunity to set upload_max_filesize and post_max_size according to your preferences.

Another thing is that as we’re using php-fpm, system environment variables like PATH, TMP or others are not automatically populated. A PHP call like getenv('PATH'); can therefore return an empty result. So we need to manually configure the environment variables in www.conf. To edit this file run:

# sudo nano /etc/php/8.2/fpm/pool.d/www.conf

Usually, near the bottom of the file, we will find some or all of the environment variables already, but commented out like this:

;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp

Just uncomment the ones that refer to PATH and TMP, like this:

env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp

With this changes done, restart the PHP service:

# sudo systemctl restart php8.2-fpm

Create PostgreSQL User and Database

To create a user and database for Nextcloud we first need to login to PostgreSQL prompt:

# sudo -i -u postgres psql

Then create a username (choose a username and password according to your preferences):


Create a database:


Set the user you created as the owner of the database:

ALTER DATABASE nextcloud OWNER TO username;

Grant the user all the privileges over the database:


To quit the PostgreSQL prompt, run:


Configure Nginx

We’ll now create a Nginx server block to nextcloud. I’m naming it nextcloud but you can name it whatever you like:

# sudo nano /etc/nginx/sites-available/nextcloud

Copy the following content to the file and change the server_name from to the domain address you want use:

server {
    listen 80;
    listen [::]:80;

Save and close the file when you’re done.

Once created, to enable the server block we need to create a symbolic link of it into /etc/nginx/sites-enabled/ using the following command:

# sudo ln -s /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/

Make sure that there are no syntax errors in the Nginx files:

# sudo nginx -t

If there aren’t any issues, restart Nginx to enable the changes:

# sudo systemctl reload nginx

Enable SSL

We can use certbot to obtain a Let’s Encrypt SSL certificate to our Nextcloud. If you didn’t already, install it as its Nginx package running the following command:

# sudo apt install certbot python3-certbot-nginx

Then to use Certbot, run:

# sudo certbot --nginx

At our first time running Certbot, we’ll be prompted to enter an email address, agree to the terms of service and authorise or not EEF (the entity that mantains Certbot) to send emails to us.

We’ll then be presented with a list of all domains enabled on our server:

Which names would you like to activate HTTPS for?
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel):

Select the appropriate number for the domain you want to obtain a certificate (for our example type 1) and press ENTER. After doing so, Certbot will communicate with the Let’s Encrypt server, then run a challenge to verify that we control the domain we’re requesting a certificate for.

If that’s successful, Certbot will finish with a message like below. Look at IMPORTANT NOTES and save the locations of both the certificate and key.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

 - Congratulations! Your certificate and chain have been saved at:
   Your key file has been saved at:

Now, edit the Nextcloud’s server block:

# sudo nano /etc/nginx/sites-available/nextcloud

And replace its content with the following code. Don’t forget to change the server_name from to the domain address you want and update the locations to the SSL certificate and key:

upstream php-handler {
    server unix:/var/run/php/php8.2-fpm.sock;

# Set the `immutable` cache control options only for assets with a cache busting `v` argument
map $arg_v $asset_immutable {
    "" "";
    default "immutable";

server {
    listen 80;
    listen [::]:80;

    # Prevent nginx HTTP Server Detection
    server_tokens off;

    # Enforce HTTPS
    return 301 https://$server_name$request_uri;

server {
    listen 443      ssl http2;
    listen [::]:443 ssl http2;

    # Path to the root of your installation
    root /var/www/nextcloud;

    ssl_certificate /etc/letsencrypt/live/;
    ssl_certificate_key /etc/letsencrypt/live/;

    # Prevent nginx HTTP Server Detection
    server_tokens off;

    # HSTS settings
    # WARNING: Only add the preload option once you read about
    # the consequences in This option
    # will add the domain to a hardcoded list that is shipped
    # in all major browsers and getting removed from this list
    # could take several months.
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;

    # set max upload size and increase upload timeout:
    client_max_body_size 512M;
    client_body_timeout 300s;
    fastcgi_buffers 64 4K;

    # Enable gzip but do not remove ETag headers
    gzip on;
    gzip_vary on;
    gzip_comp_level 4;
    gzip_min_length 256;
    gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
    gzip_types application/atom+xml text/javascript application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/ application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

    # Pagespeed is not supported by Nextcloud, so if your server is built
    # with the `ngx_pagespeed` module, uncomment this line to disable it.
    #pagespeed off;

    # The settings allows you to optimize the HTTP2 bandwitdth.
    # See
    # for tunning hints
    client_body_buffer_size 512k;

    # HTTP response headers borrowed from Nextcloud `.htaccess`
    add_header Referrer-Policy                      "no-referrer"       always;
    add_header X-Content-Type-Options               "nosniff"           always;
    add_header X-Download-Options                   "noopen"            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;
    add_header X-XSS-Protection                     "1; mode=block"     always;

    # Opt out of Google Chrome tracking everything you do.
    # Note: if you’re reading this, stop using Google Chrome.
    # It is ridiculous for web servers to essentially have to ask
    # “please do not violate the privacy of the people who are viewing
    # this site” with every request.
    # For more info, see:
    add_header Permissions-Policy                   "interest-cohort=()"    always;

    # Remove X-Powered-By, which is an information leak
    fastcgi_hide_header X-Powered-By;

    # Add .mjs as a file extension for javascript
    # Either include it in the default mime.types list
    # or include you can include that list explicitly and add the file extension
    # only for Nextcloud like below:
    include mime.types;
    types {
        text/javascript js mjs;

    # Specify how to handle directories -- specifying `/index.php$request_uri`
    # here as the fallback means that Nginx always exhibits the desired behaviour
    # when a client requests a path that corresponds to a directory that exists
    # on the server. In particular, if that directory contains an index.php file,
    # that file is correctly served; if it doesn't, then the request is passed to
    # the front-end controller. This consistent behaviour means that we don't need
    # to specify custom rules for certain paths (e.g. images and other assets,
    # `/updater`, `/ocm-provider`, `/ocs-provider`), and thus
    # `try_files $uri $uri/ /index.php$request_uri`
    # always provides the desired behaviour.
    index index.php index.html /index.php$request_uri;

    # Rule borrowed from `.htaccess` to handle Microsoft DAV clients
    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;

    # Make a regex exception for `/.well-known` so that clients can still
    # access it despite the existence of the regex rule
    # `location ~ /(\.|autotest|...)` which would otherwise handle requests
    # for `/.well-known`.
    location ^~ /.well-known {
        # The rules in this block are an adaptation of the rules
        # in `.htaccess` that concern `/.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; }

        # Let Nextcloud's API for `/.well-known` URIs handle all other
        # requests by passing them to the front-end controller.
        return 301 /index.php$request_uri;

    # Rules borrowed from `.htaccess` to hide certain paths from clients
    location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)  { return 404; }
    location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console)                { return 404; }

    # Ensure this block, which passes PHP files to the PHP process, is above the blocks
    # which handle static assets (as seen below). If this block is not declared first,
    # then Nginx will encounter an infinite rewriting loop when it prepends `/index.php`
    # to the URI, resulting in a HTTP 500 error response.
    location ~ \.php(?:$|/) {
        # Required for legacy support
        rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/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;         # Avoid sending the security headers twice
        fastcgi_param front_controller_active true;     # Enable pretty urls
        fastcgi_pass php-handler;

        fastcgi_intercept_errors on;
        fastcgi_request_buffering off;

        fastcgi_max_temp_file_size 0;

    location ~ \.(?:css|js|svg|gif|png|jpg|ico|wasm|tflite|map)$ {
        try_files $uri /index.php$request_uri;
        add_header Cache-Control "public, max-age=15778463, $asset_immutable";
        access_log off;     # Optional: Don't log access to assets

        location ~ \.wasm$ {
            default_type application/wasm;

    location ~ \.woff2?$ {
        try_files $uri /index.php$request_uri;
        expires 7d;         # Cache-Control policy borrowed from `.htaccess`
        access_log off;     # Optional: Don't log access to assets

    # Rule borrowed from `.htaccess`
    location /remote {
        return 301 /remote.php$request_uri;

    location / {
        try_files $uri $uri/ /index.php$request_uri;

Make sure that there are no syntax errors in the Nginx files:

# sudo nginx -t

If there aren’t any issues, restart Nginx to enable the changes:

# sudo systemctl reload nginx

Complete Installation

Now, to complete Nextcloud installation, in a browser visit the domain address you chosed and you will be presented with a form to fill.

On this form on “Create an admin account” we should chose a username and password for our admin account. And on “Configure Database” we should fill it with the username, password and database name defined above.

After filling the form just click on Finish setup and wait for the installation to complete. At the end we’ll be redirected to our dashboard.

And that’s it! Our Nextcloud installation is ready for use.

Additional configurations

Following we’ll look at some additional configurations that despite being optional, are advisable.

Memory Caching

Enabling memory caching can significantly improve the perfomance of your Nextcloud. The recommend solutions to implement should be based on the size and propose of our Nextcloud. For a small instance with a single server the recommended configuration is APCu for local memcache and Redis for everything else:1

  • APCu: an in-memory key-value store for PHP;
  • Redis: an excellent modern memcache to use for distributed caching, and as a key-value store for Transactional File Locking.

To install APCu, go back to your terminal and run the following commands:

# sudo apt install php8.2-apcu
APCu is disabled by default on CLI which could cause issues with Nextcloud’s cron jobs. Make sure to set apc.enable_cli to 1 on /etc/php/8.2/cli/php.ini or append --define apc.enable_cli=1 to the cron job command.

And to install Redis, run:

# sudo apt install redis-server php8.2-redis

Then restart Nginx:

# sudo systemctl reload nginx

Now we need to configure Nextcloud to use both APCu and Redis. To do so let’s edit the Nextcloud configuration file:

# sudo nano /var/www/nextcloud/config/config.php

And add the following lines just bellow 'installed' => true,:

'memcache.local' => '\OC\Memcache\APCu',
'memcache.locking' => '\OC\Memcache\Redis',
'memcache.distributed' => '\OC\Memcache\Redis',
'redis' => [
    'host' => 'localhost',
    'port' => 6379,

Background jobs

A system like Nextcloud sometimes requires tasks to be done on a regular basis without the need for user interaction or hindering Nextcloud performance. For that purpose, we can define background jobs. These jobs are typically referred to as cron jobs.

Cron jobs are commands or shell-based scripts that are scheduled to run periodically at fixed times, dates, or intervals. cron.php is a Nextcloud internal process that runs such background jobs on demand.

We can schedule cron jobs in three ways – using AJAX, Webcron, or cron. The default method is to use AJAX. However, the recommended method is to use the operating system cron feature.2

To run a cron job on our system, every 5 minutes, under the default Web server user, we must set up the following cron job to call the cron.php script.

Edit the crontab of the default Web server user (www-data):

# sudo crontab -u www-data -e

And append the following line, replacing the path /var/www/nextcloud/cron.php with the path to your current Nextcloud installation:

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

We can verify if the cron job has been added and scheduled running:

# sudo crontab -u www-data -l

Finally, choose Cron in the Background jobs section of the Admin settings page at Setting > Administration > Basic settings > Background jobs.

Previews generation

By default, Nextcloud thumbnail system generates previews of files for the following filetypes:

  • Images files;
  • Cover of MP3 files;
  • Text documents.

Additional, we can add support for SVG and video files, installing those packages:

# sudo apt install libmagickcore-6.q16-6-extra ffmpeg

To add support for PDF files, we must install ghostscript:

# sudo apt install ghostscript

And tweak ImageMagick’s security policy:

# sudo nano /etc/ImageMagick-6/policy.xml

Look for <policy domain="coder" rights="none" pattern="PDF" /> at the end of the file. And comment the line, so it look like:

<!-- <policy domain="coder" rights="none" pattern="PDF" /> -->

The generation of previews can use a fair amount of hardware resources and can degrade our experience when using Nextcloud. We can prevent that, using an app called Preview Generator and setting a cron job that runs periodically on the background and generates previews for new or recently changed files.

To do so, first run the following commands to install and enable the app, where /var/www/nextcloud/ is the path to our Nextcloud installation:

# sudo -u www-data php /var/www/nextcloud/occ app:install previewgenerator
# sudo -u www-data php /var/www/nextcloud/occ app:enable previewgenerator

To set for which filetypes Nextcloud should generate previews, edit the Nextcloud configuration file:

# sudo nano /var/www/nextcloud/config/config.php

And add the following lines just bellow 'installed' => true,:

'enable_previews' => true,
'enabledPreviewProviders' => 
array (
    0 => 'OC\\Preview\\PNG',
    1 => 'OC\\Preview\\JPEG',
    2 => 'OC\\Preview\\GIF',
    3 => 'OC\\Preview\\HEIC',
    4 => 'OC\\Preview\\BMP',
    5 => 'OC\\Preview\\XBitmap',
    6 => 'OC\\Preview\\MP3',
    7 => 'OC\\Preview\\TXT',
    8 => 'OC\\Preview\\MarkDown',
    9 => 'OC\\Preview\\Movie',
    10 => 'OC\\Preview\\MKV',
    11 => 'OC\\Preview\\MP4',
    12 => 'OC\\Preview\\AVI',
    13 => 'OC\\Preview\\PDF',

Then, before setting a cron job, we should generate all previews running:

# sudo -u www-data php /var/www/nextcloud/occ preview:generate-all -vvv

We can now edit the crontab of the default Web server user (www-data):

# sudo crontab -u www-data -e

And add a cron job that will run the application each 10 minutes:

*/10 *  *  *  * php /var/www/nextcloud/occ preview:pre-generate


Nextcloud is capable of sending password reset emails, notifying users of new file shares, changes in files, and activity notifications.

Nextcloud does not contain a full email server, but rather connects to your existing mail server, and supports three types of connections: SMTP, qmail, and Sendmail.

I recommend using a SMTP server, more precisely a SMTP relay, like Mailgun.

To connect Nextcloud to a remote SMTP server, we need the following information:

  • Encryption type: None, SSL/TLS, or STARTTLS;
  • The From address we want our outgoing Nextcloud mails to use;
  • Whether authentication is required;
  • Authentication method: None, Login, Plain, or NT LAN Manager;
  • The server’s IP address or fully-qualified domain name and the SMTP port;
  • Login credentials (if required).

Having this information we can then fill the configuration wizzard in the Email Server section of the Admin settings page at Setting > Administration > Basic settings > Email server.

Default phone region

Since Nextcloud 21, is recommend setting a default region for phone numbers, using ISO 3166-1 country codes such as DE for Germany, FR for France, PT for Portugal… It is required to allow inserting phone numbers in the user profiles starting without the country code (e.g. +351 for Portugal).

We can do that by editing the Nextcloud configuration file:

# sudo nano /var/www/nextcloud/config/config.php

And adding the following line, just bellow 'installed' => true,, changing the value to correspond to your country:

'default_phone_region' => 'PT',

Congratulations 🎉

You now have your self-hosted cloud storage solution and are one step closer to be the owner of your data!

Do you want contribute and add something to this tutorial? Have you found a typo, grammar or formatting error? Please, feel free to contact me or edit this post and open a pull resquest.