Nx migration guide

Moving off nx-remotecache-custom and tasksRunnerOptions

If your Nx remote cache runs through nx-remotecache-custom or another custom runner in tasksRunnerOptions, Nx 20 started warning about it and Nx 21 stopped loading it. This page shows the exact messages, what works on each Nx version, the smallest upgrade onto the built-in HTTP cache, and the nx.json change. Cachely requires Nx 20.8 or newer.

Updated Reviewed by the Cachely team

The messages you are seeing

On Nx 20.0 through 20.7, every command that loads a custom task runner prints this warning before the tasks start:

NX   Custom task runners will no longer be supported in Nx 21. Use Nx Cloud or the Nx Powerpack caches instead.

Nx 20.8 reworded it to Custom task runners will be replaced by a new API starting with Nx 21. and pointed at the custom task runner deprecation page. Search results and answers about nx.json usually quote the same change as:

As of Nx 20, the tasksRunnerOptions property in nx.json is deprecated

Nx 21 and later print nothing, and that is the real trap. Nx no longer loads the runner named in tasksRunnerOptions. Unless the entry points at Nx Cloud, it uses its default runner instead, so every task still succeeds but only the local cache is used. CI gets slower with no error to search for. If your cache hit rate dropped after a major upgrade, check this first.

What changed, and when

  • Nx 20.0, released 2024-10-07, deprecated custom task runners and the tasksRunnerOptions runner field. The same day, nrwl/nx discussion #28332 asked why custom runners were being dropped.
  • nrwl/nx#28434 explained the reasoning: Nx core was moving to Rust, and the runner API let packages change the task lifecycle in ways that broke invariants Nx depends on. Its closing update listed the replacements: lifecycle hooks, free self-hosted cache packages, and a documented cache API.
  • Nx 20.4 added the preTasksExecution and postTasksExecution plugin hooks, for runners that did more than caching, such as validating the environment or reporting results.
  • Nx 20.8, released 2025-04-14, added the self-hosted remote cache client: Nx speaks HTTP to any server that implements its remote cache OpenAPI specification, configured with a URL and a token. No plugin, no runner.
  • Nx 21.0, released 2025-05-05, removed custom task runners.

nx-remotecache-custom is the library most self-hosted runners were built on, including nx-remotecache-azure, @pellegrims/nx-remotecache-s3, nx-remotecache-minio, and nx-remotecache-redis. Its README now marks it deprecated, says filesystem-based custom caching will not work starting with Nx 21, and points to the maintainer's pinned issue #48 on its future. The README also lists CVE-2025-36852, the cache-poisoning flaw known as CREEP, with no planned fix. Any build holding the shared write credential can overwrite any artifact; the CREEP write-up covers how that plays out.

Plenty of CI still runs on this path. In npm's figures as of October 2026, nx-remotecache-custom had roughly 653,000 downloads in the previous month, 82% of them on its 19.x and 20.x lines, and about 31% of nx downloads were Nx 20 or older. Downloads are not teams, but the migration is clearly not finished for most of them.

What works on which Nx version

Custom task runners, the built-in HTTP remote cache, and Cachely support by Nx version.
Nx versionCustom runner in tasksRunnerOptionsBuilt-in HTTP cacheCachely
Nx 19.xWorks with nx-remotecache-custom 19.x. No deprecation warning.NoNot supported
Nx 20.0 - 20.7Works with nx-remotecache-custom 20.x, and prints the "no longer supported in Nx 21" warning on every run.NoNot supported
Nx 20.8.xStill loads, with a reworded warning. The last line where the old runner and the new cache both work.YesSupported
Nx 21 and laterNot loaded. Nx falls back to its default runner: tasks pass, only the local cache is used, nothing is printed.YesSupported

The row that matters is 20.8. It is the only line where your existing runner still loads and the new HTTP client is available, so you can upgrade Nx first, confirm nothing else broke, and swap the cache backend as a separate, reversible change. Jumping straight from a runner on Nx 19 to Nx 21 combines two migrations, and the cache half fails silently.

The smallest upgrade path to Nx 20.8

1. Upgrade Nx and keep the runner for now

20.8.4 is the last Nx 20 release. nx migrate updates nx and the first-party @nx/* plugins together and writes the code migrations to run:

npx nx migrate 20.8.4
npm install
npx nx migrate --run-migrations

From Nx 19 this is one major version. From 18 or earlier, migrate one major at a time. Leave nx-remotecache-custom on its 20.x line during this step; it still works on 20.8, so a build failure here is about the upgrade, not the cache. The usual blockers are a framework version that the @nx/* plugins pin, such as Angular or Jest, and third-party plugins without a release for the new major.

2. Pin the same version everywhere

Commit the lockfile and make sure CI and developer machines run the workspace's nx, not a global install. Mixed versions produce different task hashes and look exactly like a broken cache.

3. Swap the cache, then keep upgrading

Make the nx.json change below and set the two environment variables. Once remote hits are confirmed, 20.8 has done its job: it is the protocol boundary, not a version to stay on. Continue to a current Nx release with Nx's update guide; the cache configuration does not change again.

nx.json before and after

A typical runner configuration names the runner package and lists the cacheable targets. On the built-in cache, the runner disappears and cacheability moves to targetDefaults:

 {
-  "tasksRunnerOptions": {
-    "default": {
-      "runner": "nx-remotecache-mystorage",
-      "options": {
-        "remoteUrl": "https://cache.example.com",
-        "cacheableOperations": ["build", "test", "lint", "e2e"]
-      }
-    }
-  },
+  "targetDefaults": {
+    "build": { "cache": true },
+    "test": { "cache": true },
+    "lint": { "cache": true },
+    "e2e": { "cache": true }
+  }
 }

If you have run the Nx 17 migrations, cacheableOperations may already have been copied into targetDefaults, and the change is only deleting the block. Then remove the packages and give Nx the server URL and token in CI secrets and developer shells:

npm uninstall nx-remotecache-custom nx-remotecache-mystorage

NX_SELF_HOSTED_REMOTE_CACHE_SERVER=https://remote.cachely.dev
NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN=<your-token>

The remaining runner options map like this:

  • remoteUrl and any bucket, connection-string, or key options become the two environment variables above.
  • write: false or NXCACHE_WRITE=false on pull-request builds becomes a read-only access token, which the server enforces rather than trusting the client.
  • read, dotenv, dotenvPath, name, verbose, and silent have no equivalent and can be deleted with the block.

Any non-cache logic in a custom runner moves to preTasksExecution and postTasksExecution hooks. The full client setup, including GitHub Actions secrets, is in the Nx setup guide.

Verify the new cache

Run the targets once with a read-write token to populate the cache, then clear the local cache with npx nx reset --only-cache, delete the build outputs, and rerun with unchanged inputs. Tasks restored from the server show [remote cache]. The verification steps walk through it, including what to check when every task misses.

Existing artifacts do not carry over. The new backend starts empty, so the first CI run after the switch is a full rebuild and every run after it can hit. Keep the old bucket until you are satisfied, then delete it and revoke its credential. If you would rather keep running the storage yourself, the self-hosted comparison and the guide to replacing the deprecated Nx cache packages cover servers that implement the same API.

Can't upgrade yet?

Cachely does not work below Nx 20.8. It relies on Nx's built-in cache client and ships no task runner for older versions. Until you can upgrade, the safest thing you can do with an existing runner is limit who can write: give the write credential only to CI on protected branches, never to pull requests or developer laptops. Nx Cloud also remains an option on older versions if you need a managed cache today.

Cachely is evaluating compatibility support for Nx versions older than 20.8. That support is not available today. We want to understand existing cache setups, upgrade blockers, and interest in managed caching before deciding whether to build it. There is no commitment or release date.

Set it upSet up Nx with CachelyTwo environment variables, the bare-host rule, and the Nx versions that share a cache.

Related guides

On Nx 20.8+? Start free
Two environment variables replace the runner. Read-only tokens for pull requests and immutable artifacts are enforced at the API.
Start freeSee pricing
FAQ

nx-remotecache-custom migration: frequently asked questions

Does nx-remotecache-custom work with Nx 21?
No. Nx 21 no longer loads custom task runners from tasksRunnerOptions. Unless the entry points at Nx Cloud, Nx uses its default runner instead, so tasks still run but only the local cache is used and no warning is printed. The package is deprecated, and its compatibility table marks Nx 21 and later as unsupported.
What replaced tasksRunnerOptions for remote caching?
The self-hosted remote cache client added in Nx 20.8. Nx talks HTTP to any server that implements its remote cache OpenAPI specification, configured with NX_SELF_HOSTED_REMOTE_CACHE_SERVER NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN. Which targets are cacheable moves to cache: true in targetDefaults, and non-cache runner logic moves to the preTasksExecution and postTasksExecution plugin hooks added in Nx 20.4.
Do I have to upgrade straight to Nx 21?
No. Nx 20.8 still loads your existing runner and also has the new HTTP client, so you can upgrade Nx first, swap the cache as a separate change, and then continue to a current release. Going from a runner on Nx 19 directly to Nx 21 combines both migrations, and the cache half fails silently.
Does Cachely support Nx 19 or Nx 20.0 to 20.7?
No. Cachely requires Nx 20.8 or newer because it relies on the built-in HTTP cache client. There is no compatibility runner for older versions and no commitment to build one. If an older version blocks you, the form on this page records your setup so we can measure demand.
Will my existing cached artifacts carry over?
No. The new backend starts empty, so the first CI run after the switch rebuilds everything and later runs can hit. Keep the old storage until remote hits are confirmed, then delete it and revoke its credential.
Is nx-remotecache-custom affected by CREEP (CVE-2025-36852)?
Its README lists CVE-2025-36852 with no planned fix. Any build holding the shared write credential can overwrite cached artifacts that other builds then trust. On the HTTP cache, give pull-request builds a read-only token so the server, not the client, decides who can write.
Should I move to Nx Cloud instead?
If you want distributed task execution or managed CI agents, yes - Nx Cloud is the better fit. Cachely is a remote cache only: it implements the same self-hosted API, keeps Nx as your task runner, and adds read-only tokens and immutable artifacts.