I’ve been running owncloud, then nextcloud for years. While it worked I was never very happy with it. Performance was always lackluster even for 1-2 users, features added are seemingly superfluous and was never something I wanted or cared about (groupware, AI assistant, talk features – don’t use, need or care).
All I want is a reliable lean google drive or onedrive alternative, clean files-only minimal interface, some apps on ios and android, ability to share and edit files. I’ve been on the lookout for alternatives for years but there was nothing really good out there until now. Already has android and apple mobile apps.
Enter opencloud. I’ve been running it for 2 months now and can recommend switching. Here’s how I run it using docker compose. Note that this is unofficial docker-compose.yml. Official docker-compose.yml is over here, but myself and many other people find it too complicated. Examples also focus on traefik while people might be using other reverse proxies.
Note: This is tutorial is only to illustrate how I personally run my personal opencloud service. This post is provided for informational and educational purposes only. It does not constitute security advice, and no statement is made regarding the suitability or fitness of these configurations for production or secure deployment.
Lemmy thread: https://programming.dev/post/40034135/20294850
- Opencloud screenshots
- How I run opencloud in docker compose with nginx proxy manager as a reverse proxy
- Opencloud – docker-compose.yml
- Opencloud – docker compose .env file
- Opencloud – csp.yaml. yes this is needed
- Opencloud reverse proxy – Nginx proxy manager on a public VPS
- Join opencloud chat (matrix)
- What I would say to Nextcloud
- In conclusion
Opencloud screenshots
Sharing screenshots so people can see how it looks like. People that are looking to switch are probably interested in this the most.
What I love about it – interface is clean, uncluttered, and to the point.












Various settings pages:



How I run opencloud in docker compose with nginx proxy manager as a reverse proxy
Opencloud docker setup could be a bit convoluted if you want collabora (collabora is a selfhosted service for editing documents) or calendar (you need to add radicale container). If you don’t use collabora for editing microsoft word or open documents – you can just run opencloud as a single container (fine for people that dont care about docs) – you can still do text and markdown file editing with a single container opencloud setup.
If you want collabora – you need 3 containers: collabora, wopi and opencloud. I’m running with collabora and these examples are for 3 container setup with collabora.
I run opencloud containers straight on my NAS server running ubuntu LTS, I then expose container ports on tailscale only, and then I route it via nginx proxy manager through my public VPS via tailscale.
This avoids cloudflare as I dont like using cloudflare, I’m sure you can set this up with cloudflare as well.
You don’t necessarily need tailscale and nginx proxy manager unless you want to expose opencloud via public VPS server just like I do, or just want to use private openlcoud on tailscale VPN (good idea).
I use these 3 domains (Using example.com is just an example). Why? Because I own example.com, I then have a VPS that i identify as toolski17.example.com, I then run 3 services on that VPS.
These are just example urls, my setup uses different DNS names:
- https://fence-red5-opencloud.toolski17.example.com
- https://fence-red5-wopi.toolski17.example.com
- https://fence-red5-collabora.toolski17.example.com
Why such complex dns names? for extra obscurity. My Nginx Proxy Manager is set to return a 444 (non-standard nginx status code that means no-response) if a request doesn’t match a known domain. It completely cuts off random bots and automated probes — there is 0 junk traffic anymore. I get that some people prefer going full-private with Tailscale and not exposing anything to the public internet at all, and that’s totally valid. But for me, this setup strikes a good balance: everything’s still accessible when I need it, without the constant noise from scanners.
Edit: To clarify for my friends from the “obscurity is not security” crowd who immediately jumped in — I should have been clearer. I’m using wildcard SSL and DNS (e.g., *.toolski17.example.com). That means only *.toolski17.example.com is visible to Let’s Encrypt and the registrar. For an attacker to even begin targeting the application, they’d first have to guess a valid subdomain (brute force and internet latency maths apply) — everything else instantly receives a 444 response from Nginx. After that, they’d still face standard OpenCloud authentication and authorization. And yes, all the usual hardening measures still apply — geo-blocking, WAF, OIDC, rate limiting, etc. This setup isn’t meant to replace proper security, just to eliminate noise and random scans. My point is that, in practice, most self-hosters don’t have the skills, time, or infrastructure to properly implement things like GEO restrictions, WAFs, or OIDC. For the majority who simply expose their services to the public internet “as is,” using randomized or hidden subdomains is a simple first barrier that immediately cuts off 99% of random IPv4 scans, crawlers, and automated probes. Those scanners aren’t harmless – they often collect and fingerprint services, waiting for a 0-day vulnerability to be published so they can auto-exploit any matching instances. So while this isn’t “security” in the strict sense, it’s a very practical noise and risk-surface reduction measure. Real defense in depth still means proper authentication, authorization, WAF, rate limiting, and monitoring – and I’ve been explicit that this approach does not replace any of those. I agree with your larger point that obscurity alone isn’t security, but for many home or small-scale self-hosters, it’s a useful additional layer that drastically reduces unwanted exposure while they work toward more robust protections.
Edit 2: Per your request, I added a bold disclaimer at the top clarifying that security is not the focus of this post, as I don’t have time to go back and forth on the secret subdomain topic. This approach works well for me and may benefit the majority of self-hosters, but as always — your mileage may vary, so decide for yourself.

Opencloud – docker-compose.yml
networks:
default:
external: true
name: infranet
# Example from here: https://www.reddit.com/r/selfhosted/comments/1n5kgvi/looking_for_a_reliable_nextcloud_alternative/
services:
opencloud-app:
restart: always
image: opencloudeu/opencloud-rolling:latest
hostname: opencloud-app
container_name: opencloud-app
user: "1000:1000"
logging:
driver: "json-file"
options:
max-file: "5"
max-size: "10m"
environment:
# enable services that are not started automatically
OC_ADD_RUN_SERVICES: "notifications"
OC_URL: "https://fence-red5-opencloud.toolski17.example.com"
OC_LOG_LEVEL: "info"
OC_LOG_COLOR: "false"
OC_LOG_PRETTY: "false"
PROXY_TLS: "false"
OC_INSECURE: "true"
### demo users ###
IDM_CREATE_DEMO_USERS: "false"
PROXY_HTTP_ADDR: 0.0.0.0:9200
FRONTEND_ARCHIVER_MAX_SIZE: 1099511627776 # 1 TiB
# (optional) if you also need more files per archive:
FRONTEND_ARCHIVER_MAX_NUM_FILES: 500000
### Notifications Settings ###
NOTIFICATIONS_SMTP_HOST: blah.mxrouting.net
NOTIFICATIONS_SMTP_PORT: 587
NOTIFICATIONS_SMTP_SENDER: "OpenCloud <donotreply@example.com>"
NOTIFICATIONS_SMTP_USERNAME: donotreply@example.com
NOTIFICATIONS_SMTP_PASSWORD: FILL_IN_YOUR_PASS
NOTIFICATIONS_SMTP_INSECURE: false
NOTIFICATIONS_SMTP_AUTHENTICATION: plain
NOTIFICATIONS_SMTP_ENCRYPTION: starttls
STORAGE_USERS_DRIVER: posix
STORAGE_USERS_ID_CACHE_STORE: nats-js-kv
### Wopi Server Settings ###
WOPISERVER_DOMAIN: "fence-red5-wopi.toolski17.example.com"
# Stuff needed for collabora
PROXY_CSP_CONFIG_FILE_LOCATION: "/etc/opencloud/csp.yaml"
FRONTEND_APP_HANDLER_SECURE_VIEW_APP_ADDR: "eu.opencloud.api.collaboration.CollaboraOnline"
### NATS Settings - by default it listens on 127.0.0.1 ###
NATS_NATS_HOST: 0.0.0.0
NATS_NATS_PORT: 9233
### GRPC Settings ###
GATEWAY_GRPC_ADDR: "0.0.0.0:9142"
### Collabora Settings ###
COLLABORA_DOMAIN: "fence-red5-collabora.toolski17.example.com"
COLLABORA_ADMIN_USER: "admin"
COLLABORA_ADMIN_PASSWORD: "FILL_IN_YOUR_PASS"
COLLABORA_SSL_ENABLE: false
COLLABORA_SSL_VERIFICATION: false
volumes:
- ${FS_DIR}/data:/var/lib/opencloud
- ${FS_DIR}/config:/etc/opencloud
#exposing port to internal Tailscale network only
ports:
- ${TAILSCALE_IP}:${OPENCLOUD_PORT}:${OPENCLOUD_PORT}
opencloud-collaboration:
image: opencloudeu/opencloud-rolling:latest
container_name: opencloud-collaboration
depends_on:
opencloud-app:
condition: service_started
opencloud-collabora:
condition: service_healthy
restart: always
entrypoint:
- /bin/sh
command: ['-c', 'opencloud collaboration server']
environment:
OC_LOG_LEVEL: info
COLLABORATION_GRPC_ADDR: 0.0.0.0:9301
COLLABORATION_HTTP_ADDR: 0.0.0.0:9300
MICRO_REGISTRY: "nats-js-kv"
MICRO_REGISTRY_ADDRESS: "opencloud-app:9233"
COLLABORATION_WOPI_SRC: "https://fence-red5-wopi.toolski17.example.com"
COLLABORATION_APP_NAME: "CollaboraOnline"
COLLABORATION_APP_PRODUCT: "Collabora"
COLLABORATION_APP_ADDR: "https://fence-red5-collabora.toolski17.example.com"
COLLABORATION_APP_ICON: "https://fence-red5-collabora.toolski17.example.com/favicon.ico"
COLLABORATION_APP_INSECURE: true
COLLABORATION_CS3API_DATAGATEWAY_INSECURE: true
COLLABORATION_LOG_LEVEL: info
OC_URL: "https://fence-red5-opencloud.toolski17.example.com"
volumes:
- ${FS_DIR}/config:/etc/opencloud
ports:
#exposing port to internal Tailscale network only
# - 9300:9300
- ${TAILSCALE_IP}:${COLLABORATION_PORT}:${COLLABORATION_PORT}
opencloud-collabora:
image: collabora/code:latest
container_name: opencloud-collabora
cap_add:
- MKNOD
environment:
aliasgroup1: https://fence-red5-wopi.toolski17.example.com:443
DONT_GEN_SSL_CERT: "YES"
extra_params: |
--o:ssl.enable=false \
--o:ssl.ssl_verification=false \
--o:ssl.termination=true \
--o:welcome.enable=false \
--o:net.frame_ancestors=fence-red5-opencloud.toolski17.example.com
username: admin
password: 'FILL_IN_YOUR_PASS'
domain: "fence-red5-collabora\\.toolski17\\.aurora\\.dev|fence-red5-opencloud\\.toolski17\\.aurora\\.dev|fence-red5-wopi\\.toolski17\\.aurora\\.dev|opencloud-app|opencloud-collaboration"
restart: always
entrypoint: ['/bin/bash', '-c']
command: ['coolconfig generate-proof-key && /start-collabora-online.sh']
healthcheck:
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/9980 && echo -e 'GET /hosting/discovery HTTP/1.1\r\nHost: localhost:9980\r\n\r\n' >&3 && head -n 1 <&3 | grep '200 OK'"]
ports:
#exposing port to internal Tailscale network only
- ${TAILSCALE_IP}:${WOPI_PORT}:${WOPI_PORT}
Opencloud – docker compose .env file
#General settings
COMPOSE_PROJECT_NAME=opencloud
# Where data is stored for opencloud on my nas
FS_DIR=/mnt/nas-pool-furyroad/dockervolumes/opencloud
#ports
OPENCLOUD_PORT=9200
COLLABORATION_PORT=9300
WOPI_PORT=9980
#Nas Host Tailscale network ip
TAILSCALE_IP=100.93.99.20
Opencloud – csp.yaml. yes this is needed
I had to set it up manually. This is a content security policy file and various UI operations will fail if you don’t have it.
This file is used by main opencloud service:
# csp file needed for collabora
PROXY_CSP_CONFIG_FILE_LOCATION: "/etc/opencloud/csp.yaml"
directives:
child-src:
- "'self'"
connect-src:
- "'self'"
- 'blob:'
- 'https://fence-red5-wopi.shedsky1.example.com/'
- 'wss://fence-red5-wopi.shedsky1.example.com/'
- 'https://raw.githubusercontent.com/opencloud-eu/awesome-apps/'
- 'https://fence-red5-opencloud.shedsky1.example.com/'
- 'https://update.opencloud.eu/'
default-src:
- "'none'"
font-src:
- "'self'"
frame-ancestors:
- "'self'"
frame-src:
- "'self'"
- 'blob:'
- 'https://embed.diagrams.net/'
- 'https://fence-red5-collabora.shedsky1.example.com/'
- 'https://docs.opencloud.eu'
img-src:
- "'self'"
- 'data:'
- 'blob:'
- 'https://raw.githubusercontent.com/opencloud-eu/awesome-apps/'
- 'https://fence-red5-collabora.shedsky1.example.com/'
manifest-src:
- "'self'"
media-src:
- "'self'"
object-src:
- "'self'"
- 'blob:'
script-src:
- "'self'"
- "'unsafe-inline'"
- "'unsafe-eval'" # <-- allow eval()/new Function()
style-src:
- "'self'"
- "'unsafe-inline'"
Opencloud reverse proxy – Nginx proxy manager on a public VPS
I prefer this route when exposing a service from my local network. Basically if I want service to be publicly available, – I route it from my home network via tailscale and expose via remote public VPS I own and some domain name I own using nginx proxy manager reverse proxy.
Here is an example: just point domains to the tailscale DNS names.
Again – I use tailscale purely to connect from public VPS server to my homenetwork.
a. I add 3 proxies in nginx proxy manager:

b. Nginx proxy config looks something like this – note how I point to my NAS tailscale dns:

c. Just make sure to increase nginx timeouts. (advanced tab in nginx proxy manager), otherwise transferring a lot of files could fail.
proxy_connect_timeout 60s;
proxy_send_timeout 4h; # or 14400s
proxy_read_timeout 4h; # or 14400s
send_timeout 4h; # or 14400s
# these help WebDAV COPY/MOVE and uploads
# disabled as im not sure this is needed
# proxy_request_buffering off;
# proxy_buffering off;
Join opencloud chat (matrix)
They have good active matrix channel (that’s a chat app): https://matrix.to/#/#opencloud:matrix.org
If you need help and support community seems active and growing.
What I would say to Nextcloud
You need to refactor your backend. No – you cannot build application like nextcloud on share-nothing per request PHP backend. What you have now is a buggy, unscalable mess of a product that can only survive on top of redis caching and being entirely free. You need to convert nextcloud to a long running in-memory always-on application with heavy use of async/coroutines.
@nextcloud team – If you want to refactor quickly – use swoole on top PHP, use hyperf framework. This way you can keep PHP, but shift entirely how it works under the hood.
Switch to long-running paradigm, parallelize database calls (improves performance), use coroutines, use swoole built-in side processes, use built-in cronjobs abilities, offload async tasks to nonblocking TaskExecutor using side nonblocking task workers.
Look at the benchmark I’ve done to illustrate the differene – it barely scratches the surface of what is possible with swoole.
Websockets, socket.io, tcp server, grpc server -> these are a killer features most PHP apps simply incapable of. You need them for nextcloud. – I dont have time to list it all. Just take a look at this list https://hyperf.wiki/3.1/#/en/ and if you’ve been in a PHP field for a while the difference of possibilities will be self-evident. it’s a night and day.
In conclusion
Switch to opencloud today, it’s fast, clean, lean and overall amazing. And maybe nextcloud will get their act together (it should better be soon).
Haha, what a ride through the setup jungle! Love the dedication to obscurity – finally found a way to make my OpenCloud so hidden even I cant find it half the time. And the explanation about obscurity *not* being security? Brilliant! Keep up the hilarious complexity, folks! Works for me, mostly. Ymmv… or maybe it just means youre less likely to get hacked by bots trying to guess subdomains. 😉
For a program that is supposedly so lean and modern… that’s an awful looking compose file!