How to install NextCloud securely

NextCloud is a free and open source cloud solution which offers a private file hosting and much more. If you have ever needed to quickly upload huge files and share them with others, NextCloud can definitely help with that. You won't need to rely on 3rd party file upload services which have major limitations. It's also great for privacy and security. You can easily set up NextCloud on your own home server or computer using Docker. But what about...
Security
Having your own personal cloud / file hosting service is great - but not if it means exposing yourself out on the big bad wolf internet where not everyone has the best intensions in mind. So we must lock it down and secure it. One possible way of doing this would be to use a Zero Trust tunnel. That way only you can access your NextCloud instance. But what if you wanted to access it from outside the Zero Trust network? That would be inconvenient. It would be much nicer if we could simply log on at cloud.techbitz.dev without having to worry about the network that we are on.
Alas, the age old dilemma of security vs convenience. The more secure something is, the less convenient it is to use. The more convenient something is to use, the less secure it is. You cannot ever have both 100%. So how do we find a good balance between security and convenience? Our solution will be to use a combination of Cloudflare, Access Rules and IP addresses to limit the public instance of NextCloud to only our own IP addresses (yes, you will need to get a static IP address or two). It helps having at least a few static IPs (home, work, phone). While at the same time exposing only the publicly shared files to the world.
Install NextCloud using Docker
The easiest way to get NextCloud up and running is to start a Docker container. Lucky for us, NextCloud provides a Docker image which we can use. You don't even need multiple containers and don't pay much attention to the warning about using SQLite database. It will work just fine.
To get started, simply create a new Docker compose project
services:
nextcloud:
image: nextcloud
container_name: nextcloud
ports:
- "20895:80"
volumes:
- nextcloud_html:/var/www/html
- nextcloud_data:/var/www/html/data
restart: unless-stopped
volumes:
nextcloud_html:
nextcloud_data:
and run $ docker compose up -d then, once it starts, head over to http://localhost:20895/ and finish the installation. And that's all as far as spinning up a NextCloud instance. Now we just make it accessible from the world.
Create a Cloudflare site with Security rules
Now head over to Cloudflare, register your domain and point it to your public IP of your home server. Now it's time to lock it down.
Now go to → Security → Security rules → Custom rules and create a new security rule. (Note: Cloudflare offers only 5 free security rules on the free plan. Cloudflare makes good money selling rules. But we can be clever and combine our rules into one big messy rule to bypass this limitation).
Cloudflare security rules work very similarly to any router Firewall. Rules are processed top-to-bottom with the first rule that matches deciding the outcome. No later rules matter once a match is found.
We will create 3 rules (although we will combine them into one or two):
- Allow access to cloud.techbitz.dev from our own IPs
- Allow public access to cloud.techbitz.dev/s/ for publicly shared files
- Drop everything else
It will work something like this.
A request arrives at Cloudflare.
- Rule #1 → “Is it from my allowed IP?”
- If yes → allowed → stop.
- If no → go to Rule #2.
- Rule #2 → “Is the URL under /s/?”
- If yes → allowed → stop.
- If no → go to Rule #3.
- Rule #3 → block everything else → stop.
Skip / Allow (our combined Allow rule)
This will have Skip / Allow action.
(http.host eq "cloud.example.com" and (ip.src in {50.11.123.202 80.13.111.108 32.100.58.67} or (ip.src.country eq "FR" and starts_with(http.request.uri.path, "/s/")))) or (http.host eq "blog.example.com" and ip.src.country eq "FR")
You will need another Drop All rule if you plan on combining rules
This will have a block / drop action.
(http.host eq "cloud.example.com") or (http.host eq "blog.example.com")
Now you (and no one else) should be able to access your NextCloud instance at https://cloud.example.com/ and share files publicly with the world.
And you should be done... there's just one small, tiny problem...
You can't actually use Cloudflare (or any 3rd party provider)
The above solution won't actually work if you plan on uploading huge files. Cloudflare will not allow you to push Gigabytes and Terabytes of files through their network. They won't sign up for that. No 3rd party service will (Cloudflare, Tailscale etc.) They are not designed for that, so they limit file upload size to a measly 100MB. But we want to upload 50GB.
Note: NuxtCloud does have support for chunked uploads (100MB/chunk) which would theoretically allow you to upload huge files through Cloudflare. But I wasn't able to get that working and besides, pushing uploads through Cloudflare is not ideal for performance either.
Cloudflare's free plan does not have a specific bandwidth limit for normal website traffic, and usage is "unmetered" as long as it complies with their Terms of Service. Even with chunking, however, disproportionate use, such as serving large files or video content, is not "officially" supported and may lead to restrictions, as this type of traffic is not included in the free tier.
That means we'll need to host this ourselves. On our own server. And bring our own security. Yeah... about that...
Host NextCloud on your own server (and secure it)
This means we'll need to disable that proxy option in Cloudflare and host NextCloud on our own server. This also means we will lose all security benefits provided to us by Cloudflare. We will need to implement all of that ourselves. Here are some general guidelines on how to do that.
- Choose a custom port: This will help you avoid common bots that scan well known ports 1 - 1024.
- Configure Nginx for security: Use Nginx as a reverse proxy to handle TLS and other security features. Create a whitelist of IP addresses that are allowed to access NextCloud. Make sure you do not expose your nginx version or anything else that could give away information about your server.
- Configure Hairpin: If you want to access NextCloud from your own network, you will need to configure hairpin NAT on your router. If you will be using a custom port, make sure to add it to the hairpin NAT configuration.
Here is an example Nginx configuration that you can use as a starting point. It will allow you to access your NextCloud instance from your own IP addresses, while still allowing access to publicly shared files.
# The Docker container running NextCloud on port 20895
upstream nextcloud_backend {
server 127.0.0.1:20895;
}
server {
server_tokens off;
listen 8654;
server_name cloud.example.com;
client_max_body_size 0;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
access_log /var/log/nginx/cloud.example.com.access.log;
error_log /var/log/nginx/cloud.example.com.error.log;
# Map any 403 (e.g. from deny all in /) to a silent drop
error_page 403 = @drop;
location @drop {
return 444; # close connection, no response, no page
}
# Public share
location ^~ /s/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass http://nextcloud_backend;
}
# Public files allowed
location ^~ /public.php {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass http://nextcloud_backend;
}
# Public avatar images (guest avatar, etc.)
location ^~ /avatar/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass http://nextcloud_backend;
}
location ^~ /core/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass http://nextcloud_backend;
}
location ^~ /apps/files_pdfviewer/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass http://nextcloud_backend;
}
# Public preview for shared files
location ^~ /apps/files_sharing/publicpreview/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass http://nextcloud_backend;
}
location = /cron.php {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass http://nextcloud_backend;
}
# Favicon
location ^~ /apps/theming/favicon/ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass http://nextcloud_backend;
}
# Public assets allowed
location ~* \.(?:css|js|mjs|woff2?|png|jpe?g|gif|webp|svg|ico)$ {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass http://nextcloud_backend;
}
# Only my IPs allowed for main instance
location / {
# Only your IPs (plus LAN/hairpin) can hit /, /login, /settings, etc.
allow 192.168.1.0/24; # LAN hairpin
allow 50.11.123.202; # home
allow 80.13.111.108; # work
allow 32.100.58.67; # mobile
deny all;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass http://nextcloud_backend;
}
}
While it might be a little inconvenient to use a custom port, it does add a little bit to security. So now you finally have a working NextCloud instance running on your own infrastructure, and your life depending on how well you keep your server updated and secured. Good luck!