Server tuning

This page collects configuration changes that can improve the performance of your Nextcloud server. Most items only require editing a configuration file or installing a package, while a few involve additional services. Start with the ones that match your setup and revisit the rest as your instance grows.

Using cron to perform background jobs

See :doc:`../configuration_server/background_jobs_configuration` for a description and the benefits.

Reducing system load

High system load will slow down Nextcloud and may also lead to other unwanted side effects. To reduce load, you should first identify the source of the problem. Tools such as htop, iotop, netdata, or glances can help you identify the process or drive that slows down your system. First, make sure that you have installed and assigned enough RAM. Minimize swap usage as much as possible, as excessive swapping can severely degrade performance. If you run your database inside a VM, use a dedicated block device for database storage rather than storing it inside the VM's disk image file, to reduce latency caused by multiple abstraction layers.

Log Levels

Verify the loglevel in your config.php file. The default log level is set to 2 (WARN) in new installations. Sometimes this parameter is inadvertently left at the DEBUG level (0) after troubleshooting. In some older installations, this parameter may also be something other than the default. Use 0 (DEBUG) when you have a problem to diagnose, and then reset your log level to a less-verbose level. DEBUG outputs a lot of information, and can affect your server performance.

Debug Mode

Verify that debug is set to false in your config.php file. The default is false in new installations (or when not specified). While similar to the DEBUG logging level, this option also disables various optimizations (to facilitate easier debugging) and generates additional debug output both at the browser level and server-side. It should not be enabled in production environments except during isolated troubleshooting.

Caching

Caching improves performance by storing data, code, and other objects in memory. Memory caching is not enabled by default because it requires optional extensions (such as APCu) and/or system components (e.g., Redis). Although these add-ons are generally not challenging to install and activate—at least in single-server deployments—you must install them before enabling their use in Nextcloud. See :doc:`../configuration_server/caching_configuration` for guidance.

Compression

Enabling compression in your web server for JavaScript, CSS, and SVG files improves performance because less data is transferred to clients.

Replacing SQLite

SQLite is a suitable database for some use cases, but using MariaDB, MySQL, or PostgreSQL can be more beneficial with Nextcloud.

If you do not select a database at installation time, SQLite is used by default because it does not require any external components.

However, MySQL/MariaDB or PostgreSQL are generally recommended for Nextcloud because of the performance limitations of SQLite with highly concurrent applications, like Nextcloud.

If your installation is already running on SQLite, you can convert to MySQL or MariaDB using the steps provided in :doc:`../configuration_database/db_conversion`.

See the section :doc:`../configuration_database/linux_database_configuration` for instructions on configuring Nextcloud for MySQL or MariaDB.

Tuning your database

Databases are not plug-and-play. They benefit not only from basic configuration for compatibility with Nextcloud, but also from tuning within the environment in which they are deployed. This tuning should be based on your hardware, storage, usage patterns, underlying operating system, priorities, and other factors.

For more details and help tuning your database:

Using Redis-based transactional file locking

Transactional File Locking uses the database as the default backend. This places additional load on your database. See the section :doc:`../configuration_files/files_locking_transactional` for instructions on configuring Nextcloud to use Redis-based Transactional File Locking.

TLS / encryption app

TLS (HTTPS) and file encryption/decryption can be offloaded to a processor's AES-NI extension. This can both speed up these operations while lowering processing overhead. This requires a processor with the AES-NI instruction set.

Here are some examples of how to check if your CPU/environment supports the AES-NI extension:

Enable HTTP/2 for faster loading

HTTP/2 has huge speed improvements over HTTP with multiple requests. Most browsers already support HTTP/2 over TLS (HTTPS).

Tune PHP-FPM

PHP-FPM is required for Nginx setups and is widely used with Apache as well. Its default configuration is extremely conservative: the default pool has pm.max_children = 5, which limits Nextcloud to five simultaneous PHP requests and is a common cause of gateway timeouts, slow page loads, and sync client errors under any real load.

Process manager modes

The pm directive controls how PHP-FPM manages its worker processes:

dynamic

Keeps between pm.min_spare_servers and pm.max_spare_servers idle workers alive, up to a ceiling of pm.max_children. Good default for most Nextcloud installations: balances RAM efficiency with burst capacity. Set pm.min_spare_servers high enough that sync-client poll bursts do not stall waiting for new processes to spawn.

static

Always keeps exactly pm.max_children processes running. Highest memory use, lowest latency. Use on dedicated servers with predictable load. Always set pm.max_requests to recycle workers and prevent memory leaks.

ondemand

Spawns a worker only when a request arrives; kills idle workers after pm.process_idle_timeout (default 10s). Lowest memory use but adds cold-start latency on every burst. Not recommended for Nextcloud: desktop and mobile clients poll every 30 seconds, repeatedly triggering cold starts.

Key parameters

pm.max_children

Maximum (or fixed, under static) number of simultaneous worker processes. This is the most important value to tune. If all workers are busy, new requests queue up; a full queue produces 502/504 errors.

Estimate it from available RAM:

pm.max_children = floor(available_RAM_for_PHP / average_worker_RSS)

Measure the average RSS of a running pool:

ps --no-headers -o rss -C php-fpm | awk '{sum+=$1; count++} END {if (count>0) print sum/count/1024 " MB"; else print "No php-fpm processes found"}'

A typical Nextcloud worker uses 50–100 MB (more if Imagick or LDAP is loaded). Leave headroom for the OS, web server, database, and cache. Setting pm.max_children too high causes swapping, which is worse than queuing.

pm.start_servers (dynamic only)

Workers started at FPM boot. Defaults to (pm.min_spare_servers + pm.max_spare_servers) / 2 if not set.

pm.min_spare_servers / pm.max_spare_servers (dynamic only)

Range of idle workers kept warm. For Nextcloud, keep pm.min_spare_servers high enough to absorb a sync-client burst without spawning new processes:

pm.min_spare_servers = 4    # adjust upward for many connected clients
pm.max_spare_servers = 16
pm.max_requests

Recycle a worker after this many requests. 0 means never recycle. Setting a value of 500–1000 guards against slow memory growth from leaky extensions (Imagick, LDAP, SAML XML parsers). Essential under static mode.

pm.process_idle_timeout (ondemand only)

How long an idle worker lives before being killed. Default: 10s.

Example configuration

A starting point for dynamic mode on a server with 2 GB of RAM dedicated to PHP (adjust pm.max_children based on your measured worker RSS):

pm = dynamic
pm.max_children = 30
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 16
pm.max_requests = 500

Use the PHP-FPM process calculator as a cross-check for your values.

Slow log

Enable the slow log to identify PHP scripts that are taking too long:

slowlog = /var/log/php-fpm-slow.log
request_slowlog_timeout = 5s

Each entry records the full PHP backtrace of the slow request. This is the fastest way to find the root cause of gateway timeouts and sluggish pages.

Troubleshooting

502 Bad Gateway

All pm.max_children workers are busy. Increase pm.max_children if RAM allows. Enable the slow log to check whether a slow query is tying up workers. Also check that a request_terminate_timeout is not killing workers mid-request.

504 Gateway Timeout

A worker is running but not responding within the web server's upstream timeout (nginx fastcgi_read_timeout, Apache ProxyTimeout). Common Nextcloud causes: large file operations, slow database queries during sync, or PROPFIND over large directory trees. Use the slow log to identify the bottleneck.

Memory grows over time

Memory leaks can occur in worker processes. Common culprits include libraries that manage external resources (like Imagick for image processing). Use the slow log and RSS monitoring to identify which requests cause growth. Set pm.max_requests = 500 to recycle them before they grow too large.

Slow first request after idle

pm = ondemand or pm.min_spare_servers too low. Switch to pm = dynamic and raise pm.min_spare_servers.

After any configuration change, reload PHP-FPM — changes do not take effect until you do:

sudo systemctl reload php8.3-fpm   # Debian/Ubuntu — adjust version as needed
sudo systemctl reload php-fpm      # RHEL/Fedora

For pool configuration details (environment variables, upload sizes, Unix socket vs TCP), see :ref:`php_fpm_tips_label` in the installation guide.

Enable PHP OPcache

The OPcache improves the performance of PHP applications by caching precompiled bytecode.

Revalidation

OPcache revalidation in PHP handles changes made to PHP application code stored on disk. Code changes occur whenever:

  • Nextcloud or a Nextcloud app is upgraded

  • a configuration change is made (e.g. when config.php is modified)

Nextcloud, as much as possible, handles cache revalidation internally when required. However, this is not foolproof. In a default PHP environment, revalidation is enabled, and cached scripts are checked for changes on disk every 2 seconds. In many environments, these default values are reasonable and may never need to be changed.

However, the revalidation frequency can be adjusted and may potentially enhance performance. We make no recommendations here about appropriate values for revalidation (other than the PHP defaults).

To change the default from 2 and check for changes on disk at most every 60 seconds, add the following setting to your php.ini file:

opcache.revalidate_freq = 60

Any Server/app upgrades or changes to config.php will then require restarting PHP (or otherwise manually clearing the cache or invalidating this particular script).

Sizing

If any OPcache size limit exceeds 90% of its allocated size, the admin panel will show a related warning and suggest changes.

For more details, check the official PHP documentation. To monitor OPcache usage and clear individual or all cache entries, you can use opcache-gui.

Comments

Nextcloud strictly requires code comments to be preserved in opcode, which is the default. If your PHP settings have changed, ensure the following is set in your php.ini:

opcache.save_comments = 1

JIT

PHP ships with a JIT compiler that can be enabled on x86 platforms to benefit any CPU-intensive apps you might be running. To enable a tracing JIT with all optimizations, add to your php.ini:

opcache.jit = 1255
opcache.jit_buffer_size = 8M

Previews

It is possible to speed up preview generation using an external microservice: Imaginary.

We strongly recommend running our custom Docker image, which is more up to date than the official image. You can find the image at https://ghcr.io/nextcloud-releases/aio-imaginary. When running it, map a port by adding -p <port>:9000 to the docker run command (or Compose equivalent), e.g.

docker run -d -p 9000:9000 --name nextcloud_imaginary --restart always ghcr.io/nextcloud-releases/aio-imaginary:latest

Ensure the service is only accessible from your internal servers. Then, configure Nextcloud to use Imaginary by editing your config.php file:

'enabledPreviewProviders' => [
    'OC\Preview\TXT',
    'OC\Preview\MarkDown',
    'OC\Preview\OpenDocument',
    'OC\Preview\Krita',
    'OC\Preview\Imaginary',
],
'preview_imaginary_url' => 'http://<url of imaginary>:<port>',

Settings

To set the preview format for Imaginary (default is jpeg), add to your config.php:

'preview_format' => 'webp',

To set an API key for Imaginary:

'preview_imaginary_key' => 'secret',

The default WebP quality setting for preview images is '80'. Change this with:

occ config:app:set preview webp_quality --value="30"