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.
Ok so go here: https://docs.nextcloud.com/server/20/admin_manual/installation/nginx.html#nextcloud-in-a-subdir-of-the-nginx-webroot
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.
Top comments (2)
Hi,
I have doubled check my configurations but yet I am getting the following warning when I run "sudo nginx -t"
nginx: [warn] conflicting server name "nextcloud.example.org" on 0.0.0.0:80, ignored
nginx: [warn] conflicting server name "nextcloud.example.org" on [::]:80, ignored
nginx: [warn] conflicting server name "nextcloud.example.org" on [::]:443, ignored
nginx: [warn] conflicting server name "nextcloud.example.org" on 0.0.0.0:443, ignored
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Also, I am not able to reach my homepage of "example.org/nextcloud"
Anything I can check as to why this would occur?
Thanks for guide
Hi there!
Sorry for late reply!
Just to confirm, you are using your own domain instead of "example.org", correct?
Ok so
sudo nginx -t
tests to make sure your Nginx config file is all correct. It SHOULD look like this:Could you please check that the nextcloud's
server
block in the config files only contains these lines at the very top?It should NOT contain anything about port 80 in the nextcloud's
server
block, only port 443.