Running Nextcloud + Collabora on TrueNAS SCALE with Nginx Proxy Manager

For a while, I have been having trouble setting up Nextcloud services in my TrueNAS box, the difficulty being that unlike most TrueNAS SCALE apps, Nextcloud often requires additional apps to provide additional services (in this case, an office suite by Collabora). Managing the routing between different apps and accessing them through a reverse proxy (Nginx Proxy Manager) has been quite confusing for me. I need to make this work with the least amount of hacks possible, as I am neither an expert in Docker deployment nor do I have time to debug errors on every update. Interestingly, compared to programming, when it comes to TrueNAS setup (and my home network setups), I am exactly the opposite of a hacker 😬. I do not want to tinker with them at all. I'd much prefer to set it up once and have it run for years (auto-update of course).

So there you go: this blog documents the trials and errors of setting up Nextcloud + Collabora CODE on TrueNAS SCALE, using Nginx Proxy Manager (NPM) as the reverse proxy.

Background

I've been using TrueNAS SCALE since 2022. Watching it spin off from TrueNAS CORE has been quite a journey and a great technology investment. Hail ZFS! A NAS system combining the power of Docker containers brings so many possibilities. I've been using it to run my own DNS server for blocking ads; paperless-ngx for scanning documents into digital copies; the Kavita ebook reader that is always online; Audiobookshelf that helps with downloading podcasts through metube, … The beauty is that all the data is centralized in one place, always online and private.

Most of them are quite easy to set up — they are self-contained apps; since TrueNAS SCALE Electric Eel (24.10) they are basically managed Docker Compose YAML files. This all works fine if all you need is inside a single app. Well, not the case anymore for Nextcloud. Surprisingly there isn't a blog post about this at all.

Office as a Service.

Nextcloud is a file sync and share platform; it cannot render a docx. Editing is delegated to a separate office engine — Collabora Online (LibreOffice) — which runs as its own service, on its own hostname, reached over the network like anything else.

The two roles:

  • Nextcloud — the WOPI host. Owns the files, permissions, and sharing. Exposes endpoints (/index.php/apps/richdocuments/wopi/) to ask about and fetch files. The richdocuments app is the integration point; it holds no editor, it just advertises Collabora and serves these endpoints.
  • Collabora — the WOPI client. Owns rendering/editing. Knows nothing about users or storage — it asks Nextcloud for file bytes over WOPI.

They speak WOPI, a small REST protocol: CheckFileInfo (metadata + permissions), GetFile, PutFile, and locking calls. Collabora needs only a URL and a token, never credentials or storage access — which is why the editor can run as a separate, untrusted service, and why any WOPI host can pair with any WOPI client.

%%{init: {"themeVariables": {"fontSize": "13px"}}}%%
flowchart TB
    B["Browser"]
    NC["Nextcloud<br/>WOPI host"]
    CO["Collabora<br/>WOPI client"]

    B -- "loads editor" --> CO
    B -- "opens / auth" --> NC
    NC -- "editor config + token" --> B
    CO -- "WOPI / loads file" --> NC

Trust is carried by three settings: Collabora's aliasgroup1 (which host it serves), Nextcloud's wopi_allowlist (which source it accepts WOPI from), and matching URLs/protocol on both sides (overwriteprotocol).

Because the browser loads the editor directly from Collabora, and Collabora calls back to Nextcloud server-to-server, the two must each be reachable, we will see how to archive this in later sections.

Prior art

This is not the first attempt. An earlier setup (2024) ran Nextcloud and OnlyOffice in a single Docker Compose file following Thomas Wilde's blog. It worked for a while — routing between Nextcloud, Collabora, and OnlyOffice was fully connected, and it was fully compatible with my reverse proxy as well. The problem was that I now had to maintain their upgrades myself. Boy oh boy, did I underestimate the upgrade bugs 😱 such as this and this. The workaround was pinning the Nextcloud and Collabora images (nextcloud:31.0.9 and collabora/code:24.04.12.3.1) for months. Well, not the situation I was hoping for.

Speaking about traffic routing, the workaround was being able to manually specify DNS servers in docker-compose.yaml: pointing the containers at the LAN DNS servers (dns:) and pinning each other's public hostnames to the host IP (extra_hosts:), so traffic left the Docker network and came back through NPM. It avoided bundling an extra nginx proxy, but made container networking depend on hand-maintained DNS entries. The setup also carried overwriteprotocol=https and trusted_proxies in config.php, which is required in the new setup as well.

Wiring NPM (needed in both setups)

In the old setup as much as in the new one, NPM needs two Proxy Hosts, both terminating TLS:

Nextcloud

Domain Names:         nextcloud.example.com
Scheme:               http
Forward Hostname/IP:  <Nextcloud reachable address>
Forward Port:         <Nextcloud published port>

Collabora

Domain Names:         collabora.example.com
Scheme:               http
Forward Hostname/IP:  <Collabora reachable address>
Forward Port:         9980

Enable Websockets Support on the Collabora proxy host — the editor's browser connection depends on it. (And don't blame WebSockets first when the Collabora logs show a WOPI Moved Permanently or 403 error; fix the backend request first.)

Second Attempt: Why Not a Shared Docker Network?

The obvious next step was a shared Docker network: docker network create nextcloud, have both containers join it, and let Nextcloud talk to Collabora directly at http://collabora:9980. TrueNAS Apps actually support this — App containers can be attached to a predefined Docker network straight from the App configuration, no manual docker network connect required.

I did not continue down this path for one reason: NPM. The reverse proxy would have to be hacked into the same network as well, and its proxy hosts rewritten to forward to the container aliases — nextcloud.example.com and collabora.example.com resolving to container names instead of published host ports. That puts the LAN DNS records, the container DNS, and the proxy aliases in three places that all have to agree, for what should be a single routing decision. Since both services are already reachable by hostname through NPM, there was no compelling reason to introduce that coupling.

Final Architecture

The new setup keeps the good idea — route everything through the reverse proxy by public hostname — and drops the rest: the office stack moves into TrueNAS Apps so the platform owns the upgrades, and LAN DNS + NPM replaces the docker-DNS plumbing. The final architecture is intentionally simple:

%%{init: {"themeVariables": {"fontSize": "13px"}}}%%
flowchart TB
    WC["nextcloud.example.com"]
    CC["collabora.example.com"]
    NPM["<b>Nginx Proxy Manager</b><br/>192.168.100.10"]
    NC["Nextcloud<br/>container"]
    CO["Collabora<br/>CODE container"]

    WC -- HTTPS --> NPM
    CC -- HTTPS --> NPM
    NPM --> NC
    NPM --> CO

The key decision, inherited from the old setup and kept deliberately:

Nextcloud and Collabora do not need to share a Docker network.

Both services are reached through their normal public hostnames, and Collabora calls back to Nextcloud over the same DNS → NPM path that every other client uses:

https://nextcloud.example.com
https://collabora.example.com

On the LAN, the custom DNS server points both hostnames at NPM's physical address:

nextcloud.example.com    → 192.168.100.10
collabora.example.com    → 192.168.100.10

Containers live in a second DNS world: Docker's embedded resolver (nameserver 127.0.0.11 in /etc/resolv.conf) forwards to whatever upstreams the container was given, in this case the truenas. The check that matters is inside: the containers:

getent hosts nextcloud.example.com
getent hosts collabora.example.com

Both should resolve to NPM. As the TrueNAS docs put it:

By default, containers use the DNS settings from the host system. You can change the DNS policy and define separate nameservers and search domains.

(TrueNAS custom App screens) — which is exactly the knob that made the old setup's dns: entries necessary, and what makes this setup possible, (hopefully ixsystems never changes this, also I heard Truenas 26 offers customized per-app DNS nameserver configurations).

New setup

The configuration that makes the final architecture work, in the order I would apply it.

Verify Each Hop

Before touching any Nextcloud or Collabora settings, confirm the plumbing hop by hop.

DNS

From a normal LAN machine:

getent hosts nextcloud.example.com
getent hosts collabora.example.com

Both should resolve to NPM's physical address (192.168.100.10).

Nextcloud through NPM

curl -vk https://nextcloud.example.com/status.php

An HTTP 200 with Nextcloud's status JSON means the client → DNS → NPM → Nextcloud path works, with Collabora not involved at all.

Collabora discovery

curl -vk https://collabora.example.com/hosting/discovery

Collabora answers with XML describing the office formats it supports. Anything unexpected here means the reverse-proxy configuration is wrong — fix it before debugging WOPI, because everything downstream depends on it.

Nextcloud's richdocuments app also ships a bundled CODE installation, but it proved not usable for daily driving — its proxy.php endpoints kept returning 404s — so a dedicated external Collabora CODE container took over the editor role. The richdocuments app stays enabled, since it remains the integration point, but the office server it talks to is external.

Collabora Alias Configuration

Collabora was configured with:

aliasgroup1=https://nextcloud.example.com:443

This is important because Collabora needs to know the Nextcloud/WOPI host it is allowed to communicate with.

The HTTPS URL is intentional.

Do not change this to an HTTP URL merely because NPM forwards HTTP internally.

The WOPI Allowlist

The one richdocuments setting with no intuitive counterpart in the UI. On the Collabora side its absence manifests as:

WOPI::CheckFileInfo returned 403 (Forbidden)

which looks like a networking or NPM problem but isn't — the request is arriving, Nextcloud is refusing it. Nextcloud's log says why:

WOPI request denied from 172.20.1.1

That line is the most valuable debugging artifact in the whole setup: it reveals the address Collabora's WOPI requests actually come from — neither the host's nor the browser's. The allowlist must include that source network:

php occ config:app:set richdocuments \
    wopi_allowlist \
    --value="172.20.0.0/16"
php occ config:app:get richdocuments wopi_allowlist

Always take the source address from the Nextcloud WOPI log. Never infer it from the host or the browser.

The denial message helpfully prints both the source address and the configured ranges verbatim — read it before touching anything else.

Nextcloud Configuration

php occ config:system:set writes to Nextcloud's persistent config.php, so it survives container restarts. But for a reproducible App configuration, prefer the environment variable:

environment:
  - OVERWRITECLIURL=https://nextcloud.example.com
  - OVERWRITEPROTOCOL=https

OVERWRITEPROTOCOL was the setting that actually solved the Moved Permanently problem; OVERWRITECLIURL keeps CLI- and background-generated URLs consistent with the public HTTPS URL.

One TrueNAS-specific trap: the App template already defines some variables. Adding

NEXTCLOUD_TRUSTED_DOMAINS=${THE_DOMAIN}

fails template rendering with:

Environment variable [NEXTCLOUD_TRUSTED_DOMAINS]
is already defined from the application developer.

This is TrueNAS's App framework, not Docker. Configure NEXTCLOUD_TRUSTED_DOMAINS through the App's own field instead; OVERWRITEPROTOCOL can be added freely if the template doesn't already define it.

Finally, a setting that is easy to confuse with overwriteprotocol: trusted_proxies tells Nextcloud which reverse proxies it may trust for headers like X-Forwarded-Proto:

'trusted_proxies' => [
    '192.168.100.10',
],

Note the address: this should be where NPM connects from — not the WOPI source address (172.20.1.1) that shows up in the logs (see The WOPI Allowlist). The two serve different purposes. For this setup, overwriteprotocol=https alone was sufficient.

The Moved Permanently Detour

Why OVERWRITEPROTOCOL is needed at all: after the allowlist fix, Collabora reported:

Failed to get settings json from
[http://nextcloud.example.com/index.php/apps/richdocuments/wopi/settings?...]
with status[Moved Permanently]

The key clue is the scheme: Collabora was fetching an http:// URL, and NPM redirects HTTP to HTTPS — so instead of the expected JSON, the WOPI settings request received a 301. My first instinct was to blame aliasgroup1, but it was already https://nextcloud.example.com:443; the alias and the URL used for this particular request simply travel different configuration paths. The real question was: how does Nextcloud decide which protocol to put in the URLs it generates?

The answer is overwriteprotocol. HTTPS terminates at NPM, and NPM forwards plain HTTP to Nextcloud — from the PHP process's point of view, the connection is HTTP, so without help it generates http://nextcloud.example.com/... URLs even though every user-facing URL is HTTPS. overwriteprotocol=https tells it:

The externally visible protocol for this installation is HTTPS.

Now Nextcloud hands Collabora https:// URLs, which sail through NPM without hitting the redirect.

Commands Worth Keeping

Check Nextcloud protocol

php occ config:system:get overwriteprotocol

Set Nextcloud protocol

php occ config:system:set overwriteprotocol --value=https

Check WOPI allowlist

php occ config:app:get richdocuments wopi_allowlist

Set WOPI allowlist

php occ config:app:set richdocuments \
    wopi_allowlist \
    --value="172.20.0.0/16"

Check DNS

getent hosts nextcloud.example.com
getent hosts collabora.example.com

Check Nextcloud through NPM

curl -vk https://nextcloud.example.com/status.php

Check Collabora through NPM

curl -vk https://collabora.example.com/hosting/discovery

Inspect Docker DNS

docker info | grep -i dns
cat /etc/resolv.conf
docker inspect <container> \
    --format '{{json .HostConfig.Dns}}'

Inspect containers

docker ps --format 'table {{.Names}}\t{{.Image}}'

Lessons Learned

Several things were initially tempting but turned out to be unnecessary or misleading:

  1. A shared Docker network is not required. Nextcloud and Collabora can communicate through their normal HTTPS hostnames and NPM.
  2. The physical LAN DNS record does not need to point into the Docker network. It can correctly point to NPM.
  3. Read the log before touching anything else. The WOPI denial message states both the source address and the configured ranges verbatim.
  4. aliasgroup1=https://... does not by itself guarantee that every URL generated by the Nextcloud/Collabora integration will use HTTPS.
  5. TrueNAS App environment variables may already be owned by the App template. Don't duplicate variables such as NEXTCLOUD_TRUSTED_DOMAINS when the App already defines them.
  6. NPM's HTTP→HTTPS redirect can break backend WOPI requests. If Collabora receives a URL beginning with http://, it may get a Moved Permanently response instead of the expected WOPI data.

Errors

The error messages map cleanly to layers, which is what kept all of this tractable:

Error Layer to investigate
Could not resolve host DNS / container networking
WOPI::CheckFileInfo returned 403 richdocuments WOPI allowlist
status[Moved Permanently] HTTP → HTTPS redirect
socket connection closed unexpectedly WebSocket / Collabora reverse proxy

Fix one layer, re-test, move on — with HTTPS as the canonical external protocol throughout the Nextcloud ↔ Collabora integration.

comments powered by Disqus