In my adventure to put some order in my personal photos library, and this new view of self-hosting as much as possible, I ended up installing a Nextcloud instance in my Raspberry Pi 4. It's about a month and a half since I've been playing around with it so I consider it stable enough to stay with me.

In this article I intend to log the actions to have it up and running

What is in this article

We're going to install Nextcloud on a Rasberry Pi 4 running Debian (or Raspbian, or the Raspberry OS as they call it now) using Docker:

  1. Why would I want that?
  2. What do I need?
  3. Install and setup Docker and the needed containers
  4. Initial setup
  5. Wrapping up
  6. References

I am basically following this article I found (thanx chrisbeardy, nice job!), but some things are not working the same for me and I had to fix some points in a different way (and that's why this article!)

Why I do want to self-host my Cloud service

I have some premises here that are important for me:

  • The Cloud concept is just someone else's computer. This actually tries to break down the magic spell around the word "cloud", and assuming automatisations and autoscalings, it's nothing else than a set of computers owned by somebody, that one day may choose to shut down the service or to change the rules, leaving you out of your own content.
  • My personal photos must never leave my phone. This has been my rule until now, but with this cloud concept of backing up automagically your photos, I rephrase it as "My personal photos must never be stored anywhere where I don't have control".
  • Nowadays One can to set up an infrastructure to provide myself cloud features. And this is what I'm going to explain in this article.

I have no idea about any of this

What do I need

Even this section appears big, we "just" need a machine to install the Nextcloud, and DNS & internet access to our machine. Breaking this down in parts, what we need is:

Raspberry Pi 4 with the official Raspberry OS

I will use my Raspberry Pi 4 4GB to host it. It is not the most powerful one but it demonstrated to work pretty well for a single user / household use. I have installed the official Raspberry OS 64 bits Lite version (no desktop). The steps are the same that I explained in my past article Quick DNS server in a Raspberry Pi, but making sure that we're selecting the 64 bits Lite version. What do you want to have setup in your Raspberry Pi?

  • SSH access enabled (I will assume that the user is xavi)
  • Static IP (I will assume that you have a local DNS and the host is called tatooine. If not, just replace the host name with your Raspberry's IP)
  • no-ip script installed (see below)

DNS domain

You want to access to your Raspberry through the open internet, so you want to have a domain set up. In my case I simply reused a domain I already own and set up a subdomain that points to my home address. To go a bit deeper, I like to use the combination of:

  1. A Dynamic DNS service that will point to my home's public IP (i.e.
  2. A no-ip script in my local host that automatically updates my public IP into the Dynamic DNS registry (yeah, IPs change)
  3. A subdomain setup on my domain that points to the Dynamic DNS entry, via a CNAME entry (i.e. ->

Service provider that does not block your ports

Some ISPs have a firewall by default that automatically block requests to your ports, or even allow you just a small subset in a strange ports range. This is a problem as you won't be able to access to your Raspberry from the internet, and all functionality on the go is gone.

I live in Germany and (luckily) my ISP is 1&1 (I am so happy that I could get rid of Vodafone). 1&1 has a firewall that they can disconnect for you if you request it via email to For other ISPs you'll need to check it with them.

A router that allows you to set up port redirection

Some ISPs deliver to the customers routers that have lot of functionalities sliced down. In my case I own the router so I have full control of it and it's configuration.

Install and setup Docker and the needed containers

Open the ports first!

Before we start, and assuming that you already have your Raspberry up and running, go to the router and forward the ports 80 and 443 to the Rasberry Pi's host.

Install Docker

  1. SSH to the Rasberry

    ssh xavi@tatooine
  2. Download the Docker installer

    curl -fsSL -o
  3. Install Docker

  4. Add my current user (that's what $LOGNAME refers to) into the docker group

    sudo usermod -aG docker $LOGNAME
  5. Logout and SSH back again

  6. Test that the docker is installed correctly by running a stupid container

    docker container run hello-world
  7. Get the ID of this container from the list of running containers

    docker ps -a
  8. Remove the container. We just wanted to test that it works:


Visit the Docker website for more info.

Install portainer

This is not a required step, however portainer is a useful tool for setting up and managing docker containers so we are going to install this to make our lives easier to manage our containers later in the future.

  1. Create a volume for the portainer's data

    docker volume create portainer_data
  2. Run a new container for portainer:

    docker run -d -p 8000:8000 -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce
  3. Navigate to the portainer site to end up the configuration: http://tatooine:9000

  4. Set up the admin username and password.

  5. Select Docker as the container environment to manage.

You'll be left into the portainer homepage of your instance. Visit their docs for more information.

Install docker-compose

We'll manage the containers by using docker-compose. Assuming that you're already SSHed into tatooine:

  1. Install docker-compose
    sudo apt install docker-compose

Set up the reverse proxy stack

We are going to use Nginx Proxy Manager for our reverse proxy, but as we are only running on a raspberry pi will setup using sqlite and not a full SQL database. Assuming that you're already SSHed into tatooine:

  1. Create a new proxy in Docker

    docker network create proxy
  2. We'll create all our services in our home. Move there.

    cd ~
  3. Create a directory for the proxy and move there

    mkdir reverse-proxy
    cd reverse-proxy
  4. Create a docker-compose.yml file in this reverse-proxy directory by editing a new file:

    nano docker-compose.yml
  5. Paste the following content:

    version: "3"
        external: true
        image: "jc21/nginx-proxy-manager:latest"
        restart: always
            - "80:80"
            - "443:443"
            - "81:81"
            DB_SQLITE_FILE: "/data/database.sqlite"
            DISABLE_IPV6: "true"
            - ./data:/data
            - ./letsencrypt:/etc/letsencrypt
            - proxy
  6. Save the file (ctrl + o) and exit (ctrl + x)

  7. Now create the container

    docker-compose up -d
  8. Navigate to the Proxy manager page: http://tatooine:81

  9. Enter the following default credentials. You're requested to change them right after.

    Password: changeme

Set up the Nextcloud stack

Now comes the Nextcloud itself. One more time, assuming that you're already SSHed into tatooine:

  1. Move to the home directory where we settle all stacks:

    cd ~
  2. Create and move to a new nextcloud directory

    mkdir nextcloud
    cd nextcloud
  3. Create the conf.d directory

    mkdir conf.d
  4. Create a docker-compose.yml file in this nextcloud directory by editing a new file:

    nano docker-compose.yml
  5. Paste the following content:

    version: '3'
            name: proxy
        image: nextcloud
        container_name: nextcloud
        restart: always
            - nextcloud-data:/var/www/html
            - MYSQL_PASSWORD=****
            - MYSQL_DATABASE=nextcloud
            - MYSQL_USER=nextcloud
            - MYSQL_HOST=nextcloud-db
            - PHP_UPLOAD_LIMIT=10G
            - PHP_MEMORY_LIMIT=512M
            - frontend
            - backend
        image: mariadb
        restart: always
        command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW --innodb-file-per-table=1 --skip-innodb-read-only-compressed
            - nextcloud-db:/var/lib/mysql
            - MYSQL_ROOT_PASSWORD=****
            - MYSQL_PASSWORD=****
            - MYSQL_DATABASE=nextcloud
            - MYSQL_USER=nextcloud
            - backend
  6. Set the password that you want to use by replacing it into the **** placeholders

  7. Save the file (ctrl + o) and exit (ctrl + x)

  8. Now create the container

    docker-compose up -d

Set up the proxy and SSL certificates

What we want to do now is to make the communication secure. It is going to manage our personal data, so we want the communication encrypted.

Navigate back to the Nginx Proxy Manager Web UI and set up a SSL certificate for your domain using web UI on the SSL Certificates tab. This will only be possible once the domain has propagated.

From the Hosts tab, set up a new proxy host for your domain and point it to the hostname nextcloud-app on port 80. Note as the docker compose was set up using the external network the containers can resolve themselves using their names. On the SSL tab, select the certificate created earlier and then select the Force SSL, HTTP/2 Support and HSTS Enabled options.

Initial setup

At this point we should be able to navigate to the domain / subdomain we created (remember the above ?) and login screen should appear. We should also see the secure connection set up. Now:

Create the admin user and untick the Install recommended apps, for performance reasons using the Raspberry Pi, install only what you need using the Nextcloud web gui later. After a short while, and possibly a couple of page refreshes, you should be able to log in and upload/download files from the nextcloud web gui.

Sometimes after the login the site does not redirect to the dashboard and presents the login screen again. Just login again.

Preparing the system for further configuration

Next steps are meant to polish the installation and to solve some warnings that appear. Mostly all warnings require that we do something internally to the instance, meaning to log in into the shell of the container. To do so we can use portainer or we can shell via the docker command. Other relate to set up specific configurations that we can do so by editing the configuration file or by executing the occ tool. The following initial section is meant to setup the system so that configuring the system is easier later on. Bear with me.

shell using Portainer

This is one of the methods to enter into the container's shell.

  1. Navigate to the Portanier web (remember: http://tatooine:9000/) and enter the credentials.
  2. Click on Containers in the left menu
  3. Click on the shell icon in the nextcloud_nexcloud-app_1 container, under the Quick Actions column

A shell will open

shell using the docker command

This is another method to enter into the container's shell. Again, assuming that you're already SSHed into tatooine:

  1. Execute the following command:
    docker exec -it nextcloud_nextcloud-app_1 /bin/bash

Updating the container's inner system and installing a shell's file editor

Dealing with configuration files means that we need to edit them, and to do so we better install nano in our system. So once we are in the shell of our container, execute:

apt update && apt install nano

This will update the references of our packages and install the nano editor.

Setting up an external alias for the occ tool

There is a command line utility called occ that you’ll need from time to time. occ is inside the Nextcloud Docker container. To make life easier, we can add a bash alias in the Raspberry Pi's shell so that we can easily run occ commands from outside the container. Then, from the shell of the Raspberry (to be clear, outside of the container's shell):

  1. Add a new alias in our home's .bashrc file pointing to the tool inside the container:

    echo alias occ=\'docker exec -it -u www-data nextcloud_nextcloud-app_1 php occ\' >> ~/.bashrc
  2. Reload the .bashrc file so the alias is loaded:

    source ~/.bashrc

From now on, we can execute the occ from outside the container comfortably. Try:

occ config:system:get overwritehost

Remove setup warnings

Once logged in, click in the upper left user icon (your user icon!) and a dropdown menu appears. There click in Administration settings. You should be directed to the Overview section of the Administration settings page. There there is a subsection Security & setup warnings with some issues that we need to solve.

php-imagick warning

In the shell of the container, execute:

apt install libmagickcore-dev

Set the default phone region

In the shell of the container:

  1. Edit the file /var/www/html/config/config.php:

    nano /var/www/html/config/config.php
  2. At the end of the file, before the line that contains );, add a line with the following key value pairs:

    'default_phone_region' => 'DE',

    Note that I placed the country code of Germany. You please add instead the one belonging to your country based on this page: ISO 3166-1 code.

  3. Save the file (ctrl + o) and exit (ctrl + x)

Fix the caldav and carddav issues

These appear only after installing the calendar apps, but just for the note to fix them:

  1. Navigate to the Nginx Manager (remember: http://tatooine:81/) and enter the credentials

  2. Click in the Hosts top menu

  3. Click in the 3 vertical dots of the line of your Nextcloud proxy host (most likely will appear as A dropdown will pop up.

  4. Select Edit in the pop up menu. A modal window will appear

  5. Select the Advanced option from the top menu in the modal.

  6. In the Custom Nginx configuration add the following:

    location /.well-known/carddav {
    return 301;
    location /.well-known/caldav {
    return 301;
  7. Click on Save

Allow access from Nextcloud sync apps

When accessing from the Android / iOS client apps, I suffered from a strange error that blocked me to log in, with a message telling Strict mode, no HTTP connection allowed!. Digging deeper, from the Chrome Developer Tools Console I saw an error telling Refused to send form data from because it violates the following Content Security Policy directive: "form-action 'self'".

Turned out that it's a trusted proxies issue and gets fixed executing the following occ commands (remember that we're using our nice alias!) in the Raspberry shell:

occ config:system:set trusted_proxies 1 --value=''
occ config:system:set overwritehost --value=""
occ config:system:set overwriteprotocol --value="https"

Setting up a mail service

As this is a personal / home instance, I will reuse a spare Google account I have around as emailer. Google makes it a bit tricky to send emails as it now requires to activate the less secure app access config for the account. Take a look at this Google Answer to learn how to do so.

Then, from the Nextcloud Admin Settings page, move to the Basic Settings page and set up the SMTP service as follows:

  • send mode ->
  • encryption -> SSL/TLS
  • authentication method -> login
  • authentication required -> YES
  • server address -> : 465
  • credentials are your email address and password (or an app password if using 2FA on google).

Wrapping up

That was a long read, but it's done faster than it seems. It took me a while to discover how to fix some of the issues, and that's why I include a closing References section. It's now around 2 months having my personal Nextcloud instance up and running with no issues, having it as a cloud service that auto-magically backs up the pics of my phone, syncs my Calendar and Bookmarks, and brings me a space to set my tasks and notes.

I am fascinated by the personal cloud service concept, and I would expect some more articles coming with some specifics like using an external storage, the proper set up for the photos cloud backup, some fine tuning for the pictures indexing, having LibreOffice available as an online doc tool too...


Previous Post Next Post