Nextcloud Setup with Nginx

Some FAQs before we begin:

Why do I use Nextcloud? Well I used to heavily use Google Drive and Dropbox, but recently I’ve started to slowly realize how much of my online life was stored with one platform. Google for example has all my contacts, calendar, and primary emails. If something were to happen to my Google account I’d lose like 90% of my online data. So instead of resorting to the big 5 to store my data, I decided to become self-reliant.

Why Nginx? Well I only have 1 server and 1 domain, and I need to serve multiple sites. Nginx server blocks allow me to serve Nextcloud, my portfolio, and my small business site all from one server. I’m sure Apache could do the same but I find that Nginx is easier to configure, easier to fix if something goes wrong, and more lightweight compared to Apache.

Why Ubuntu? It was one of the default options on the AWS startup page and I was lazy. Also it’s the most popular Linux distro so it’s probably good.

Why am I writing this? First time I installed Nextcloud it was a breeze. First time I tried to install using Nginx it was hell. Documentation is out there and it’s great, but rarely is it all in one place and easy to read through in one swoop. So I decided to write my own guide that starts from the bottom and takes you through the entire installation process and setup.

What are my qualifications? (probably not a question but I’m not gonna pass up an opportunity to put myself out there). I’m a 3rd year Software Engineering student at McMaster University. I previously completed a 3yr associate’s degree in software development. I’m planning on pursuing an MSc in Computer Science at University of Toronto in the future, but the future is still unclear.

Alright, let’s begin.

We’re first gonna install everything that we’ll need. Then we’ll set up the database, then PHP, then Nginx, then adjustments, then (hopefully) Nextcloud itself.

Dependencies

First off, let’s look at the dependencies and requirements. Nextcloud leans heavily on PHP and MySQL/MariaDB, so let’s get that out of the way first.

The MySQL database is used for storing all of the metadata that comes with running a cloud storage solution. Examples of stored data include account info, types of data that is stored, and an overall “snapshot structure” of the cloud database. Anytime you add/remove data from your Nextcloud, the database gets updated.

And MariaDB is just a community developed fork of MySQL with drop-in capability. Basically what Nextcloud is of OwnCloud (research this on your own if you’re interested).

So first update your repo list, then install MySQL:

sudo apt-get update
sudo apt-get install mysql-server -y

Ok so that’s MySQL installed. Now let’s look at the PHP stuff. I’ll be using PHP 7.4 for this installation, but if there’s a newer one out at the time you’re reading this, then use that instead. It should be the same. Should.

First add Ondrej’s PPA to your source list. Ondrej is the main package dev for PHP for Ubuntu & Debian.

sudo add-apt-repository ppa:ondrej/php

Ok now install the fundamental PHP modules:

sudo apt-get install php7.4-cli php7.4-fpm php7.4-opcache

You’ll need PHP-FPM to manage the fastcgi crap that gave me a migraine earlier, but I sort of figured it out so we’ll be fine.

Ok now install the remaining PHP dependencies:

sudo apt-get install php7.4-ctype php7.4-curl php7.4-dom php7.4-gd php7.4-iconv php7.4-json php7.4-mbstring php7.4-posix php7.4-fileinfo php7.4-imagick php7.4-zip php7.4-mysql -y

You’ll also need openssl and zip/unzip

sudo apt-get install openssl zip unzip

Ok now let’s talk Nginx.

At this point you’ll be installing apache and setting up the LAMP stack and all that. But we’ll be using Nginx here so just install that instead.

sudo apt-get install nginx

We’ll need to get SSL certification as well. I normally recommend Digicert for my clients, but since I don’t like paying for stuff I’m going to use a free version instead.

We’ll be using Let’s Encrypt. It’s actually recommended by some big name companies so it’s pretty good.

The easiest way to get an SSL cert using Let’s Encrypt is to just use certbot.

sudo apt install certbot python3-certbot-nginx

I’m assuming you have a domain, if you don’t then go to GoDaddy and get one. It’s cooler to have example.ca/nextcloud than 123.456.78.90/nextcloud. Trust me, I’m sort of an engineer.

Unpackaging Nextcloud

Speaking of Nextcloud, we should probably get that downloaded before we start any setup.

You can either go to the nextcloud website, install the zip, then push it to the server, but my preferred way is to just curl it into the server directly. I used to use wget but I prefer curl these days.

This is Nextcloud’s mirror for server downloads: https://download.nextcloud.com/server/releases/

Go there, find the latest zip file, and copy the url. Make sure you get the URL of the ZIP file, not files like .zip.sha256 or something. The sha256 is a hash used for checking file integrity.

The latest version at the time of writing is 20, so the URL that I was able to get was https://download.nextcloud.com/server/releases/latest-20.zip

Now on your server, create a directory in your home directory called ‘nextcloud’. cd into that directory, and use the curl command to download the zip as ‘nextcloud.zip’.

curl --output nextcloud.zip https://download.nextcloud.com/server/releases/latest-20.zip

Now… normally I’d just unzip it but because this is supposed to be a tutorial I’m just going to check the integrity of the download. Because good practices and all that.

Ok so you have the zip file. Now go back to the mirror site, and find the .zip.sha256 file that matches the version number you downloaded. For me, it should be this one https://download.nextcloud.com/server/releases/latest-20.zip.sha256 because I downloaded version 20.

Now download the hash:

curl --output hash_file https://download.nextcloud.com/server/releases/latest-20.zip.sha256

Ok so now you should have the hash and the zip in your nextcloud dir.

Now run this to get the sha256 hash of the nextcloud zip:

sha256sum nextcloud.zip

Now you should see something like this:

413e0d41016bfaab05d9e0dc55a1701e0bf5aaf7d588101d4237fdea3eb8abb6  nextcloud.zip

Ok now check the hash in the md5 file we downloaded:

cat checksum

And you’ll see:

413e0d41016bfaab05d9e0dc55a1701e0bf5aaf7d588101d4237fdea3eb8abb6  latest-20.zip

Ok so the checksum on the nextcloud mirror matches the checksum of the file that we downloaded, meaning the integrity is good.

Ok, NOW we can unzip it.

unzip nextcloud.zip

It’ll unzip into a folder called ‘nextcloud’

Ok, now move the nextcloud folder into /var/www. That’ll be the root of our nextcloud:

sudo mv nextcloud /var/www/

And change ownership of the nextcloud directory:

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

Btw you need to use sudo because you’re dealing with the root directory. Any operations performed in subdirectories of your own home directory (/home/you/*) don’t require sudo permissions. But any operations that you perform in the root directory outside of your home directory demand superuser.

Ok so now we’ve installed the dependencies for MySQL, PHP, Nginx, Certbot. I probably forgot a few packages to install but I’ll come back to that later once we got some of the setup finished.

MySQL Database Setup

Let’s setup the MySQL database first:

sudo mysql_secure_installation

Select ‘y’ for VALIDATE PASSWORD, ‘2’ for STRONG password, then set up a strong password. This’ll be your root db password, so don’t just forget it.

It’ll tell you the strength of the password (I got 100), then select ‘y’ to continue with the password provided.

Then select ‘y’ to remove anonymous users, ‘y’ to disallow remote logins, ‘y’ remove the test database, and finally ‘y’ again to reload the privilege tables.

Ok now log in to your MySQL db as root

sudo mysql -u root -p

It’ll ask for a password, so put in your root db password and hit enter to log in.

Ok, now we’ll do this:

Create the nextcloud db:

create database nextcloud;

Create the nextcloud user:

create user 'nextcloud'@'localhost' identified by <new_password>;

( should be another strong password, this time for the nextcloud user)

Grant nextcloud db privileges to the nextcloud user:

grant all privileges on nextcloud.* to 'nextcloud'@'localhost';

Flush the privileges and exit the db console:

flush privileges;
exit

Ok good, you got the nextcloud database setup.

Nginx setup

Ok now let’s set up the nginx server.

Make sure you have a domain at this point. You can get one at Google Domains, GoDaddy, Namecheap, etc. I use GoDaddy but you do you.

And make sure your domain name is pointed at the static IP of the server, and the DNS configuration is all correct.

I’ll be using example.ca as “my” domain in this tutorial, but just substitute it out with your own domain.

Ok so first create your nginx config file in the nginx “sites-available” directory:

sudo touch /etc/nginx/sites-available/example.ca

Ok now use vim (emacs suck) to edit the file.

sudo vim /etc/nginx/sites-available/example.ca

At this point, I initially thought that we start with a barebones configuration, then SSL, then more modifications to this file.

But now I think it’s best if we configure Nginx for Nextcloud in full, then let Certbot modify it for HTTPS.

Copy all the code from there to your /etc/nginx/sites-available/example.ca file. Make sure that it is the code from “subdir of nginx webroot” and NOT “webroot of nginx”.

Ok so if you look in the file after copying, you will boilerplate stuff like ‘cloud.example.com’ and ‘/etc/ssl/nginx/cloud.example.com.crt’.

Let’s get rid of the cloud.example.com stuff by using sed find/replace:

sudo sed -i 's/cloud.example.com/example.ca/g' /etc/nginx/sites-available/example.ca

Ok, so right now we haven’t gotten our SSL certificates yet. So let’s remove all the lines in the config file that doesn’t pertain to HTTPS. Don’t worry, certbot will add it in later.

So where it says this:

server {
    listen 443      ssl http2;
    listen [::]:443 ssl http2;
    server_name example.ca;    
    # ...    
    ssl_certificate /etc/ssl/nginx/example.ca.crt;
    ssl_certificate_key /etc/ssl/nginx/example.ca.key;

Remove the ssl between 443 and http2.

And remove the lines ssl_certificate and ssl_certificate_key.

Before we get into SSL and firewall config, let’s do this first:

Anytime you edit a configuration file in /etc/nginx, it’s always a good idea to run this:

sudo nginx -t

This checks the config files and tells you if there are any errors. You should ideally see this:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok 
nginx: configuration file /etc/nginx/nginx.conf test is successful

Ok and successful means everything is ok and the initial execution was successful. That’s good.

Ok so now you have a hopefully working config file in the sites-available nginx directory. But it’s only available, doesn’t mean that it’s enabled.

You could always copy it to the /etc/nginx/sites-enabled directory, but that would mean anytime you edit 1 config you need to update the other.

Best approach is to just create a symbolic link from the sites-enabled directory to the config file in the sites-available directory.

Think of a symbolic link like this:

The config file is a room. The door to that room is /etc/nginx/sites-available/example.ca. We want to access that room from another door. So we create another door in /etc/nginx/sites-enabled called ‘example.ca’ that links directly to our room. It’s basically the Linux version of a Windows shortcut.

sudo ln -s /etc/nginx/sites-available/example.ca /etc/nginx/sites-enabled/

If you go to /etc/nginx/sites-enabled and list all the files there, you’ll see this:

total 0 
lrwxrwxrwx 1 root root 34 Mar 25 03:19 default -> /etc/nginx/sites-available/default 
lrwxrwxrwx 1 root root 37 Mar 25 16:51 example.ca -> /etc/nginx/sites-available/example.ca

See that? example.ca points to the original example.ca file in sites-available.

SSL setup

Ok, NOW we can set up SSL and perform some modification.

This is going to be the longest section, but it’s the most important section.

*** Please read carefully and pay attention. ***

First let’s modify the firewall a little to allow https connections. HTTPS is just HTTP with S, the S standing for Secured (SSL).

First check the status of the firewall:

sudo ufw status

If you haven’t set up the firewall before, it should say this:

Status: inactive

Nice. Ok so now list the available profiles:

sudo ufw app list

For me it shows this:

Available applications: 
  Nginx Full 
  Nginx HTTP 
  Nginx HTTPS 
  OpenSSH

Ok so we want to enable Nginx Full (which is basically Nginx HTTP + HTTPS), and OpenSSH.

I bolded that last part because the first time I did this I stupidly enabled only Nginx and started up the firewall; while being SSHed into the server. Of course when the server rebooted the firewall blocked everything except Nginx and didn’t allow me to SSH in. So yeah don’t be like me.

Allow Nginx and OpenSSH by doing this:

sudo ufw allow 'Nginx Full'
sudo ufw allow 'OpenSSH'

Enable the firewall:

sudo ufw enable

And check the status:

sudo ufw status

You should see this:

Status: active 

To                         Action      From 
--                         ------      ---- 
Nginx Full                 ALLOW       Anywhere                   
OpenSSH                    ALLOW       Anywhere                   
Nginx Full (v6)            ALLOW       Anywhere (v6)              
OpenSSH (v6)               ALLOW       Anywhere (v6)

Nice.

Ok, before you use Certbot, MAKE SURE that you followed the guide properly and make sure that your nginx config file has YOUR OWN domain in it instead of example.ca.

Certbot checks the domain you give it against the domain in the config file. If you give it yourdomain.com and the config file has example.com, it'll write all the SSL stuff to the default config which is useless.

So MAKE SURE you use YOUR OWN domain name instead of example.ca in this guide.

Good? Ok so now get your SSL certificates from Let’s Encrypt using Certbot:

sudo certbot --nginx -d example.ca

It’ll ask for your email address for renewal notices, so put that in.

Then agree to the terms and conditions. I am legally obligated to advise you to read it before you agree. Not just to this but anytime you agree to anything.

Decide if you want to share your email with EFF (Electronic Frontier Foundation).

Wait for the challenges to complete.

Select ‘2’ to redirect all HTTP requests to HTTPS.

Then you should see something like this:

Congratulations! You have successfully enabled https://example.ca

Nice.

Ok so your certificates are stored in /etc/letsencrypt/live/example.ca

Let’s go there and see:

cd /etc/letsencrypt/live/example.ca/

In this directory, you will find the following files, but you only need 2 of them:

fullchain.pem this is your certificate

privkey.pem this is your certificate’s key

Those are the 2 files you need to specify in your Nginx config.

Speaking of Nginx config, let’s take a look:

sudo vim /etc/nginx/sites-available/example.ca

Nginx SSL setup

You will see that certbot has modified the config a bit for HTTPS configuration.

Adjust the file to look like this:

upstream php-handler { 
        server 127.0.0.1:9000; 
#server unix:/var/run/php/php7.4-fpm.sock; 
} 

server { 
        if ($host = example.ca) { 
                return 301 https://$host$request_uri; 
        } # managed by Certbot 


        listen 80; 
        listen [::]:80; 
        server_name example.ca; 

# Enforce HTTPS just for `/nextcloud` 
        location /nextcloud { 
                return 301 https://$server_name$request_uri; 
        } 


} 

server { 
        listen 443 ssl http2; 
        listen [::]:443 ssl http2; 
        server_name example.ca; 

# Use Mozilla's guidelines for SSL/TLS settings 
# https://mozilla.github.io/server-side-tls/ssl-config-generator/ 
        ssl_certificate /etc/letsencrypt/live/example.ca/fullchain.pem; # managed by Certbot 
                ssl_certificate_key /etc/letsencrypt/live/example.ca/privkey.pem; # managed by Certbot 

# HSTS settings

Basically you need to re-add the ‘ssl’ between ‘443’ and ‘http2’ in the nextcloud server block. Remove most of the stuff in the certbot server block, and add in the SSL certificates.

Your final config file should look something like this:

upstream php-handler {
        server 127.0.0.1:9000;
#server unix:/var/run/php/php7.4-fpm.sock;
}

server {
        if ($host = example.ca) {
                return 301 https://$host$request_uri;
        } # managed by Certbot


        listen 80;
        listen [::]:80;
        server_name example.ca;

# Enforce HTTPS just for `/nextcloud`
        location /nextcloud {
                return 301 https://$server_name$request_uri;
        }
}

server {
        listen 443  ssl http2;
        listen [::]:443 ssl http2;
        server_name example.ca;

# Use Mozilla's guidelines for SSL/TLS settings
# https://mozilla.github.io/server-side-tls/ssl-config-generator/
        ssl_certificate /etc/letsencrypt/live/example.ca/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/example.ca/privkey.pem; # managed by Certbot

# HSTS settings
# WARNING: Only add the preload option once you read about
# the consequences in https://hstspreload.org/. 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;

# Path to the root of the domain
                root /var/www;

        location = /robots.txt {
                allow all;
                log_not_found off;
                access_log off;
        }

location ^~ /nextcloud {
# set max upload size
                client_max_body_size 512M;
                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/vnd.ms-fontobject 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 `/nextcloud/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/ /nextcloud/index.php$request_uri`
# always provides the desired behaviour.
                index index.php index.html /nextcloud/index.php$request_uri;

# Rule borrowed from `.htaccess` to handle Microsoft DAV clients
                location = /nextcloud {
                        if ( $http_user_agent ~ ^DavClnt ) {
                                return 302 /nextcloud/remote.php/webdav/$is_args$args;
                        }
                }

# Rules borrowed from `.htaccess` to hide certain paths from clients
                location ~ ^/nextcloud/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)    { return 404; }
                location ~ ^/nextcloud/(?:\.|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
# `/nextcloud/index.php` to the URI, resulting in a HTTP 500 error response.
                location ~ \.php(?:$|/) {
                        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;
                }

                location ~ \.(?:css|js|svg|gif)$ {
                        try_files $uri /nextcloud/index.php$request_uri;
                        expires 6M;         # Cache-Control policy borrowed from `.htaccess`
                                access_log off;     # Optional: Don't log access to assets
                }

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

                location /nextcloud {
                        try_files $uri $uri/ /nextcloud/index.php$request_uri;
                }
        }
        location = /favicon.ico { 
                log_not_found off; 
        }


}

Now, run this again to check the file:

sudo nginx -t

Hopefully, we see this:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok 
nginx: configuration file /etc/nginx/nginx.conf test is successful

Final modifications

As of now, we’ve installed the dependencies, unpackaged Nextcloud, set up Nginx, gotten SSL certificates, and altered the nginx configs.

Nice, we are finished majority of the work. However, we still have some modifications to do.

First, update the example.ca configuration file in sites-available.

Scroll down to the bottom of the page, and add this to the nextcloud server block:

location = /favicon.ico { 
                log_not_found off; 
}

It should look like this when you’re done:

location /nextcloud { 
                        try_files $uri $uri/ /nextcloud/index.php$request_uri; 
                } 
        } # closing brace of location ^~ /nextcloud

        location = /favicon.ico { 
                log_not_found off; 
        } 

} # closing brace of server block

Notice how it’s before the final closing brace of the server block.

Ok, now run sudo nginx -t again to see if everything is good:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok 
nginx: configuration file /etc/nginx/nginx.conf test is successful

Nice.

Now edit this file:

sudo vim /etc/php/7.4/fpm/pool.d/www.conf

Scroll down to where it says

listen = /run/php/php7.4-fpm.sock

Replace it with

listen = 127.0.0.1:9000

Ok, now scroll down to where it says

;listen.allowed_clients = 127.0.0.1

And remove the ; to uncomment it.

You already ran this before, but for some reason it didn't work for me so just run it again:

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

Ok now reload the php service:

sudo systemctl reload php7.4-fpm.service

And reload nginx while you’re at it:

sudo systemctl reload nginx.service

Now reboot your server.

And go to example.ca/nextcloud (again, replace example.ca with your own domain name).

You should see the nextcloud get started page. If not, then let me know in the comments and I'll try to help you out.

Ok you’re done. You’ve installed Nextcloud on Nginx from scratch.

Well done. 👍

Please leave comments if you are having trouble with your Nextcloud installation or if you are stuck somewhere in this tutorial. I check the comments frequently and will reply to you if you are in need of help.

33