Feed on

What is this about?

So far I have installed all services on my server, such as mail, database and web server, in one operating system. This is of course inconvenient, since a problem or a security incident can affect all services at once. However, classic virtualization requires many resources because all hardware must be virtualized. Target ScenarioI therefore decided to use LXD containers, which require hardly any more resources than the processes running in it themselves. The containers behave like independent operating systems. Therefore LXD containers are not comparable with e.g. Docker containers as these normally just host one process.

This article covers the installation of Nextcloud and Collabora in two separate LXD containers on a server with only one public IP address. We don’t use Docker for Collabora as many other guides do. A previous installation of LXD is not required – we start at the very beginning. For convenient reasons I’ll use the root user – you can of course use ‘sudo’ if you want. I also tried using just different subdomains instead of two different domains but this resulted in several collabora errors I wasn’t able to figure out. Thats why we us two different domains for this how-to. Enough said, let’s start:

Step 1: Install LXD

The server (LXD host) runs with Ubuntu 16.04.4. LXD is already included in the repository, but we will install the latest version from the backports:

apt update && apt install zfsutils-linux -y
apt install -t xenial-backports lxd lxd-client -y

The ZFS-Utils are needed to use the full functionality of LXD, e.g. snapshots. Before we can create the containers, LXD must be initialized once. This creates the storage pool and the network configuration. The example creates a ZFS storage with 50GB and a network bridge for communication:

lxd init
Do you want to configure a new storage pool (yes/no)[default=yes]?
Name of the new storage pool[default=default]:
Name of the storage backend to use (dir, btrfs, lvm, zfs)[default=zfs]:
Create a new ZFS pool (yes/no)[default=yes]?
Would you like to use an existing block device (yes/no)[default=no]?
Size in GB of the new loop device (1GB minimum)[default=15GB]: 50
Would you like LXD to be available over the network (yes/no)[default=no]?
Would you like stale cached images to be updated automatically (yes/no)[default=yes]?
Would you like to create a new network bridge (yes/no)[default=yes]?
What should the new bridge be called[default=lxdbr0]?
What IPv4 address should be used (CIDR subnet notation, "auto" or "none")[default=auto]?
What IPv6 address should be used (CIDR subnet notation, "auto" or "none")[default=auto]?
LXD has been successfully configured.

LXD provides a DHCP and DNS server for our containers so we don’t need to take care of this. For communication with the outside world we will later create IPtables rules, so that as in the example “no” in the question “Would you like LXD to be available over the network” can be specified.

This completes the configuration of the host for the time being and we can now create the first container:

lxc launch ubuntu: nextcloud

This will create a container called “nextcloud” with the current version of Ubuntu, since we did not specify a version after the colon. The Ubuntu image is cached and is immediately available when installing additional containers. Now let’s switch to the container we just created.

Step 2: Nextcloud container

lxc exec nextcloud bash

So we are in the container “nextcloud”, which now behaves like a normal Ubuntu. Before we start installing the required software, we update the container and correct the time zone if necessary:

dpkg-reconfigure tzdata
add-apt-repository ppa:certbot/certbot -y
apt update && apt dist-upgrade -y

Now we install the components needed for Nextcloud according to the Nextcloud Deployment Guide and the certbot-client to get valid SSL certificates from letsencrypt:

apt-get install apache2 mariadb-server libapache2-mod-php php-gd php-json php-mysql php-curl php-mbstring php-intl php-mcrypt php-imagick php-xml php-zip unzip certbot -y

Now we load and unpack the latest version of Nextcloud:

wget https://download.nextcloud.com/server/releases/latest.zip
unzip latest.zip
cp -r nextcloud /var/www/
chown -R www-data:www-data /var/www/nextcloud/

Nextcloud is in the right place and has the correct permissions. Now we create the Apache configuration for the directory:

nano /etc/apache2/sites-available/nextcloud.conf
Alias /nextcloud "/var/www/nextcloud/"

<Directory /var/www/nextcloud/>
  Options +FollowSymlinks
  AllowOverride All

 <IfModule mod_dav.c>
  Dav off

 SetEnv HOME /var/www/nextcloud
 SetEnv HTTP_HOME /var/www/nextcloud


… and activate the required Apache modules as well as the configuration you just created and the default SSL site:

a2enmod rewrite headers ssl proxy proxy_wstunnel proxy_http
a2ensite nextcloud
a2ensite default-ssl
service apache2 restart

Since we installed the MariaDB server with “-y”, no root password was requested. We’ll do that now and create a user and a database for our Nextcloud:

mysqladmin -u root password NEWPASSWORD
mysql -u root -p
GRANT ALL PRIVILEGES ON nextcloud.* To 'nextcloud'@'localhost' IDENTIFIED BY 'nextcloudpassword';

Please change the database name, the user name and the password to your needs. This completes the basic installation and we can open the setup page of our Nextcloud. But if we now enter the Nextcloud URL in the browser, we end up on the LXD host and not in the container. To fix that we have to create NAT rules for the ports 80 and 443 on the host:

lxc list
iptables -A FORWARD -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
iptables -A FORWARD -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT
ip6tables -A FORWARD -p tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT
ip6tables -A FORWARD -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT
iptables -t nat -I PREROUTING -i eth0 -p TCP --dport 80 -j DNAT --to-destination
iptables -t nat -I PREROUTING -i eth0 -p TCP --dport 443 -j DNAT --to-destination
ip6tables -t nat -I PREROUTING -i eth0 -p TCP --dport 80 -j DNAT --to-destination [fd42:1995:8000:e20f:216:3eff:feeb:9856]:80
ip6tables -t nat -I PREROUTING -i eth0 -p TCP --dport 443 -j DNAT --to-destination [fd42:1995:8000:e20f:216:3eff:feeb:9856]:443

If the interface with the public IP is not called “eth0”, this must of course be adapted. Also the current IPv4 and IPv6 addresses of the Nextcloud container must be specified (see ‘lxc list’ output).

Now we can go to https://mydomain.tld/nextcloud and enter the data from our database. Nextcloud is now installed in the container.

The following optional steps should also be considered for optimal security and performance:

lxc exec nextcloud bash

Optional 1: Use letsencrypt SSL certificates:

Ok, to be honest this isn’t really optional. If you want to configure collabora later or use Nextcloud in production this is a must-have. We’ll create two separate certificates, one for each domain:

service apache2 stop
certbot certonly --standalone -d mydomain.tld
certbot certonly --standalone -d collaboradomain.tld
nano /etc/apache2/sites-available/default-ssl.conf
SSLCertificateFile /etc/letsencrypt/live/mydomain.tld/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/mydomain.tld/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/mydomain.tld/chain.pem

Make sure to uncomment the chain file line.

service apache2 start

A CRON entry ensures that the certificate is updated automatically:

crontab -e
15 3 * * * /usr/bin/certbot renew --quiet --post-hook "apachectl graceful"

This gives us a valid SSL certificate.

Optional 2: Activate HSTS

Strict Transport Security is recommended by Nextcloud and activated as follows:

nano /etc/apache2/sites-available/default-ssl.conf
DocumentRoot /var/www/html
Header always set Strict-Transport-Security "max-age=15552000; includeSubDomains"
service apache2 restart

Optional 3: Caching

To ensure optimal PHP performance, we activate PHP Opcache:

nano /etc/php/7.0/apache2/php.ini

In the[opcache] section everything remains commented and we add the following:


We use Redis as memcache and file locking cache:

apt install redis-server php-redis -y
nano /var/www/nextcloud/config/config.php
'memcache.local' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => array(
     'host' => 'localhost',
     'port' => 6379,
service apache2 restart

Last but not least, we activate the CRON job for the Nextcloud background tasks:

crontab -u www-data -e
*/15 * * * * php -f /var/www/nextcloud/cron.php

Now there should be no more warnings on https://mydomain.tld/nextcloud/index.php/settings/admin

So let’s get to the last step.

Step 3: Collabora container

We switch to the host and create another container:

lxc launch ubuntu: collabora -c security.privileged=true

Using “-c security.privileged=true” we create a so-called privileged container. In it, the processes run with the same IDs as on the host, which reduces security. This is required by Collabora because Collabora uses MKNOD to create Linux devices in operation. However, the advantages of encapsulation are fully retained and we can neglect the security aspect, since the user data is stored in the nextcloud container.

So let’s switch to the new container, update it and install the required components:

lxc exec collabora bash
dpkg-reconfigure tzdata
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 0C54D189F4BA284D
echo 'deb https://www.collaboraoffice.com/repos/CollaboraOnline/CODE ./' >> /etc/apt/sources.list
apt update
apt dist-upgrade -y && apt install loolwsd code-brand -y

Now Collabora Office is installed, but we still have to configure it:

nano /etc/loolwsd/loolwsd.xml
<server_name desc="Hostname:port of the server running loolwsd. If empty,
      it's derived from the request." type="string" default="collaboradomain.tld:9981"></server_name>
<host desc="Regex pattern of hostname to allow or deny." allow="true">localhost</host>
<host desc="Regex pattern of hostname to allow or deny." allow="true">mydomain.tld</host>
<host desc="Regex pattern of hostname to allow or deny." allow="true">10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}</host>

The configuration also includes SSL certificates. We copy these from our nextcloud container using scp.

ssh-keygen -t ed25519
cat .ssh/id_ed25519.pub

Copy the output of the ‘cat’ command.

lxc exec nextcloud bash
nano .ssh/authorized_keys

Paste the just copied output into the authorized_key file and save it. Now return to the collabora container:

lxc exec collabora bash
scp nextcloud.lxd:/etc/letsencrypt/live/collaboradomain.tld/cert.pem /etc/loolwsd/cert.pem
scp nextcloud.lxd:/etc/letsencrypt/live/collaboradomain.tld/privkey.pem /etc/loolwsd/key.pem
scp nextcloud.lxd:/etc/letsencrypt/live/collaboradomain.tld/chain.pem /etc/loolwsd/ca-chain.cert.pem

Now we start the loolwsd service and check that it listens on ports 9980 and 9981:

service loolwsd start
netstat -lnp
tcp 0 0* LISTEN 4915/loolwsd
tcp6 0 0 :::9980 :::* LISTEN 4915/loolwsd

That looks good. Since the loolwsd service has to communicate back to the nextcloud container, we create an host entry for this:

nano /etc/hosts localhost www.mydomain.tld mydomain.tld

This completes the configuration of the collabora container. The final work takes place in the nextcloud container and on the Nextcloud web interface.

lxc exec nextcloud bash
nano /etc/apache2/sites-available/collaboradomain.conf
<VirtualHost *:443>
ServerName collaboradomain.tld:443

# SSL configuration, you may want to take the easy route instead and use Lets Encrypt!
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/collaboradomain.tld/cert.pem
SSLCertificateChainFile /etc/letsencrypt/live/collaboradomain.tld/chain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/collaboradomain.tld/privkey.pem
SSLProtocol all -SSLv2 -SSLv3
SSLHonorCipherOrder on

# Encoded slashes need to be allowed
AllowEncodedSlashes NoDecode

# Container uses a unique non-signed certificate
SSLProxyEngine On
SSLProxyVerify None
SSLProxyCheckPeerCN Off
SSLProxyCheckPeerName Off

# keep the host
ProxyPreserveHost On

# static html, js, images, etc. served from loolwsd
# loleaflet is the client part of LibreOffice Online
ProxyPass /loleaflet https://collabora.lxd:9980/loleaflet retry=0
ProxyPassReverse /loleaflet https://collabora.lxd:9980/loleaflet

# WOPI discovery URL
ProxyPass /hosting/discovery https://collabora.lxd:9980/hosting/discovery retry=0
ProxyPassReverse /hosting/discovery https://collabora.lxd:9980/hosting/discovery

# Main websocket
ProxyPassMatch "/lool/(.*)/ws$" wss://collabora.lxd:9980/lool/$1/ws nocanon

# Admin Console websocket
ProxyPass /lool/adminws wss://collabora.lxd:9980/lool/adminws

# Download as, Fullscreen presentation and Image upload operations
ProxyPass /lool https://collabora.lxd:9980/lool
ProxyPassReverse /lool https://collabora.lxd:9980/lool

a2ensite collaboradomain

As we now have two SSL sites configured, we have to edit the default-ssl site and add a ServerName there:

nano /etc/apache2/sites-available/default-ssl.conf
<VirtualHost _default_:443>
   ServerName mydomain.tld
service apache2 reload

Now we log into the browser at https://mydomain.tld/nextcloud/index.php/settings/apps?category=office# and activate the “Collabora Online” app.

At https://mydomain.tld/nextcloud/index.php/settings/admin/richdocuments we enter “https://collaboradomain.tld:443”. Nextcloud is now configured to access our collabora container.

The last step is to test our installation. Go to files view in Nextcloud an create a new “Document” file with the “+” button at the top. A click on the newly created file will open it in Collabora Office for editing.

4 Responses to “Install Nextcloud and Collabora in LXD containers”

  1. Jonathan says:

    Hi, thanks for the great guide. I’ve got a server with several nextcloud instances in lxd containers. I’m wanting to set up collabora, and have followed your guide. However I keep getting GuzzleHttp\Exception\ConnectException: cURL error 7: Failed to connect to demo-docs.ootbds.com port 443: Connection refused whenever I try to open a document.

    There are a few differences with my setup. Firstly my instances are behind haproxy, which handles ssl termination. So nextcloud is technically running http, not ssl, but collabora doesn’t work regardless of whether I set it to use ssl or not.

    Secondly, when I browse to the external url of the collabora instance, it’s supposed to go via the nextcloud’s instance’s apache, however I just get the “untrusted domain” page, indicating that it’s not even going to the collabora instance.

    Do you have any suggestions for someone running in lxd containers using haproxy with ssl termination?


  2. bttd says:


    I have almost the same setup as Jonathan.
    One public ip, one host with proxmox 4.4 with haproxy container with https termination, nextcloud container, and collabora container. I can make the system running. I’m just set the ssl enable to false in collabora setting and set proxy with termination to true, delete the ssl part of apache conf, and sert http instend of https.

    But if I try to open a document I get error.

    “The server was unable to complete your request.

    If this happens again, please send the technical details below to the server administrator.

    More details can be found in the server log.

    Technical details
    Remote Address:
    Request ID: ChJ8ia4atMK7vo6y5AM8”

    The ip is my haproxy lan IP

  3. peacecop kalmer: says:

    I did everything but the “nextcloud” and optional things up to
    netstat -lnp
    which doesn’t print anything regarding loolwsd, only:
    Active Internet connections (only servers)
    Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
    tcp 0 0* LISTEN 150/systemd-resolve
    tcp 0 0* LISTEN 208/sshd
    tcp6 0 0 :::22 :::* LISTEN 208/sshd
    udp 0 0* 150/systemd-resolve
    udp 0 0* 148/systemd-network
    udp6 0 0 fe80::216:3eff:fe3e:546 :::* 148/systemd-network
    raw6 0 0 :::58 :::* 7 148/systemd-network
    Active UNIX domain sockets (only servers)
    Proto RefCnt Flags Type State I-Node PID/Program name Path
    unix 2 [ ACC ] STREAM LISTENING 2825504 1/init /run/systemd/private
    unix 2 [ ACC ] STREAM LISTENING 2825508 1/init /run/systemd/journal/stdout
    unix 2 [ ACC ] STREAM LISTENING 2825530 1/init /run/lvm/lvmetad.socket
    unix 2 [ ACC ] STREAM LISTENING 2825535 1/init /run/lvm/lvmpolld.socket
    unix 2 [ ACC ] STREAM LISTENING 2828466 1/init /run/apport.socket
    unix 2 [ ACC ] STREAM LISTENING 2828468 1/init /run/uuidd/request
    unix 2 [ ACC ] STREAM LISTENING 2828470 1/init /run/acpid.socket
    unix 2 [ ACC ] STREAM LISTENING 2828474 1/init /run/snapd.socket
    unix 2 [ ACC ] STREAM LISTENING 2828478 1/init /run/snapd-snap.socket
    unix 2 [ ACC ] STREAM LISTENING 2828487 1/init /var/run/dbus/system_bus_socket
    unix 2 [ ACC ] STREAM LISTENING 2828472 1/init /var/lib/lxd/unix.socket

  4. fenris says:

    root@collabora:/etc/loolwsd# systemctl status loolwsd
    ● loolwsd.service – LibreOffice Online WebSocket Daemon
    Loaded: loaded (/lib/systemd/system/loolwsd.service; enabled; vendor preset: enabled)
    Active: failed (Result: exit-code) since Mon 2019-07-01 01:16:24 +08; 3min 38s ago
    Main PID: 2415 (code=exited, status=70)

    Jul 01 01:16:24 collabora systemd[1]: loolwsd.service: Service hold-off time over, scheduling restart.
    Jul 01 01:16:24 collabora systemd[1]: loolwsd.service: Scheduled restart job, restart counter is at 5.
    Jul 01 01:16:24 collabora systemd[1]: Stopped LibreOffice Online WebSocket Daemon.
    Jul 01 01:16:24 collabora systemd[1]: loolwsd.service: Start request repeated too quickly.
    Jul 01 01:16:24 collabora systemd[1]: loolwsd.service: Failed with result ‘exit-code’.
    Jul 01 01:16:24 collabora systemd[1]: Failed to start LibreOffice Online WebSocket Daemon.

Leave a Reply