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. Therichdocumentsapp 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.comBoth 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.phpAn 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/discoveryCollabora 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_allowlistAlways 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 overwriteprotocolSet Nextcloud protocol
php occ config:system:set overwriteprotocol --value=httpsCheck WOPI allowlist
php occ config:app:get richdocuments wopi_allowlistSet 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.comCheck Nextcloud through NPM
curl -vk https://nextcloud.example.com/status.phpCheck Collabora through NPM
curl -vk https://collabora.example.com/hosting/discoveryInspect Docker DNS
docker info | grep -i dnscat /etc/resolv.confdocker 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:
- A shared Docker network is not required. Nextcloud and Collabora can communicate through their normal HTTPS hostnames and NPM.
- The physical LAN DNS record does not need to point into the Docker network. It can correctly point to NPM.
- Read the log before touching anything else. The WOPI denial message states both the source address and the configured ranges verbatim.
aliasgroup1=https://...does not by itself guarantee that every URL generated by the Nextcloud/Collabora integration will use HTTPS.- TrueNAS App environment variables may already be owned by the App template.
Don't duplicate variables such as
NEXTCLOUD_TRUSTED_DOMAINSwhen the App already defines them. - NPM's HTTP→HTTPS redirect can break backend WOPI requests.
If Collabora receives a URL beginning with
http://, it may get aMoved Permanentlyresponse 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.
References
- Setting up Nextcloud With Docker Compose — Thomas Wilde Tech
- CollaboraOnline/online#11280
- CollaboraOnline/online#11279
- ONLYOFFICE/Docker-DocumentServer#352 — Excessive processes and memory usage
- Nextcloud admin manual — Reverse proxy configuration (
overwriteprotocol,trusted_proxies) - nextcloud/richdocuments — the Nextcloud ↔ Collabora integration app
- Collabora Online (CODE) Docker image
- WOPI REST API reference
- Nginx Proxy Manager
- TrueNAS SCALE — custom App installation screens (DNS policy quote)