$linuxjunkies
>

Self-Host Nextcloud

Install a production Nextcloud server using Nginx, PHP-FPM, MariaDB, and Redis — including systemd-managed cron and performance tuning.

IntermediateUbuntuDebianFedoraArch12 min readUpdated June 7, 2026

Before you start

  • A server with at least 2 GB RAM and a public IP address
  • A domain name with an A record pointing at the server
  • Root or sudo access on the server
  • Certbot installed and working for TLS certificate issuance

Nextcloud gives you a self-hosted alternative to Google Drive, Dropbox, and similar services. Running it yourself means your data stays on your hardware, but it also means you are responsible for the stack. This guide walks through a production-grade install using Nginx, PHP-FPM, MariaDB, and systemd-managed cron — the combination that performs best under real load.

Prerequisites and Architecture

You need a server (VPS or bare metal) running a supported Linux distro with at least 2 GB RAM and 20 GB disk. A domain name pointed at the server's IP is assumed. The stack we build:

Step 1: Install System Packages

Install the web server, PHP-FPM, and required PHP extensions. Nextcloud's list changes between releases; always check the official requirements page.

Debian / Ubuntu

sudo apt update && sudo apt install -y \
  nginx mariadb-server redis-server \
  php8.2-fpm php8.2-cli php8.2-mysql php8.2-xml \
  php8.2-mbstring php8.2-curl php8.2-zip php8.2-gd \
  php8.2-intl php8.2-bcmath php8.2-gmp \
  php8.2-imagick php8.2-redis php8.2-apcu \
  unzip wget

Fedora / RHEL 9 / Rocky 9

sudo dnf install -y epel-release
sudo dnf install -y nginx mariadb-server redis \
  php php-fpm php-mysqlnd php-xml php-mbstring \
  php-curl php-zip php-gd php-intl php-bcmath \
  php-gmp php-pecl-imagick php-pecl-redis php-pecl-apcu \
  unzip wget

Arch Linux

sudo pacman -Syu nginx mariadb redis \
  php php-fpm php-gd php-intl php-apcu \
  php-imagick redis unzip wget
# php-fpm includes all core extensions on Arch; install extras from AUR as needed

Step 2: Configure MariaDB

Secure the installation, then create a dedicated database and user. Using utf8mb4 with the unicode_ci collation is mandatory for Nextcloud to store emoji and non-ASCII filenames correctly.

sudo systemctl enable --now mariadb
sudo mariadb-secure-installation
sudo mariadb -u root
CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'ncuser'@'localhost' IDENTIFIED BY 'StrongPassHere';
GRANT ALL PRIVILEGES ON nextcloud.* TO 'ncuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Add the following to /etc/mysql/conf.d/nextcloud.cnf (Debian/Ubuntu) or /etc/my.cnf.d/nextcloud.cnf (RHEL/Arch) to tune MariaDB for Nextcloud:

[mysqld]
transaction_isolation = READ-COMMITTED
binlog_format         = ROW
innodb_buffer_pool_size = 512M
sudo systemctl restart mariadb

Step 3: Configure PHP-FPM

Edit /etc/php/8.2/fpm/pool.d/www.conf (paths vary; on RHEL it is /etc/php-fpm.d/www.conf). The key settings for a medium workload:

pm = dynamic
pm.max_children      = 20
pm.start_servers     = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.max_requests      = 500

; These environment variables are required by Nextcloud
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMPDIR] = /tmp

Then tune php.ini (find it with php --ini):

memory_limit = 512M
upload_max_filesize = 16G
post_max_size = 16G
max_execution_time = 360
date.timezone = UTC
opcache.enable = 1
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 10000
opcache.memory_consumption = 128
opcache.save_comments = 1
opcache.revalidate_freq = 1
apc.enable_cli = 1
sudo systemctl enable --now php8.2-fpm
# On RHEL/Rocky:
# sudo systemctl enable --now php-fpm

Step 4: Download and Install Nextcloud

Always grab the latest stable release from nextcloud.com and verify the checksum. Do not install from distro packages — they lag behind on security releases.

cd /tmp
wget https://download.nextcloud.com/server/releases/latest.tar.bz2
wget https://download.nextcloud.com/server/releases/latest.tar.bz2.sha256
sha256sum -c latest.tar.bz2.sha256
sudo tar -xjf latest.tar.bz2 -C /var/www/
sudo chown -R www-data:www-data /var/www/nextcloud
# On RHEL/Arch the web user is 'nginx' or 'http'; adjust accordingly:
# sudo chown -R nginx:nginx /var/www/nextcloud

Step 5: Configure Nginx

Create /etc/nginx/sites-available/nextcloud (Debian/Ubuntu) or /etc/nginx/conf.d/nextcloud.conf (RHEL/Arch) with the configuration below. This follows Nextcloud's official Nginx template — the rewrite rules are required for DAV clients and the mobile apps.

upstream php-handler {
    server unix:/run/php/php8.2-fpm.sock;
    # RHEL path: unix:/run/php-fpm/www.sock
}

server {
    listen 80;
    server_name cloud.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name cloud.example.com;

    ssl_certificate     /etc/letsencrypt/live/cloud.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cloud.example.com/privkey.pem;

    root /var/www/nextcloud;
    index index.php index.html;

    add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" 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;
    add_header X-XSS-Protection "1; mode=block" always;

    client_max_body_size 16G;
    fastcgi_buffers 64 4K;

    location = /robots.txt { allow all; log_not_found off; access_log off; }
    location = /.well-known/carddav { return 301 $scheme://$host/remote.php/dav; }
    location = /.well-known/caldav  { return 301 $scheme://$host/remote.php/dav; }

    location / {
        rewrite ^ /index.php;
    }

    location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
    location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }

    location ~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) {
        fastcgi_split_path_info ^(.+?\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_pass php-handler;
        fastcgi_intercept_errors on;
        fastcgi_read_timeout 360;
    }

    location ~ \.(?:css|js|woff2?|svg|gif|map)$ {
        try_files $uri /index.php$request_uri;
        expires 6M;
        access_log off;
    }

    location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$ {
        try_files $uri /index.php$request_uri;
        access_log off;
    }
}
# Debian/Ubuntu only:
sudo ln -s /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Step 6: Run the Web Installer

Before visiting the URL, obtain a TLS certificate with Certbot if you have not already:

sudo certbot --nginx -d cloud.example.com

Navigate to https://cloud.example.com in a browser. Fill in the admin account details and database credentials you created in Step 2. Set the data directory to somewhere outside /var/www — for example /srv/nextcloud-data — to avoid accidental web exposure. Create it first:

sudo mkdir -p /srv/nextcloud-data
sudo chown www-data:www-data /srv/nextcloud-data

After the wizard completes, run the post-install OCC commands to set missing indexes and convert columns to bigint:

sudo -u www-data php /var/www/nextcloud/occ db:add-missing-indices
sudo -u www-data php /var/www/nextcloud/occ db:convert-filecache-bigint

Step 7: Set Up Redis for Caching and File Locking

sudo systemctl enable --now redis-server
# Fedora/RHEL: sudo systemctl enable --now redis

Add to /var/www/nextcloud/config/config.php inside the existing array:

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

Step 8: Replace Crontab with a systemd Timer

Nextcloud's background jobs must run every 5 minutes. A systemd timer is more reliable than cron and integrates with journalctl for logging.

Create /etc/systemd/system/nextcloud-cron.service:

[Unit]
Description=Nextcloud background job
After=network.target

[Service]
Type=oneshot
User=www-data
ExecStart=/usr/bin/php -f /var/www/nextcloud/cron.php

Create /etc/systemd/system/nextcloud-cron.timer:

[Unit]
Description=Run Nextcloud background jobs every 5 minutes

[Timer]
OnBootSec=5min
OnUnitActiveSec=5min
Unit=nextcloud-cron.service

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now nextcloud-cron.timer

Tell Nextcloud to use system cron rather than AJAX:

sudo -u www-data php /var/www/nextcloud/occ background:cron

Verification

# Check the timer is firing
systemctl list-timers nextcloud-cron.timer

# Run the Nextcloud security scan
sudo -u www-data php /var/www/nextcloud/occ security:check

# Check overall status
sudo -u www-data php /var/www/nextcloud/occ status

The admin panel at https://cloud.example.com/settings/admin/overview will also flag any outstanding configuration warnings — work through these before putting the instance into production.

Troubleshooting

  • 502 Bad Gateway: PHP-FPM is not running or the socket path in Nginx does not match. Check systemctl status php8.2-fpm and confirm the socket path with grep listen /etc/php/8.2/fpm/pool.d/www.conf.
  • 413 Request Entity Too Large: client_max_body_size in Nginx or upload_max_filesize in php.ini is too low. Both must be set; the smaller one wins.
  • Redis connection refused: Redis binds only to 127.0.0.1 by default, which is correct. Confirm with redis-cli ping. On SELinux systems (RHEL/Rocky), run setsebool -P httpd_can_network_connect 1.
  • Slow file listing after many files: Run sudo -u www-data php /var/www/nextcloud/occ files:scan --all once and ensure the bigint conversion from Step 6 completed successfully.
  • OCC commands fail with permission errors: Always prefix with sudo -u www-data. Running as root corrupts file ownership inside the data directory.
tested on:Ubuntu 24.04Debian 12Rocky 9Arch rolling

Frequently asked questions

Can I use PostgreSQL instead of MariaDB?
Yes. Nextcloud fully supports PostgreSQL 14+. Create the database with UTF-8 encoding and LC_COLLATE='C' for best compatibility, then install the php-pgsql extension in place of php-mysql.
Do I need a separate Redis instance, or can I share one?
You can share a single Redis instance by assigning Nextcloud a unique database index (add 'dbindex' => 1 to the redis array in config.php). Dedicated instances are safer in multi-tenant environments.
How do I upgrade Nextcloud to a new major version?
Put the instance in maintenance mode with 'occ maintenance:mode --on', back up the database and data directory, then use the built-in web updater or the occ upgrade command. Never skip major versions.
Why not use the all-in-one Docker image instead of this manual setup?
The AIO Docker image is excellent for low-maintenance homelabs. A manual stack gives you precise control over PHP tuning, storage paths, and reverse proxy configuration — important when running alongside other services or on shared infrastructure.
How do I handle large file uploads reliably?
Set client_max_body_size in Nginx, upload_max_filesize and post_max_size in php.ini, and fastcgi_read_timeout to a high value (360s or more). For very large files, the desktop or mobile sync client with chunked upload is more reliable than the browser.

Related guides