Known issues and limitations¶
Nextcloud Playground runs Nextcloud's PHP under the @php-wasm WebAssembly
runtime, where php_sapi_name() is wasm, the posix extension is absent, and
no external processes can be spawned. The core web UI, basic file operations, and
WebDAV work, but several features are dead or degraded in this sandbox.
Everything below is grounded in the Phase-0 feasibility spike. Don't promise features the spike says don't work.
No previews / thumbnails¶
Preview and thumbnail generation needs ffmpeg, libreoffice, or imagick —
none exist in WASM, and proc_open/exec cannot spawn processes. The playground
sets enabledPreviewProviders => []. Image/video/PDF thumbnails and the photos
preview pipeline are off.
No office / collaborative editing¶
Office and document-collaboration features (which rely on external services or process spawning) do not work. Collabora/OnlyOffice integrations are out of scope.
No antivirus¶
Antivirus scanning (files_antivirus, ClamAV, etc.) requires an external daemon
and process spawning — unavailable.
Letter avatars fall back to a solid colour¶
php-wasm's GD/FreeType cannot parse Nextcloud's bundled NotoSans-Regular.ttf,
so imagettfbbox() returns false. Without a guard this throws a TypeError
during letter-avatar generation (triggered by --admin-email at install and in
the UI). The Avatar.php patch (#5) returns a fallback box, so avatars render as
solid-colour tiles instead of drawn initials. Uploaded avatar images still
work.
No real cron — AJAX only¶
There is no cron.php daemon, no system crontab, and no pcntl-based workers in
the browser. Background jobs run only via AJAX cron, piggybacking on web
requests. Jobs that expect a reliable background scheduler (preview
pre-generation, full-text indexing, heavy maintenance) effectively don't run.
WebDAV is basic only¶
File access via remote.php (WebDAV) works for basic operations. Advanced
sync-client behaviour, chunked uploads at scale, and locking-dependent flows are
not guaranteed — see file locking below.
File locking is disabled¶
flock is unreliable under Emscripten; the shared/exclusive locks on
config.php return false and would fatal. The Config.php patches (#2/#3)
bypass those locks under wasm, and filelocking.enabled => false turns off
Nextcloud's transactional file locking. Concurrent-write protection that relies
on locking is therefore not in effect (the runtime is single-threaded anyway).
No Redis / APCu caching¶
apcu and redis extensions are absent. No memcache.local is configured, so
Nextcloud uses no-op caching. Expect slower repeated operations than a normal
server with a memory cache.
Memory limits¶
Nextcloud 31 extracts to 807 MB / 26,865 files; the playground ships a
trimmed bundle and still operates close to browser memory limits. WASM linear
memory grows but never shrinks, and large operations (big uploads, bulk imports)
can exhaust memory and crash the runtime (memory access out of bounds /
unreachable). Crash recovery restarts the runtime, but in-flight non-idempotent
work is lost. Keep operations modest.
Ephemeral by default¶
Mutable state (the SQLite database, user files, sessions) lives in the browser's
in-memory filesystem with optional journalling. Closing or hard-refreshing the
tab can discard changes, and a bundle/version mismatch resets state when
resetOnVersionMismatch is enabled. Treat the playground as a disposable demo,
not durable storage.
Trimmed app set¶
To fit the browser, heavy optional shipped apps are removed from the bundle
(e.g. password_policy, photos, text, suspicious_login,
files_pdfviewer). Only the default-enabled core set is guaranteed present;
enableApp only works for apps actually included in the bundle.
Other absent extensions / capabilities¶
Absent: pcntl, gmp, sodium, ftp, imap, ldap. OPcache's file cache is
on by default (see below). proc_open/exec/shell_exec
exist as functions but cannot spawn processes. Anything depending on those
(external auth backends, shell integrations) won't work.
OPcache¶
src/runtime/php-loader.js configures OPcache's file cache
(opcache.file_cache_only=1, bytecode under /internal/shared/opcache). Two
PHP-side facts shape what that means:
- PHP 8.3/8.4 never start OPcache under php-wasm's
wasmSAPI (php-src only accepts a fixed list of SAPI names), which is why the runtime reportsuwsgiinstead (below). With?opcache=0opcache_get_status()returnsfalseand nothing is cached. PHP 8.5 removed that whitelist, so the cache would run for real there (not offered by this playground today). - In file-cache-only mode
opcache_reset()andopcache_invalidate()are no-ops. The only invalidation is the script's mtime, compared in whole seconds (opcache.validate_timestamps=1,revalidate_freq=0; PHP's default 2 sfile_update_protectionkeeps a freshly written file out of the cache). Never setvalidate_timestamps=0here: files do change within a session (app install,writeFile/unzipsteps) and would keep executing old bytecode./internal/eval.php, the single path everyphp.run({ code })compiles, is blacklisted so snippets are never cached.
On by default ("opcache": true in playground.config.json; ?opcache=0
turns it off and ?opcache=1 forces it on; the "OPcache file cache" checkbox in
the Info panel sets the same parameter and restarts only the PHP runtime,
keeping the data): the runtime reports the uwsgi SAPI name before the first
request, which is on PHP's whitelist, so the file cache starts on PHP 8.3.
The default was flipped after the soak test in issue #106, using the
measurements of Moodle Playground
(ADR-0046:
2–3.5× faster navigation in Chromium, Firefox and WebKit, unchanged install
and reload time, about 400 MB lower peak browser RSS, no memory errors).
Consequences:
PHP_SAPI/php_sapi_name()returnuwsgiinside Nextcloud. The audit of the shipped Nextcloud 33 core found no branch on that name; the only SAPI-dependent code paths arecli/phpdbg(Composer autoloaders,Server.php, Snowflake,updater/),cli-server(Setup.php,Log/File.php) and the playground's ownwasmpatches (lib/base.phpocc detection,Config.phpflock bypass,console.phpowner check), whichscripts/build-nextcloud-bundle.shnow gates onwasmoruwsgi. The cache therefore needs a core bundle built from that script version: with an older bundleoccis not recognised as CLI and the install fails.- Memory: the cached
.binfiles live in MEMFS (tens of MB for Nextcloud) and WASM linear memory grows accordingly. Moodle Playground measured +32…71 MiB of WASM memory, yet a lower peak browser RSS with the cache live; Nextcloud was not measured separately. Its bootstrap already raisesmemory_limitto 512M (src/runtime/bootstrap.js), the ceiling Moodle needed with the cache live (its ADR-0044), so no limit change was needed; the canary asserts it. - Persistence: the bytecode is not journaled. Until October 2026
fs-persistence.jsmirrored/internal/shared/opcacheinto the IndexedDB storenextcloud-opcache:<phpVersion>; the core is re-extracted into MEMFS on every boot with fresh mtimes, so restored bytecode never passed timestamp validation, and with the cache live the journal alone made a clean boot ~2.4× slower (7.1 s → 17.4 s locally). The store was removed; boots delete anynextcloud-opcache:*database left by older sessions. Reloads recompile from scratch, which is the ephemeral model this playground follows. - A rewrite that restores a file's previous mtime to the second is the known blind spot of timestamp validation; no playground write path does that.
Same decisions as Moodle Playground ADR-0038 and ADR-0039; upstream discussion in WordPress/wordpress-playground#4465.
Harmless boot noise¶
These appear during install and are safe to ignore:
/bin/sh: inkscape: command not found— theming tries to rasterize SVG.- "The process control (PCNTL) extensions are required …" —
pcntlis optional (used only for Ctrl-C handling in occ).