Nextcloud self-hosting tutorial

Fabio Natali, 15 January 2025

Intro

Today I had to setup a Nextcloud instance on a cloud server. A completely scripted approach (e.g. via Ansible or OpenTofu) felt a bit over-engineered in my particular case, so I went for a semi-manual installation.

By semi-manual I mean that I scripted parts of the process but not the entirety of it, back-to-back. What I did, though, was to document things in a human readable format and as granularly as possible, in case I need to get back to this and redo the installation again.

Once done with the documentation, I thought this could actually double as a blog post, so there it is. I hope you may find it interesting.

Shout out to jgart of WhereIsEveryone who pointed me to the Nextcloud Ubuntu snap and helped with a first version of this post.

Nextcloud instance setup

In the unlikely case that you've never heard of it, Nextcloud is a Free and Open Source software suite for creating and using a file hosting service.

Nextcloud-based hosting can be bought as a service from various providers. However, being an open source project, it can also be self-hosted either on a local or cloud server.

In this short guide we'll see how to set up a Nextcloud instance on a DigitalOcean cloud server. Things shouldn't be much different if you want to use a different cloud provider or if you want to use a local machine.

DigitalOcean account

You'll need a DigitalOcean account if you want to create an actual Nextcloud instance as you follow this guide. Note that you'll be charged for creating DigitalOcean machines (or droplets, in DigitalOcean jargon) and using other of their services. Spinning up a new DigitalOcean machine is very easy, make sure you cancel any unused instances to avoid extra costs.

Most DigitalOcean operations can be launched via a command-line tool called doctl. This guide assumes doctl is installed and that the DIGITALOCEAN_ACCESS_TOKEN environment variable is set to a valid DigitalOcean API token.

export DIGITALOCEAN_ACCESS_TOKEN=`pass show digitalocean.com/tokens/nextcloud`

In order to provision a new machine, first define the SSH key that you intend to use to connect to it. The list of the SSH keys available for a DigitalOcean account can be retrieved with the following command.

doctl compute ssh-key list

You also need to specify a system image. The list of available images can be retrieved with doctl compute image list or found at this page. Use a recent Ubuntu, e.g. ubuntu-24-10-x64.

Finally, you'll need a domain name to assign to Nextcloud once installed. Choose a domain and make sure you have access to its DNS settings.

Machine pre-configuration with cloud-init

We'll be using cloud-init to apply some initial configuration to the newly created machine, like installing some packages and setting up a firewall. Prepare a cloud-init configuration file like the one below, you'll need it when provisioning the machine (see the --user-data-file option). Note that the file has to start with #cloud-config to be interpreted properly.

#cloud-config
package_update: true
package_upgrade: true
package_reboot_if_required: true
write_files:
  - path: /etc/nftables.conf
    content: |
      flush ruleset
      table inet firewall {
          chain inbound {
              type filter hook input priority 0; policy drop;
              iif lo accept
              meta l4proto {icmp, ipv6-icmp} accept
              ct state vmap {
                  established: accept, related: accept, invalid: drop
              }
              ct state new limit rate over 1/second burst 10 packets drop
              tcp dport 80 accept
              tcp dport 443 accept
              tcp dport ssh accept
          }
          chain forward {
              type filter hook forward priority 0; policy drop;
          }
      }
runcmd:
  - [ systemctl, enable, nftables.service ]
  - [ systemctl, restart, nftables.service ]
  - [ snap, install, nextcloud ]

Provisioning

A new DigitalOcean machine can now be provisioned with this command.

export region=lon1
export size=s-2vcpu-4gb
export image_id=ubuntu-24-10-x64
export ssh_key_id=26720555
doctl compute droplet create \
    --enable-ipv6 \
    --image "${image_id}" \
    --region "${region}" \
    --size "${size}" \
    --ssh-keys "${ssh_key_id}" \
    --user-data-file build/cloud-config \
    --wait \
    nextcloud

After a short while, the command should return a success message that includes some machine details, such as its IPv4 and IPv6 addresses.

Connect to the machine from the DigitalOcean web console and retrieve its SSH public key fingerprint with ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.

From a terminal, SSH into the machine as root, check that the SSH fingerprint corresponds to what obtained from the web console.

Once logged in, verify that:

  • The firewall has been activated correctly with nft list ruleset. The output should be similar to what was defined in our cloud-config file.
  • Nextcloud is running, for example by connecting to it locally with curl localhost.

You may now complete the Nextcloud installation - do this straightaway because Nextcloud is currently exposed to the internet and everybody can log in with the default credentials.

Nextcloud configuration

We mentioned that a domain name is required to complete the installation. Update the domain DNS with A and AAAA records pointing to the machine IPv4 and IPv6 addresses, respectively.

Run the following commands from the DigitalOcean machine.

nextcloud.enable-https lets-encrypt

This will ask you a few questions about the domain name that will be used to serve Nextcloud. Once done, continue with the following commands.

nextcloud.manual-install admin REPLACE-WITH-A-STRONG-PASSWORD
nextcloud.occ config:system:set trusted_domains 0 --value=YOUR-DOMAIN
nextcloud.occ config:system:set overwrite.cli.url --value="https://YOUR-DOMAIN"
snap set nextcloud php.memory-limit=1024M
snap set nextcloud http.compression=true

SMTP server configuration

Add the SMTP server configuration via the web interface at https://domain-name/index.php/settings/admin. Alternatively, use the occ command-line client:

cat << EOF | nextcloud.occ config:import
{
  "mail_from_address": "nextcloud",
  "mail_smtpmode": "smtp",
  "mail_sendmailmode": "smtp",
  "mail_domain": "example.com",
  "mail_smtphost": "mail.example.com",
  "mail_smtpport": "587",
  "mail_smtpauth": 1,
  "mail_smtpname": "nextcloud@example.com",
  "mail_smtppassword": "SMTP-PASSWORD"
}
EOF

Parameters can also be set individually with nextcloud.occ config:system:set KEY --value="VALUE", and inspected with nextcloud.occ config:system:get KEY. For instance:

nextcloud.occ config:system:set mail_from_address --value="nextcloud"

Security scan

At this point you may want to go to the Nextcloud Security Scan page and run a scan of your installation.

Nextcloud management

Congratulations, if you got to this point your Nextcloud instance is running and ready to use! Now let's have a look at a few more commands that will be needed in Nextcloud's day-to-day use and administration.

Users and groups

Nextcloud users can be added with this occ command:

nextcloud.occ user:add \
    --display-name="Jane Doe" \
    --email="jane@example.com" \
    --generate-password \
    --group="staff" \
    jane@example.com

The --generate-password enables a workflow where the user receives an email notification with a link to set up their account. Note that this requires a correctly configured SMTP server.

Use --group to add a user to a group. Repeat the option multiple times to specify more than one group. Any non-existing group will be created automatically.

By default, users are created with unlimited disk quota. If you want to change that, run this command:

nextcloud.occ user:setting jane@example.com files quota 10GB

Or this one to set the quota back to unlimited:

nextcloud.occ user:setting jane@example.com files quota none

Batch import

It's possible to write a little script to import users in batch as opposed to individually. For example, let's assume our users are listed in a JSON file like this:

[
  {
    "display-name": "Alice",
    "email": "alice@example.com",
    "groups": [
      "staff",
      "director"
    ],
    "quota": "none",
    "username": "alice@example.com"
  },
  {
    "display-name": "Bob",
    "email": "bob@example.com",
    "groups": [
      "staff",
      "project-manager"
    ],
    "quota": "100GB",
    "username": "bob@example.com"
  },
  {
    "display-name": "Charlie",
    "email": "charlie@example.com",
    "groups": [
      "staff",
      "sales-manager"
    ],
    "quota": "20GB",
    "username": "charlie@example.com"
  }
]

Now use this script to batch-create the Nextcloud users. First install Guile and the guile-json library with apt install guile guile-json. The script can be run as cat users.json | guile nextcloud-create-users.scm.

(use-modules (json))
(use-modules (srfi srfi-1))

(define* (nextcloud-occ args)
  "Run a Nextcloud occ command and return its exit status."
  (let ((bin/occ "nextcloud.occ"))
    (zero? (status:exit-val (apply system* bin/occ args)))))

(define (create-nextcloud-user user)
  "Create a Nextcloud user via Nextcloud's occ command."
  (let* ((display-name (assoc-ref user "display-name"))
         (email (assoc-ref user "email"))
         (groups (vector->list (assoc-ref user "groups")))
         (quota (assoc-ref user "quota"))
         (username (assoc-ref user "username"))
         (args-add (append (list
                            "user:add"
                            "--display-name" display-name
                            "--email" email
                            "--generate-password")
                           (append-map (lambda (x) (list "--group" x)) groups)
                           (list username)))
         (args-quota (list "user:setting" username "files" "quota" quota)))
    (if (nextcloud-occ args-add)
        (begin
          (format #t "Created user ~s (~s)\n" display-name email)
          (when (nextcloud-occ args-quota)
            (format #t "User quota set to ~s\n" quota)))
        (format #t "Could not create user ~s (~s)\n" display-name email))))

(map create-nextcloud-user (vector->list (json->scm)))

Misc

Nextcloud maintenance

A new Nextcloud user can be added with nextcloud.occ user:add USERNAME. A Nextcloud password can be changed with nextcloud.occ user:resetpassword USERNAME.

See this website for further details.

Destroy a DigitalOcean machine

A list of currently active DigitalOcean machines can be obtained with doctl compute droplet list. A machine can be destroyed with doctl compute droplet delete MACHINE-ID.

Update 17 February 2025

Shortly after setting up the system, I ran into a problem when syncing a relatively large local folder with Nextcloud.

This is the command used (locally, from my client) for the syncing:

guix shell nextcloud-client -- nextcloudcmd \
    --user user@example.com \
    --password `pass show nextcloud.example.com/user@example.com` \
    --non-interactive \
    /home/user/nextcloud \
    https://nextcloud.example.com

The syncing terminated halfway-through, with various errors and/or warnings along the lines of "useless task found", "too many open files", "bulk upload" - sorry, I don't have a full copy of the messages at my fingertip now. Searching on the web, it turns out that this might be related to Nextcloud's bulkupload setting, which can be changed with:

nextcloud.occ config:system:set bulkupload.enabled --value=false --type=boolean

Possibly unnecessary, but I restarted Nextcloud after the change, just in case. This did fix the upload issue and the sync terminates successfully now. I'm not sure what the full implications of the change are, to begin with the sync seems to be slower now but I guess that's expected. No time to investigate this further at the minute, so I'll just file it under "works for me" and leave it as it is.

Outro

This should prove that self-hosting a Nextcloud instance is pretty easy if you're at ease with basic system administration tasks. Should you go through the guide and see that something is wrong or missing, do let me know. Hope you found this useful, until next!

Revision 41cb4cd.