Nextcloud and OnlyOffice on Ubuntu 18.04 LTS

Author: Paul Sueno
Created: 5/23/2020 // Updated: 9/6/2020


Set up a private document server with seamless integration of productivity apps, built on Ubuntu 18 modified LEMP stack.

This tutorial will show you how to set up Nextcloud and build OnlyOffice on top of that. It allows you to store whatever documents you want and have access to it wherever the internet is available. We will use this as a backend for a suite of free productivity software called OnlyOffice. This is like having your own Google Documents or MS Office 365 and having full control over it. Our user access will be based on emails, authenticated by Dovecot IMAP through a PostgreSQL database of virtual users. In other words, you will have your own web-based private document server. Both Nextcloud and OnlyOffice have paid versions of their products. This tutorial will show you how to set up the free versions.

If you haven't already:

Nextcloud and OnlyOffice minimum requirements

Ever worry about having a handful of global companies have control over your documents? You wouldn't be reading this if you haven't thought of it. You can read more about the Nextcloud document server and OnlyOffice productivity suite on their sites.

Before we get going with installing these services, your server (I recommend cloud based) should meet OnlyOffice's minimum requirements. The information is below is accurate as of the time of writing this blog.

  • CPU single core 2 GHz or better
  • RAM 2 GB or more
  • HDD at least 40 GB of free space
  • Additional requirements at least 4 GB of swap

If you followed along in my tutorial, you probably selected Linode's Nanode. However, this won't meet the requirements. You may have to update your cloud server product to meet the requirement. You may also have to adjust your storage partitions to match the 4 GB swap minimum requirement.

Set up Let's Encrypt TLS and Nginx

As with the other web apps installed in some of the other tutorials on my site, I use Nginx virtual hosts and subdomains for my registered domain. I will do the same for Nextcloud and OnlyOffice. For the document server, I will use the subdomain drive; for the productivity suite, I will use apps. To be consistent with my tutorials, the FQDN would be and Be sure to replace your own registered domain for Remember this, because I won't be consistent with my reminders later on.

These instructions are based on the official installation documentation by Nextcloud, and tailored to our system build. Let's create the Nginx virtual host for Nextcloud by running sudo nano /etc/nginx/sites-available/ Note the warning about HTTP Strict-Transport-Security by reading about it at

upstream php-handler { server unix:/run/php/php7.2-fpm.sock; }
server {
  listen 80;
  listen [::]:80;
  return 301$request_uri;
server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;

  ssl_certificate /etc/letsencrypt/live/;
  ssl_certificate_key /etc/letsencrypt/live/;
  ssl_trusted_certificate /etc/letsencrypt/live/;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  ssl_dhparam /etc/ssl/private/dhparam.pem;
  ssl_stapling on;
  ssl_stapling_verify on;

  # Add headers to serve security related headers
  add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
  add_header Referrer-Policy "no-referrer";
  # WARNING: Only add the preload option once you read about
  # the consequences in 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 X-Content-Type-Options nosniff;
  add_header X-XSS-Protection "1; mode=block";
  add_header X-Robots-Tag none;
  add_header X-Download-Options noopen;
  add_header X-Permitted-Cross-Domain-Policies none;
  add_header X-Frame-Options "SAMEORIGIN" always;
  # Remove X-Powered-By, which is an information leak
  fastcgi_hide_header X-Powered-By;

  # Path to the root of your installation
  root /usr/share/nginx/drive;

  location = /robots.txt {
    allow all;
    log_not_found off;
    access_log off;
  location = /.well-known/carddav { return 301 $scheme://$host/remote.php/dav; }
  location = /.well-known/caldav { return 301 $scheme://$host/remote.php/dav; }
  location ^~ /.well-known/acme-challenge/ {
    default_type "text/plain";
    #####root /usr/share/nginx/drive;

  # 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/ 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 ~ ^/(?: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\/.+)\.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 $fastcgi_path_info;
    fastcgi_param HTTPS on;
    #Avoid sending the security headers twice
    fastcgi_param modHeadersAvailable true;
    fastcgi_param front_controller_active true;
    fastcgi_pass php-handler;
    fastcgi_intercept_errors on;
    fastcgi_request_buffering off;

  location ~ ^/(?:updater|ocs-provider)(?:$|/) {
    try_files $uri/ =404;
    index index.php;

  # Adding the cache control header for js and css files
  # Make sure it is BELOW the PHP block
  location ~ \.(?:css|js|woff|svg|gif)$ {
    try_files $uri /index.php$request_uri;
    add_header Cache-Control "public, max-age=15778463";
    # Add headers to serve security related headers (It is intended to
    # have those duplicated to the ones above)
    # Before enabling Strict-Transport-Security headers please read into
    # this topic first.
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";
    # WARNING: Only add the preload option once you read about
    # the consequences in 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 X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Robots-Tag none;
    add_header X-Download-Options noopen;
    add_header X-Permitted-Cross-Domain-Policies none;
    # Optional: Don't log access to assets
    access_log off;

  location ~ \.(?:png|html|ttf|ico|jpg|jpeg)$ {
    try_files $uri /index.php$request_uri;
    # Optional: Don't log access to other assets
    access_log off;

OnlyOffice requires its own Nginx virtual host, though it does not have a user interface at all through that virtual host. The documents are created and edited through Nextcloud directly. Nextcloud accesses OnlyOffice through the virtual host. It'll make sense once it's all put together. For now, we will create a temporary Nginx virtual host configuration file for OnlyOffice; so that we can create the Let's Encrypt Certbot TLS certificate. Once we install OnlyOffice, it will automatically install it's own Nginx virtual host configuration files. We'll then disable the temporary one we will be creating shortly and modify the installed files. Let's create the temporary OnlyOffice virtual host file by running sudo nano /etc/nginx/sites-available/

server {
  listen 80;
  listen [::]:80;
  root /usr/share/nginx/apps;
  index index.php index.html;
  location / { try_files $uri $uri/ =404; }

Now that the virtual host configuration files have been created, we have link them to the enabled directory.

sudo ln -s /etc/nginx/sites-available/ /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/ /etc/nginx/sites-enabled/
sudo service nginx reload

For the TLS certificates to be created, we need to set up the webroot directories for each Nginx virtual host.

sudo mkdir -p /usr/share/nginx/drive/.well-known/acme-challenge/
sudo mkdir -p /usr/share/nginx/apps/.well-known/acme-challenge/
sudo chown -R www-data:www-data /usr/share/nginx

To create the TLS certificate, be sure to note which subdomains have already been associated with your primary TLS certificate. Run this command to find out: sudo certbot certificates. We will also need to recall what the webroot directories are for each subdomain. If you've followed along with the tutorial, your command to expand the Let's Encrypt Certbot TLS certificates should be similar to the example below.

sudo certbot certonly --webroot \
  -w /usr/share/nginx/html -d -d -d \
  -w /usr/share/nginx/db -d \
  -w /usr/share/nginx/mail -d \
  -w /usr/share/nginx/drive -d \
  -w /usr/share/nginx/apps -d

Make sure to select (E)xpand and that the message Congratulations! Your certificate and chain have been saved shows up.

Nextcloud - required packages and install files

As you may imagine, a document server will be resource intensive. We will need to be as efficent as possible for the resources we pay for in our cloud server. Nextcloud allows memory caching, and I chose to do it through Redis because OnlyOffice also required it. The FFmpeg allows our media documents to be played. The other packages are required for Nextcloud's to work with the proper PHP extensions.

Update the apt repositories and install the prerequisite packages.

sudo apt update
sudo apt install php-apcu php7.2-bz2 php7.2-curl php7.2-gd php-imagick php7.2-intl php7.2-json php7.2-mbstring php7.2-pgsql php-redis php7.2-xml php7.2-zip ffmpeg redis-server

Now that the required packages are installed, it's time for Nextcloud. We will download the installation files. Let's find out the current version. As of writing this blog, it is version 18.0.4. To find the download link, go to and click on Download for server, then on Details and download options. Right-click over the .tar.bz2 link and copy it. Remember, the current version may not be what is in the example below.

cd ~
tar -xvf nextcloud-18.0.4.tar.bz2

Copy the Nextcloud files to its own system directory. Be sure to replace your own username for [user]. We will also replace the Nginx drive subdirectory with a symbolic link from the Nextcloud system directory; and will create the webroot subdirectory for our TLS certificate renewals.

sudo rsync -Aavx /home/[user]/nextcloud /usr/share/
sudo rm -rf /usr/share/nginx/drive
sudo ln -s /usr/share/nextcloud /usr/share/nginx/drive
sudo mkdir -p /usr/share/nginx/drive/.well-known/acme-challenge/
sudo chown www-data:www-data -R /usr/share/nextcloud

The actual documents our server will host on Nextcloud (or the documents you want access to over the internet) will be stored elsewhere. I promised my users I would encrypt the files for their peace-of-mind. More on this later. Let's set that up the directory first.

sudo mkdir /var/nextcloud
sudo chown www-data:www-data /var/nextcloud
sudo chmod 770 /var/nextcloud

Nextcloud - PHP, PostgreSQL and install wizard

We will need to allow PHP access to our Ubuntu system environment variables. To do this, we have to modify our PHP-fpm. Let back up and modify the PHP pool configuration file by running sudo cp /etc/php/7.2/fpm/pool.d/www.conf /etc/php/7.2/fpm/pool.d/www.conf.bak && sudo nano /etc/php/7.2/fpm/pool.d/www.conf.

Modify around line 113: pm.max_children = 120
Modify around line 118: pm.start_servers = 12
Modify around line 123: pm.min_spare_servers = 6
Modify around line 128: pm.max_spare_servers = 18
Modify/uncomment around line 381: clear_env = no

Let's also increase the memory usage and upload file sizes allowed. But first, back up the file by running sudo cp /etc/php/7.2/fpm/php.ini /etc/php/7.2/fpm/php.ini.bak. Now let's edit the file by running sudo nano /etc/php/7.2/fpm/php.ini.

Modify around line 822: upload_max_filesize = 20M
Modify around line 669: post_max_size = 20M
Modify around line 401: memory_limit = 512M
Modify/uncomment around line 1782: opcache.enable=1
Modify/uncomment around line 1788: opcache.memory_consumption=128
Modify/uncomment around line 1791: opcache.interned_strings_buffer=8
Modify/uncomment around line 1795: opcache.max_accelerated_files=10000
Modify/uncomment around line 1813: opcache.revalidate_freq=1
Modify/uncomment around line 1820: opcache.save_comments=1

Now let's reload PHP and Nginx by running sudo service php7.2-fpm reload && sudo service nginx reload.

Let's create the PostgreSQL users and databases that Nextcloud and OnlyOffice will use. For the password, I'd recommend a random alpha-numeric string, e.g., cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 20 | head -n 1. Please don't use password in the example below; and make sure they're different!

sudo -i -u postgres psql -c "CREATE USER nextcloud WITH PASSWORD 'password';"
sudo -i -u postgres psql -c "CREATE USER onlyoffice WITH PASSWORD 'password';"
sudo -i -u postgres psql -c "CREATE DATABASE nextcloud OWNER nextcloud ENCODING unicode;"
sudo -i -u postgres psql -c "CREATE DATABASE onlyoffice OWNER onlyoffice ENCODING unicode;"
sudo -i -u postgres psql -c "GRANT ALL ON DATABASE nextcloud TO nextcloud;"
sudo -i -u postgres psql -c "GRANT ALL ON DATABASE onlyoffice TO onlyoffice;"

Now that the basic prerequisites and files are installed, let's load up Nextcloud. Go to a browser and load your Nextcloud web app by typing in You should see that it redirects to the https site automatically.

Create a username/password combination for your admin account. Be sure to edit the Data folder to /var/nextcloud.

In the section Configure the database, be sure PostgreSQL is noted as available. For the Database user, Database password and Database name, use the values we created above. Keep localhost as the Database host. I kept the box Install recommended apps selected. Now click Finish setup. It may time out and show a 504 error. Don't worry, it did it to me too. We'll find out what happened in the next section. Most likely, there was a timeout between PHP and PostgreSQL when creating the tables. Just refresh your browser and log in with the Nextcloud admin account you just created.

Nextcloud - Redis and optimize server

Redis and APCu will be used to cache our PHP scripts in the background in our cloud server's memory. These measures improve performance. We have to modify a few things in the background first. Ubuntu 18.04 disables the older rc.local, but the Redis implementation that works for me requires this. Let's create the file by running sudo nano /etc/rc.local.

sysctl -w net.core.somaxconn=1024
echo never > /sys/kernel/mm/transparent_hugepage/enabled
exit 0

The file needs to be executable so run sudo chmod 755 /etc/rc.local. We then create the service file by running sudo nano /etc/systemd/system/rc-local.service.

  Description=/etc/rc.local Compatibility

  ExecStart=/etc/rc.local start


We then back up and modify the Redis service file by running sudo cp /etc/systemd/system/redis.service /etc/systemd/system/redis.service.bak && sudo nano /etc/systemd/system/redis.service.

Edit line: rc-local.service
Edit line: group=www-data

The service daemons have to be enabled and reloaded.

sudo systemctl enable rc-local.service
sudo systemctl daemon-reload

The Redis configuration will be backed up and modified by running sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.bak && sudo nano /etc/redis/redis.conf.

Comment out around line 69: #bind ::1
Uncomment/modify around line 109: unixsocket /var/run/redis/redis-server.sock
Uncomment/modify around line 110: unixsocketperm 775

Now change some system-wide settings regarding users, groups and directories.

sudo usermod -a -G redis www-data
sudo usermod -g www-data redis
sudo mkdir -p /var/run/redis/
sudo chown -R redis:www-data /var/run/redis

Nextcloud needs to know that Redis is how we will handle memory caching. Run sudo cp /usr/share/nextcloud/config/config.php /usr/share/nextcloud/config/config.php.bak && sudo nano /usr/share/nextcloud/config/config.php. Add these lines to the bottom of the $CONFIG array.

'filelocking.enabled' => true,
'memcache.local' => '\OC\Memcache\APCu',
'memcache.distributed' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => [
'host' => '/var/run/redis/redis-server.sock',
'port' => 0,

Redis requires a configuration file to be modified, so let's back it up first then modify. Run sudo cp /etc/sysctl.conf /etc/sysctl.conf.bak && sudo nano /etc/sysctl.conf. Add the line to the bottom of the file.

vm.overcommit_memory = 1

Have Ubuntu reload this file by running sudo sysctl -p. Now reboot the system. If you set up your system like I did, then this is how you do it. Shutdown the system first by running sudo shutdown now. Go to a browser and log into Linode. Go to the Disks/Configs section, select the options for your Ubuntu system and click on Boot This Config. Click on Launch Console and then Glish. Now type in your password (may need to press arrow keys to load the screen). Now restart your terminal window.

Redis is now set up correctly. To verify, run sudo cat /var/log/redis/redis-server.log. There should be no errors or warnings after the reboot. You can compare this to the prior time it was loaded by scrolling up in the log. Now to the Nextcloud app itself.

Let's see if there are any steps we have to run manually from the install. Go to your document server web app, and click on the user icon on the upper right corner of the screen. It defaults to the first letter of the user name in a circle. Click on the first option Settings. On the left panel, click on Overview under Administration. It may give a command that starts with occ that they recommend you running. Only run the following command, if your Nextcloud app asked you to do so. If it did not ask you to run any occ commands, then skip to the next step.

cd /usr/share/nextcloud
sudo -u www-data php occ db:add-missing-indices
sudo -u www-data php occ db:convert-filecache-bigint
[Select y when prompted]

Refresh the Overview page under Administration Settings to make sure it shows All checks passed. Let's set up automatic Nextcloud maintenance through cron. We will be modifying the default shell for www-data and then setting up cron through www-data.

sudo chsh -s /bin/bash www-data
sudo crontab -u www-data -e

You can select any text editor you want. I'm most familiar with Nano, and so I picked that. Crontab is a method to have Ubuntu run commands automatically. You can read more about it here. Add this line to the Crontab: */5 * * * * php -f /usr/share/nextcloud/cron.php. This will run the command every 5 minutes. Under the Settings page, click on Basic settings under Administration in the left panel. In the main panel, select Cron in the Background jobs section.

Nextcloud can send out automated emails regarding the web app. Because we already set up our own Postfix mail server, let's set this up. Staying in the Basic settings page, focus on the Email server section on the main panel. Select SMTP with SSL/TLS encryption. You can use your own email address, set up a new email address or a alias an email address to yours. Whichever you choose, enter this in the From address. Under Authentication method, select Plain and check the option Authentication required. Use your mail server address (e.g., via port 465.

Nextcloud - encryption and IMAP user access

My users wanted to ensure that their files were encrypted to minimize snooping. It will sacrifice performance, but I obliged. Go to Settings in the Nextcloud web app, and on the left panel under Administration, click Security. Click on the check-box next to Enable server-side encryption. In the warning box, click on Enable encryption. We have to enable the encryption app. Click on the user icon in the upper right corner of the screen, and click on Apps. In the left panel, click on Disabled apps. Click on Enable next to Default encryption module. Verify it works by going back to the Security page under Settings and review the Server-side encryption section.

Because we set up our own email server, our users who have access to our email server can also have access to our document server. Let's connect the two by downloading a plugin. Go back to the Apps page and click on Security on the left. The plugins should be listed alphabetically; look for User and Group SQL Backends. Click on Download and enable.

To modify the SQL backends settings, go back to Settings by clicking on the user icon in the upper right corner. You'll find a new setting in the left panel called SQL Backends, click on it. If you followed along in my tutorials, your PostgreSQL username/role and database names should be the same: virtualmail. Use the following settings:

SQL Driver: PostgreSQL
Hostname: localhost
Database: virtualmail
Username: virtualmail
Password: [use your own]
Hash algorithm: SHA512 (Crypt)
Email sync: SQL always win
User table, Table name: virtual_users
User table, Username: email
User table, Email: email
User table, password: password

The Nextcloud document server web app is now set up. You can upload, download and share files from anywhere in the world as long as you have internet access. Now let's allow document editing by integrating productivity suite OnlyOffice.

OnlyOffice - Nextcloud integration

This tutorial is based on the official documentation by OnlyOffice. We already created the PostgreSQL user/role and database for OnlyOffice above. We have to install RabbitMQ message broker.

sudo apt install rabbitmq-server

The OnlyOffice apt package repository will be added and package installed.

sudo apt-key adv --keyserver hkp:// --recv-keys CB2DE8E5
sudo echo "deb squeeze main" | sudo tee /etc/apt/sources.list.d/onlyoffice.list
sudo apt update
sudo apt install onlyoffice-documentserver

During the installation process, you'll be asked to provide a password for the onlyoffice PostgreSQL user. Please enter the onlyoffice password you created above.

We will change OnlyOffice to using the HTTPS (443) connection. The OnlyOffice configuration file has to be created and modified. Do this by running sudo cp -f /etc/onlyoffice/documentserver/nginx/ds-ssl.conf.tmpl /etc/onlyoffice/documentserver/nginx/ds.conf && sudo nano /etc/onlyoffice/documentserver/nginx/ds.conf. Be sure to replace with your registered domain.

Modify line 7: server_name;
Add around line 31: server_name;
Delete around line 37: #ssl_certificate {{SSL_CERTIFICATE_PATH}};
Delete around line 38: #ssl_certificate_key {{SSL_KEY_PATH}};
Add following lines right below the lines deleted above:
  ssl_certificate /etc/letsencrypt/live/;
  ssl_certificate_key /etc/letsencrypt/live/;
  ssl_trusted_certificate /etc/letsencrypt/live/;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  ssl_dhparam /etc/ssl/private/dhparam.pem;
  ssl_stapling on;
  ssl_stapling_verify on;
Delete the lines starting and including, around line 44: # Uncomment string below and specify the path ...
Delete up through and including, around line 53: ssl_prefer_server_ciphers   on;
Add following lines to the bottom of this server block, around line 77:
  location /.well-known/acme-challenge/ {
    default_type "text/plain";
    root /usr/share/nginx/apps;

Reload the Nginx service by running sudo service nginx reload. Verify that everything is working by going to a browser and loading the page You should see it automatically redirects to HTTPS and it should show Document Server is running.

You can harden the OnlyOffice document server by restricting access to your Ubuntu server only and to any subdomain on your registered domain. To find your ip addresses, run ifconfig. Look for inet and inet6 (one that does not start with fe80); these are your ipv4 and ipv6 respectively. Back up and modify the configuration file by running sudo cp /etc/onlyoffice/documentserver/default.json /etc/onlyoffice/documentserver/default.json.bak && sudo nano /etc/onlyoffice/documentserver/default.json. In the example below, be sure to replace ipv4 and ipv6 with your own. You can also add your workstation's public ip address. If you don't do this, going to will show an error. You can skip adding your workstation's public ip address, and just ignore that error should you go to that web page.

Delete the section and modify starting around line 153:
"ipfilter": {
  "rules": [
      "address": "ipv4",
      "allowed": true
      "address": "ipv6",
      "allowed": true
      "address": "*",
      "allowed": true
      "address": "[workstation public ip]",
      "allowed": true
      "address": "*",
      "allowed": false
  "useforrequest": true,
  "errorcode": 403

Now to integrate the two. Go back to the Nextcloud document server web app, click on the user icon in the upper right corner and click on Apps. Click on Office & text in the left panel. Look for ONLYOFFICE and click on Download and enable. Let's go to Settings via the user icon in the upper right corner. Click on ONLYOFFICE in the left panel. In Document Editing Service address type in (using your own Click Save.

Let's test it out. Click on the file folder icon (Files) in the top menu bar. Click on the plus icon. Click on Document and name it whatever you want. A new browser tab should load with a Word-like document editor. And there you go. You now have a fully functional alternative to Google Docs and Microsoft Office 365. Woo hoo!!


Suenotek Blog

Seattle, Washington

Cookies | Privacy | Policy

About | Contact Us