NextCloud multi-tenant without docker

Nextcloud version : 20.0.1
Operating system and version : Ubuntu 20.04
Apache or nginx version : Nginx 1.18.0
PHP version : 7.4.3

We want to use NextCloud for different companies (multi-tenant). Will it be correct to install multiple NextCloud execs (/var/www/nextcloud/company1 and /var/www/nextcloud/company2) with different nginx config files, different databases and different file storage folders. Are there risks of data leakage from their company1 to company2, will the load on the server greatly increase?

nginx conf company1

upstream php-handler {
    server unix:/var/run/php/php7.4-fpm.sock;
}
server {
    listen 80;
    listen [::]:80;
    server_name company1.nextcloud.kz;
    return 301 https://$server_name:443$request_uri;
}
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name company1.nextcloud.kz;
    ssl_certificate /etc/ssl/certs/company1.nextcloud.kz.crt;
    ssl_certificate_key /etc/ssl/private/company1.nextcloud.kz.rsa;
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;" always;
    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;
   fastcgi_hide_header X-Powered-By;
    root /var/www/nextcloud/company1;
    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }
    location = /.well-known/carddav {
      return 301 $scheme://$host:$server_port/remote.php/dav;
    }
    location = /.well-known/caldav {
      return 301 $scheme://$host:$server_port/remote.php/dav;
    }

    client_max_body_size 512M;
    fastcgi_buffers 64 4K;

    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;

    location / {
        rewrite ^ /index.php;
    }
	location /netdata {
	return 301 /netdata/;
	}
	location ~ /netdata/(?<ndpath>.*) {
	auth_basic "Shelya Parolen Vpisaten";
	auth_basic_user_file /etc/nginx/netdata-access;
	proxy_http_version 1.1;
	proxy_pass_request_headers on;
	proxy_set_header Connection "keep-alive";
	proxy_store off;
	proxy_pass http://netdata/$ndpath$is_args$args;
	gzip on;
	gzip_proxied any;
	gzip_types *;
	}


    location ~ ^\/(?:build|tests|config|lib|3rdparty|templates|data)\/ {
        deny all;
    }
    location ~ ^\/(?:\.|autotest|occ|issue|indie|db_|console) {
        deny all;
    }

    location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/proxy)\.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;
        fastcgi_param front_controller_active true;
        fastcgi_pass php-handler;
        fastcgi_intercept_errors on;
        fastcgi_request_buffering off;
    }

    location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) {
        try_files $uri/ =404;
        index index.php;
    }

    location ~ \.(?:css|js|woff2?|svg|gif|map)$ {
        try_files $uri /index.php$request_uri;
        add_header Cache-Control "public, max-age=15778463";
        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;

        access_log off;
    }

    location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$ {
        try_files $uri /index.php$request_uri;
        access_log off;
    }
}

nginx conf company2

server {
    listen 80;
    listen [::]:80;
    server_name company2.nextcloud.kz;
   return 301 https://$server_name:443$request_uri;
}
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name company2.nextcloud.kz;
    ssl_certificate /etc/ssl/certs/company2.nextcloud.kz.crt;
    ssl_certificate_key /etc/ssl/private/company2.nextcloud.kz.rsa;
    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;
    fastcgi_hide_header X-Powered-By;
    root /var/www/nextcloud/company2;
    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }
    location = /.well-known/carddav {
      return 301 $scheme://$host:$server_port/remote.php/dav;
    }
    location = /.well-known/caldav {
      return 301 $scheme://$host:$server_port/remote.php/dav;
    }
    client_max_body_size 512M;
    fastcgi_buffers 64 4K;
    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;
    location / {
        rewrite ^ /index.php;
    }
	location /netdata {
	return 301 /netdata/;
	}
	location ~ /netdata/(?<ndpath>.*) {
	auth_basic "Shelya Parolen Vpisaten";
	auth_basic_user_file /etc/nginx/netdata-access;
	proxy_http_version 1.1;
	proxy_pass_request_headers on;
	proxy_set_header Connection "keep-alive";
	proxy_store off;
	proxy_pass http://netdata/$ndpath$is_args$args;
	gzip on;
	gzip_proxied any;
	gzip_types *;
	}
    location ~ ^\/(?:build|tests|config|lib|3rdparty|templates|data)\/ {
        deny all;
    }
    location ~ ^\/(?:\.|autotest|occ|issue|indie|db_|console) {
        deny all;
    }
    location ~ ^\/(?:index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+|.+\/richdocumentscode\/proxy)\.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;
        fastcgi_param front_controller_active true;
        fastcgi_pass php-handler;
        fastcgi_intercept_errors on;
        fastcgi_request_buffering off;
    }
    location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) {
        try_files $uri/ =404;
        index index.php;
    }
    location ~ \.(?:css|js|woff2?|svg|gif|map)$ {
        try_files $uri /index.php$request_uri;
        add_header Cache-Control "public, max-age=15778463";
        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;
        access_log off;
    }
    location ~ \.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$ {
        try_files $uri /index.php$request_uri;
        access_log off;
    }
}

nexcloud conf company1

<?php
$CONFIG = array (
  'instanceid' => '###',
  'passwordsalt' => '###',
  'secret' => '###',
  'trusted_domains' => 
  array (
    0 => 'company1.nextcloud.kz',
    1 => '###',
  ),
  'trusted_proxies' => 
  array (
    0 => '###',
    1 => '###',
  ),
  'datadirectory' => '/mnt/ncdata/company1',
  'dbtype' => 'mysql',
  'version' => '20.0.1.1',
  'overwrite.cli.url' => 'https://company1.nextcloud.kz',
  'overwritehost' => 'company1.nextcloud.kz',
  'overwriteprotocol' => 'https',
  'dbname' => '###',
  'dbhost' => '###',
  'dbport' => '',
  'dbtableprefix' => 'oc_',
  'mysql.utf8mb4' => true,
  'dbuser' => '###',
  'dbpassword' => '###',
  'installed' => true,
  'versions_retention_obligation' => 'auto, 365',
  'mail_smtpmode' => 'smtp',
  'mail_sendmailmode' => 'smtp',
  'mail_smtphost' => '###',
  'mail_smtpport' => '25',
  'mail_from_address' => '###',
  'mail_domain' => '###',
  'memcache.locking' => '\\OC\\Memcache\\Redis',
  'memcache.distributed' => '\\OC\\Memcache\\Redis',
  'redis' => 
  array (
    'host' => '###',
    'port' => 6379,
  ),
  'memcache.local' => '\\OC\\Memcache\\APCu',
  'log_type' => 'file',
  'logfile' => '/var/log/nextcloud/company1-nextcloud.log',
  'loglevel' => '2',
  'log.condition' => 
  array (
    'apps' => 
    array (
      0 => 'admin_audit',
    ),
  ),
  'remember_login_cookie_lifetime' => '1800',
  'log_rotate_size' => '10485760',
  'trashbin_retention_obligation' => 'auto, 180',
  'simpleSignUpLink.shown' => false,
  'default_language' => 'ru',
  'defaultapp' => 'files',
  'auth.bruteforce.protection.enabled' => true,
  'maintenance' => false,
  'encryption.legacy_format_support' => false,
  'encryption.key_storage_migrated' => false,
  'skeletondirectory' => '',
);

nextcloud conf company2

<?php
$CONFIG = array (
  'instanceid' => '###',
  'passwordsalt' => '###',
  'secret' => '###',
  'trusted_domains' => 
  array (
    0 => 'company2.nextcloud.kz',
    1 => '###',
  ),
  'trusted_proxies' => 
  array (
    0 => '###',
    1 => '###',
  ),
  'datadirectory' => '/mnt/ncdata/company2',
  'dbtype' => 'mysql',
  'version' => '20.0.1.1',
  'overwrite.cli.url' => 'https://company2.nextcloud.kz',
  'overwritehost' => 'company2.nextcloud.kz',
  'overwriteprotocol' => 'https',
  'dbname' => '###',
  'dbhost' => '###',
  'dbport' => '',
  'dbtableprefix' => 'oc_',
  'mysql.utf8mb4' => true,
  'dbuser' => '###',
  'dbpassword' => '###',
  'installed' => true,
  'versions_retention_obligation' => 'auto, 365',
  'mail_smtpmode' => 'smtp',
  'mail_sendmailmode' => 'smtp',
  'mail_smtphost' => '###',
  'mail_smtpport' => '25',
  'mail_from_address' => '###',
  'mail_domain' => '###',
  'memcache.locking' => '\\OC\\Memcache\\Redis',
  'memcache.distributed' => '\\OC\\Memcache\\Redis',
  'redis' => 
  array (
    'host' => '###',
    'port' => 6379,
  ),
  'memcache.local' => '\\OC\\Memcache\\APCu',
  'log_type' => 'file',
  'logfile' => '/var/log/nextcloud/company2-nextcloud.log',
  'loglevel' => '2',
  'log.condition' => 
  array (
    'apps' => 
    array (
      0 => 'admin_audit',
    ),
  ),
  'remember_login_cookie_lifetime' => '1800',
  'log_rotate_size' => '10485760',
  'trashbin_retention_obligation' => 'auto, 180',
  'simpleSignUpLink.shown' => false,
  'default_language' => 'ru',
  'defaultapp' => 'files',
  'auth.bruteforce.protection.enabled' => true,
  'maintenance' => false,
  'encryption.legacy_format_support' => false,
  'encryption.key_storage_migrated' => false,
  'skeletondirectory' => '',
);

They have to use different php processes with different permissions so that the processes in company1 can’t access the files of company2.

They still share the same machine and the same resources, so if one uses too much resources there will be an effect on the others. On virtual machines, you could probably distribute the resources better but you would still share things that might go wrong, so the best is to have two physically different machines.

Is it better to use docker for a multi-tenant? We will need to split (in the future) dozens of companies, and it is not rational to create a virtual machine for each company. And NextCloud doesn’t have a multi-tenant like SeaFile or FileCloud.

If you set up the permissions correctly, you can separate them. Not sure about all the docker stuff, I haven’t used it so far. If you want more separation and virtual machines are too complicated, you might have a look into jails especially if you are familiar with the BSD world.

Do you mean the separation of PHP processes and everything I described above? So far, I do not understand how to scale NextCloud to increase the number of split companies without having a separate virtual machine for each company.

Separating different websites is more general, you should be able to find plenty of resources how to separate the processes: https://www.digitalocean.com/community/tutorials/how-to-host-multiple-websites-securely-with-nginx-and-php-fpm-on-ubuntu-14-04
And with the jails in BSD: https://ramsdenj.com/2017/06/05/nextcloud-in-a-jail-on-freebsd.html

I hope it will be in the far future and you’ll have enough time to get more familiar with this topic. If you have critical setups, you shouldn’t rely on community support forums and consider professional support.

1 Like

Docker would allow you to run several separate instances in the same server. I don’t know that I would call it “multi-tenant” as each instances would be a separate web server with a separate database.

I disagree. Compartmentalizing in this manner is very rational and also a good practice for a number of reasons (stability and security being among them).

1 Like