Deploying Nextcloud with PlanetScale cloud database

Deploying Nextcloud with PlanetScale cloud database

nextcloud manual installation

Nextcloud is an open source, self-hosted file sync and file share platform similar to Dropbox, OneDrive and other proprietary online storage services. It is a fork of Owncloud and 100% open source.

If you’re looking for a self-hosted file share and sync platform, then Nextcloud should be a good place to start. I’ll show you how to install and configure Nextcloud on your own Ubuntu server with Nginx web server and a remote PlanetScale cloud database.

Nginx installation

Nextcloud requires a web server to function, and Nginx is my choice. To install Nginx on Ubuntu, run the commands below:

sudo apt update
sudo apt install nginx

Just leave the Nginx installed, will configure the Nginx in next sections.

Nginx version consideration:

If you’re preferring the newest stable version of Nginx, you can install it by following the official guides:

  1. Install the prerequisites:

    sudo apt install curl gnupg2 ca-certificates lsb-release ubuntu-keyring
  2. Import the official nginx signing key so apt could verify the packages authenticity. Fetch the key:

    curl | gpg --dearmor \
        | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
  3. To set up the apt repository for stable nginx packages, run the following command:

    echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \ `lsb_release -cs` nginx" \
        | sudo tee /etc/apt/sources.list.d/nginx.list
  4. Set up repository pinning to prefer packages over distribution-provided ones:

    echo -e "Package: *\nPin: origin\nPin: release o=nginx\nPin-Priority: 900\n" \
        | sudo tee /etc/apt/preferences.d/99nginx
  5. To install nginx, run the following commands:

    sudo apt update
    sudo apt install nginx
    # if you've installed previous versions of nginx
    # uninstall it by following command:
    # sudo apt remove nginx --purge
    # sudo apt update
    # sudo apt install nginx

PHP installation

As Nextcloud is written in PHP programming language, PHP is required.

Ubuntu has packages for all required modules, just install them. For now, Ubuntu 20.04 has PHP 7.4 included, that’s fine.

Required PHP modules

First, have the PHP FastCGI Process Manager (FPM) installed, other related PHP modules will be installed along with php-fpm:

sudo apt install php-fpm

Then, install other PHP modules. The required modules for Nextcloud are1:

  • PHP 7.3, 7.4 or 8.0 (recommended)
  • PHP module ctype
  • PHP module curl
  • PHP module dom
  • PHP module filter (only on Mageia and FreeBSD)
  • PHP module GD
  • PHP module hash (only on FreeBSD)
  • PHP module JSON
  • PHP module libxml (Linux package libxml2 must be >=2.7.0)
  • PHP module mbstring
  • PHP module openssl
  • PHP module posix
  • PHP module session
  • PHP module SimpleXML
  • PHP module XMLReader
  • PHP module XMLWriter
  • PHP module zip
  • PHP module zlib

Check the installed PHP modules by php -m.

Any missing modules can be installed via apt. For example, to install the PHP module curl, just run sudo apt install php-curl.

Probably, you need to further install the following modules:

sudo apt install php-curl php-dom php-gd php-mbstring php-zip

For database connection, we need to install one of the following modules:

  • PHP module pdo_sqlite (>= 3, usually not recommended for performance reasons)
  • PHP module pdo_mysql (MySQL/MariaDB)
  • PHP module pdo_pgsql (PostgreSQL)

Since I’m going to use PlanetScale database (MySQL-compatible serverless database2), just install the php-mysql module:

sudo apt install php-mysql

Also, Nextcloud has recommended several packages, go ahead to install them.

  • PHP module fileinfo (highly recommended, enhances file analysis performance)
  • PHP module bz2 (recommended, required for extraction of apps)
  • PHP module intl (increases language translation performance and fixes sorting of non-ASCII characters)
sudo apt install php-fileinfo php-bz2 php-intl

Required for specific apps

  • PHP module ldap (for LDAP integration)
  • PHP module smbclient (SMB/CIFS integration, see SMB/CIFS)
  • PHP module ftp (for FTP storage / external user authentication)
  • PHP module imap (for external user authentication)
  • PHP module bcmath (for passwordless login)
  • PHP module gmp (for passwordless login, for SFTP storage)
  • PHP module exif (for image rotation in pictures app)

Except for smbclient, other modules are easy to configure:

sudo apt install php-ldap php-ftp php-imap php-bcmath php-gmp php-exif

More details about SMB, please read the Nextcloud docs.

For enhanced server performance (optional)

Select one of the following memcaches:

  • PHP module apcu (>= 4.0.6)
  • PHP module memcached
  • PHP module redis (>= 2.2.6, required for Transactional File Locking)

I’m going to use both the APCu and Redis, configurations will be included in the following section(s).

sudo apt install php-apcu php-redis

For preview generation (optional)

  • PHP module imagick
  • avconv or ffmpeg
  • OpenOffice or LibreOffice

Well, I’m not going to use Office suits in Nextcloud, just ignore them…

sudo apt install php-imagick ffmpeg

PlanetScale database

PlanetScale provides a free developer option with following limits:

  • 10GB storage/mo

  • 1 billion row reads/mo

  • 10 million row writes/mo

  • 3 branches per database

  • 1,000 concurrent connections

Check the pricing page for more details, I’m okay with the free version for personal Nextcloud usage…

PlanetScale environment set up

I’m going to use the PlanetScale CLI (pscale) to set up a local proxy for the cloud database, which is available as downloadable binaries from the releases page. For Ubuntu, download the .deb file, e.g.:

wget ""

Here, I’m running Ubuntu on an ARM-architecture server, choose the corresponding version of your server…

Then install the pscale CLI:

sudo dpkg -i ./pscale_0.91.0_linux_arm64.deb

# check it's working
pscale --help

# remove the deb file
# rm ./pscale_0.91.0_linux_arm64.deb

Also, pscale requires the MySQL command-line client to function, install it via apt:

sudo apt install mysql-client

Create and connect database

Create database

After install the pscale CLI, sign in with:

pscale auth login

You can now use pscale to create new database:

pscale db create nextcloud-database

Currently, the following regions are supported 3, with their respective slugs:

  • US East - Northern Virginia us-east
  • US West - Oregon us-west
  • EU West - Dublin eu-west
  • Asia Pacific - Mumbai ap-south
  • Asia Pacific - Singapore ap-southeast
  • Asia Pacific - Tokyo ap-northeast

Create new database with specific region, eu-west for example:

pscale db create database-name --region eu-west

Select the region closet to your server to reduce latency.

Create service token

Now, let’s move forward to create a service token for database connection:

pscale service-token create

This command will return a new service token ID and the value for your use. Take a note on the values returned, required for further configurations.

Then, give the generated service token all the permissions on the database that just created:

pscale service-token add-access <token id> \
read_branch delete_branch create_branch connect_branch connect_production_branch \
read_deploy_request create_deploy_request approve_deploy_request \
read_comment create_comment \
--database <database name>

Replace the <token id> and <database name>, correspondingly.

See more commands available for pscale service-token here: Service tokens.

Connect the database using the PlanetScale proxy

It’s easy to use the CLI to establish a secure connection to PlanetScale database:

pscale connect <DATABASE_NAME> <BRANCH_NAME>

The default <BRANCH_NAME> is main, you can create 3 branches for each database with free PlanetScale subscription. I’m not going to explain the details of the branching, just use the main branch for further configurations.

By running the command above 👆, it will return to you that the connection established at The CLI will use a different port if the 3306 is unavailable.

I’d prefer to use the systemd to establish the database connection in the background. Just create a service under /etc/systemd/system/. For instance, /etc/systemd/system/pscale.service:

Description=PlanetScale Database

ExecStart=/usr/bin/pscale connect <database-name> <database-branch> --debug --org <org-name> --service-token-id <service-token-id> --service-token <service-token-value>


Replace the <database-name> that just created, the <database-branch> could be main as just discussed.

The <org-name> can be found by pscale org list, it’s your user name in PlanetScale by default.

The <service-token-id> and <service-token-value> are the ones that just generated and gave permissions to. If you forget the value of the token, you need to create a new one.

After adding the pscale.service file, enable it on the system:

sudo systemctl daemon-reload
sudo systemctl enable pscale.service

sudo systemctl start pscale

# check the service working well
sudo systemctl status pscale

Make sure it’s running well, and check it’s running on

Now, the PlanetScale MySQL database is ready, just treated it as a local running database.

Ads by Google

Install Nextcloud

Back to the installation of Nextcloud:

  • Go to the Nextcloud Download Page.

  • Go to Download Nextcloud Server > Download > Archive file for server owners and download either the tar.bz2 or .zip archive. For example:


    This downloads a file named (where x.y.z is the version number).

  • Now you can extract the archive contents. Run the appropriate unpacking command for your archive type:

  • This unpacks to a single nextcloud directory. Move the nextcloud directory to its final destination. For Nginx:

    sudo mv ./nextcloud/ /var/www/
    # check it's existing
    # ls /var/www/
  • Change the ownership of the nextcloud directory to the HTTP user (www-data):

    sudo chown -R www-data:www-data /var/www/nextcloud/
  • Use the Nextcloud occ command to complete the installation:

    cd /var/www/nextcloud/
    sudo -u www-data php occ maintenance:install \ 
    --database "mysql" --database-name "<database-name>" \
    --database-user "root" --database-pass "" --database-host "" \
    --admin-user "<user-name>" --admin-pass "<your-password>" --admin-email "<your-email>"

    This may take several minutes to finish the installation as Nextcloud populates database schema to PlanetScale, please wait. When it’s done, you should see:

    Nextcloud was successfully installed

    Notes: The database name should be the same you created with pscale. There’s no need to fill in the database password, leave it blank "". I found that I cannot install with too complicated admin-password, so choose a simple password and then change the password in the Nextcloud web interface after installation.

Configure Nginx

Just copied from Nextcloud’s document, it’s working.

For example, create a configuration file as /etc/nginx/conf.d/ with following contents:

upstream php-handler {
    server unix:/var/run/php/php7.4-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;

    # 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;

    # Use Mozilla's guidelines for SSL/TLS settings
    ssl_certificate     /etc/ssl/nginx/;
    ssl_certificate_key /etc/ssl/nginx/;

    # 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 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;

    # 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                         "none"          always;
    add_header X-XSS-Protection                     "1; mode=block" always;

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

    # 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;

Adjust the configuration with your own domain name.

Note that we haven’t got SSL certs yet. It’s very easy with, please find my previous post Free ZeroSSL wildcard SSL certificates with DNS API to get the cert keys, and replace with the correct file path of the certs file in the Nginx configuration.

Now, go back to the /var/www/nextcloud/config/ folder. In which the custom domain should be added in the config.php:

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

Adjust the following snippets:

  'trusted_domains' => 
  array (
    0 => 'localhost',
    1 => '',
    2 => '',

Yes, we can add multiple domains, but don’t forget to set corresponding virtual server in Nginx…

Restart nginx and php7.4-fpm:

sudo systemctl restart nginx
sudo systemctl restart php7.4-fpm

Now, it’s time to visit the Nextcloud instance through the custom domain.

remember to update password if weak password was used during installation

Nextcloud optimisation

Under, several Security & setup warnings might be shown.

Nextcloud security & setup warnings

Let’s further optimise the php and nginx configurations for better security and performance.

php-fpm configuration notes

Referred to the Nextcloud installation guide.

php.ini file used by the web server (php-fpm) is:


And the php.ini used by the php-cli and so by the Nextcloud CRON jobs is:


system environment

First go to the web server configuration. In php-fpm, the system environment variables like PATH, TPM or others are not automatically populated in the same way as when using php-cli. A PHP call like getenv('PATH') can therefore return an empty result. Manually configure it in /etc/php/7.4/fpm/pool.d/www.conf. Usually, you will find some or all of the environment variables already in the file, but commented out like this:

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

Uncomment the appropriate existing entries (remove the leading ;), and uncommenting this line:

clear_env = no

maximum upload size

To increase the maximum upload size, we also need to modify the php-fpm configuration and increase the upload_max_filesize and post_max_size values in /etc/php/7.4/fpm/php.ini.

post_max_size: “Sets max size of post data allowed. This setting also affects file upload. To upload large files, this value must be larger than upload_max_filesize.”

Update accordingly in the Nginx configuration file for the client_max_body_size entry.

You will need to restart php-fpm and Nginx in order for these changes to be applied.

sudo systemctl restart php7.4-fpm
sudo systemctl reload nginx

If you’re proxying the Nextcloud by Cloudflare, note that Cloudflare limits the upload size (HTTP POST request size):

  • 100MB Free and Pro
  • 200MB Business
  • 500MB Enterprise by default

increase memory limit

To increase the PHP memory limit, edit it in the /etc/php/7.4/fpm/pool.d/www.conf, like this:

php_admin_value[memory_limit] = 2G

Memory caching

We can significantly improve the Nextcloud server performance with memory caching, where frequently-requested objects are stored in memory for faster retrieval. A memcache is not required and you may safely ignore the warning if you prefer.

Nextcloud supports multiple memory caching backends, so you can choose the type of memcache that best fits your needs. The supported caching backends are:

  • APCu, APCu 4.0.6 and up required.

    A local cache for systems.

  • Redis, PHP module 2.2.6 and up required.

    For local and distributed caching as well as transactional file locking.

  • Memcached

    For distributed caching.

Memcaches must be explicitly configured in Nextcloud by installing and enabling your desired cache, and then adding the appropriate entry to config.php (See Configuration Parameters for an overview of all possible config parameters).

Recommended caches are APCu and Redis. Here we go.


APCu is a data cache, and it is available in most Linux distributions. As we already installed the php-apcu, add this line to the /var/www/nextcloud/config/config.php file:

'memcache.local' => '\OC\Memcache\APCu',

APCu is disabled by default on CLI which could cause issues with nextcloud’s cron jobs. Please make sure you set the apc.enable_cli to 1 on your php.ini config file or append --define apc.enable_cli=1 to the cron job call.

I’m setting it at /etc/php/7.4/mods-available/apcu.ini:

It’s very tricky to set the apc.enable_cli, as discussed here:

And then check it’s working:

sudo -u www-data php /var/www/nextcloud/occ status

The error message looks like:

OCP\HintException: [0]: Memcache \OC\Memcache\APCu not available for local cache (Is the matching PHP module installed and enabled?)

If no error message outputted by sudo -u www-data php /var/www/nextcloud/occ status, the APCu is correctly configured.


Redis is an excellent modern memcache to use for distributed caching, and as a key-value store for Transactional File Locking because it guarantees that cached objects are available for as long as they are needed.

Well, I’m going to use for redis caching… which includes 10,000 commands daily and max daily bandwidth of 50GB. Might be fine for my case.

First, create one Redis database on with TLS disabled. And then, insert the configuration in /var/www/nextcloud/config/config.php like:

'memcache.locking' => '\OC\Memcache\Redis',
'memcache.distributed' => '\OC\Memcache\Redis',
'redis' => [
    'host' => '<your-redis-uri-by-upstash>',
    'port' => '<upstash-port>',
    'password' => '<top-secret>',
    'timeout' => 1.5,

Then check with sudo -u www-data php /var/www/nextcloud/occ status, no error or warning messages should be shown.

If you figured out how to connect with TLS enabled with upstash redis server, please let me know in the comment section at the end of this post 🫶.

Additional notes for Redis vs. APCu on memory caching

APCu is faster at local caching than Redis. If you have enough memory, use APCu for Memory Caching and Redis for File Locking. If you are low on memory, use Redis for both.

External storage

External storage is disabled by default. Enable it in Apps > Disabled apps of the Nextcloud web interface. Then add external storage under Settings > Administration > External storage.

For me, I’m mounting my OneDrive with Rclone, then add it as a Local storage.

My custom scripts to mount onedrive:

# just mounting the /nextcloud folder in my onedrive account
sudo rclone mount onedrive:/nextcloud /home/ubuntu/onedrive --daemon --allow-other --log-level NOTICE --syslog --vfs-cache-mode writes --vfs-read-chunk-size 1M --dir-cache-time 5m --copy-links --no-gzip-encoding --no-check-certificate --allow-non-empty --ignore-checksum --ignore-size
sleep 2s
sudo mount -o bind /home/ubuntu/onedrive /media/onedrive

Then add /media/onedrive in Nextcloud. More details see Rclone documents.

My previous post Setting up snap Nextcloud on Ubuntu also talks about this.

Ads by Google


Frank Lin, PhD

Hey, there! This is Frank Lin (@flinhong), one of the 1.41 billion . This 'inDev. Journal' site holds the exploration of my quirky thoughts and random adventures through life. Hope you enjoy reading and perusing my posts.


Setup an IKEv2 server with strongSwan



Setup an IKEv2 server with strongSwan

IKEv2, or Internet Key Exchange v2, is a protocol that allows for direct IPSec tunnelling between two points. In IKEv2 implementations, IPSec provides encryption for the network traffic. IKEv2 is natively supported on some platforms (OS X 10.11+, iOS 9.1+, and Windows 10) with no additional applications necessary, and it handles client hiccups quite smoothly.

Hands on IBM Cloud Functions with CLI



Hands on IBM Cloud Functions with CLI

IBM Cloud CLI allows complete management of the Cloud Functions system. You can use the Cloud Functions CLI plugin-in to manage your code snippets in actions, create triggers, and rules to enable your actions to respond to events, and bundle actions into packages.