What is the Nx remote cache?
Every cacheable Nx task - build, test, lint, typecheck, anything with "cache": true - goes through the same decision before it runs. Nx computes a hash of everything that could affect the task's result and checks its local cache at .nx/cache. If the hash is there, Nx restores the declared outputs and replays the recorded terminal output; the task "runs" in milliseconds. If not, the task executes and its result is stored under that hash for next time.
The remote cache is a second layer behind the local one. On a local miss, Nx asks a shared cache server for the same hash; on a remote hit it downloads and replays the artifact, and on a full miss it runs the task and uploads the result so every other machine gets the hit. The lookup order is always local first, then remote - a warm local cache never touches the network. The result: work is done once per input state, across the whole team, not once per machine.
Remote caching composes multiplicatively with nx affected. Affected narrows the task graph to projects reachable from the files a diff touched; the cache then eliminates the tasks inside that narrowed graph whose inputs did not actually change. A typical pull-request pipeline running nx affected -t build test lint against a warm remote cache truly executes only the handful of tasks the diff invalidated - everything else is either outside the affected graph or a cache hit. That combination, not either mechanism alone, is what cuts CI wall time by the 30-70% teams report. (If you want the tool-neutral background on remote caching first, start with What is a remote cache?)
How Nx computes the task hash
The cache is only as correct - and only as fast - as its key. Nx builds each task's hash from a well-defined set of inputs, and understanding them is the difference between a 90% hit rate and a cache that misses on every commit:
- Source files. The contents of the project's own files, filtered by the task's
inputs. Theproductionfileset convention excludes test files, so changing a spec does not invalidate the builds of every dependent project. - Dependency inputs. The
^production(dependencies' production files) entry pulls upstream projects into the hash, so a change in a shared library correctly invalidates everything downstream - and nothing else. - Named inputs.
namedInputsinnx.jsondefine reusable filesets likedefaultandproductiononce, so every target agrees on what "my sources" means. - Runtime and environment inputs.
{ "runtime": "node --version" }puts the toolchain version in the key;{ "env": "MY_FLAG" }includes an environment variable that changes the output. Anything that affects the result but is not a file must be declared this way, or the cache will serve results built under different conditions. - External dependencies and global config. The lockfile and workspace-level configuration participate, so a dependency bump or an
nx.jsonchange re-keys affected tasks.
outputs are the other half of the contract: they tell Nx which files to store on a miss and restore on a hit ({workspaceRoot}/dist/{projectName}, coverage directories, and so on). Undeclared outputs are silently not cached, which shows up as "the build was a hit but my artifact is missing". Getting inputs and outputs right is the highest-leverage tuning work in an Nx workspace - the full walkthrough is in Tuning Nx cache inputs and outputs.
Setup: the self-hosted API and NX_SELF_HOSTED_REMOTE_CACHE_SERVER
Since Nx 19.8, remote caching has an official, open integration point: the self-hosted remote cache API. It is a small HTTP protocol - GET and PUT against /v1/cache/<hash> with a Bearer token - documented by Nx with an OpenAPI spec, and any server that implements it works with an unmodified Nx CLI. No plugin to install, no task runner to swap: Nx activates the remote layer when two environment variables are present - NX_SELF_HOSTED_REMOTE_CACHE_SERVER, the bare host of the cache server, and NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN, the credential it authenticates with:
NX_SELF_HOSTED_REMOTE_CACHE_SERVER=https://remote.cachely.dev
NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN=<your token>Give NX_SELF_HOSTED_REMOTE_CACHE_SERVER the host only. Nx appends the /v1/cache/<hash> route itself, so writing the path in yourself turns every lookup into a 404 - the single most common setup mistake. With neither variable set, Nx falls back to the local cache in .nx/cache and nothing leaves the machine.
That is the entire client-side setup, and it works identically everywhere Nx runs. On a laptop, put the variables in your shell profile or a local .env file (Nx loads .env / .env.local automatically). In CI, set them as pipeline secrets - for GitHub Actions, an env: block at the workflow level covers every step:
# .github/workflows/ci.yml
env:
NX_SELF_HOSTED_REMOTE_CACHE_SERVER: ${{ vars.NX_CACHE_SERVER }}
NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: ${{ secrets.NX_CACHE_TOKEN }}From then on, every nx build, nx run-many, and nx affected consults the remote cache automatically. Behavior on failure is deliberately boring: an unreachable server or a 404 is treated as a miss, the task runs locally, and the result is uploaded when the server is reachable again. The cache is an optimization, never a dependency in the critical path. The step-by-step version - create a workspace, mint a token, set the variables - is in the docs.
Your options: Nx Cloud, DIY, or managed
The client protocol is settled; the real decision is who runs the server. There are three honest answers:
Nx Cloud
Nx Cloud is the Nx team's own platform, and remote caching is one feature of a much larger product: distributed task execution (spreading one command's task graph across many agents), managed CI agents, flaky-test detection and re-runs, and run analytics. If you want that platform - especially DTE, which nothing else replicates - Nx Cloud is the right choice, and its cache is first-party and excellent. The trade-off is that you adopt platform pricing (per-seat plans and credit-metered usage) to get caching, and for many teams caching is the only piece they came for. The detailed comparison is in Cachely vs Nx Cloud.
DIY self-hosted
The self-hosted API means you can run your own server: open-source implementations like nx-cache-server, or an object-storage bucket (S3, GCS, Azure) behind an adapter. You get full control and data locality, and you own hosting, scaling, auth, monitoring, and upkeep. One cautionary tale is worth knowing: the previous generation of first-party bucket adapters - the Nx Powerpack packages @nx/s3-cache, @nx/gcs-cache, @nx/azure-cache, and @nx/shared-fs-cache - were deprecated after CVE-2025-36852 showed their shared-bucket-credential design allowed cache poisoning that could not be patched out (more below). If a bucket is specifically what you were looking for, the options are laid out in the Nx S3 cache guide. Any DIY setup has to solve the same problem those packages could not: server-side write control. The full picture of what running your own cache involves is in Cachely vs a self-hosted Nx cache and the self-hosted-without-infrastructure walkthrough.
A managed implementation of the protocol
The third option keeps the self-hosted API's open protocol but hands the server to a service: someone else operates the storage, tokens, immutability enforcement, and monitoring, and your Nx config is still just the two environment variables - no lock-in beyond changing a URL. Cachely is in this category: a managed implementation of the Nx self-hosted remote cache API (and the Turborepo protocol) with read-only tokens and immutable artifacts enforced at the API, hit-rate and time-saved reporting, and flat pricing with no per-seat fees. It is the right fit when caching is what you want and operating cache infrastructure is not; it is the wrong fit if you need distributed task execution (use Nx Cloud) or on-premises data (self-host). See also Cachely vs the Powerpack cache packages for the migration path Nx points deprecated-adapter users at.
Security: CVE-2025-36852 and read-only tokens
A remote cache is a supply-chain component: trusted builds execute whatever it serves. The attack that matters for Nx is CVE-2025-36852, published as CREEP (Cache Race-condition Exploit Enables Poisoning). The shape of it: in a setup where untrusted builds share write credentials with trusted builds - the default when every pipeline holds the same bucket keys - a fork pull request can compute the hash a future trusted build will look up (hashes are deterministic; that is the whole point) and upload a malicious artifact under it first. When main later runs, it gets a "hit", restores the attacker's outputs, and ships them. No malicious code ever has to land in your repository.
Two server-side properties close the attack, and you should demand them from any Nx remote cache setup:
- Read-only tokens for untrusted builds. PR and fork pipelines get tokens that can download (so they stay fast) but never upload. This must be enforced by the server per token - a bucket credential that can write anywhere fails this test by construction, which is exactly why the Powerpack adapters could not be fixed.
- Immutable, content-addressed artifacts. Once a hash exists, re-uploads to it are rejected (409), so a known-good artifact can never be silently replaced - even by a compromised write token.
The full attack walkthrough is in Cache poisoning and CVE-2025-36852, and the trust model Cachely enforces - including per-workspace isolation and no repository access, with only customer-configured task outputs sent through the cache protocol - is documented on the security page.
Clearing, skipping, and disabling the Nx cache
Three different things get called "clearing the Nx cache", and mixing them up is why a cache appears not to reset. There is no nx cache clean command; the command is nx reset, and by itself it does more than empty the cache.
Clearing the local cache
npx nx reset # clear the local cache, workspace data, and stop the daemon
npx nx reset --only-cache # clear only the local cache directory
npx nx reset --only-daemon # only restart the daemon
npx nx reset --only-workspace-data # only clear cached workspace metadataThe local cache lives in .nx/cache inside the workspace, so deleting that directory is equivalent to nx reset --only-cache. The important caveat: none of these touch the remote cache. A reset only empties the machine you ran it on, and the next task will happily restore the same outputs from the shared cache - which is usually what you want, and occasionally very confusing. If you genuinely need a remote entry gone, evict it on the server side; with Cachely that is a workspace-level operation in the dashboard, not a CLI flag.
The step-by-step version of this section - every nx reset flag, moving the cache directory, and the recurring problems that clearing does not fix - is in How to clear the Nx cache.
Skipping the cache for one run
To force real execution without changing any configuration, skip the cache at the call site:
# rerun tasks even when a cached result exists (local or remote)
npx nx run-many -t build --skip-nx-cache
# turn the remote cache off for this run; the local cache still works
npx nx run-many -t build --skip-remote-cache--disable-nx-cache and --disable-remote-cache are accepted aliases of the same two flags. Mind the scope of each: --skip-nx-cache bypasses the cache entirely and does not write the fresh result back, while --skip-remote-cache disables remote reads and writes but leaves the local cache working normally. Neither can replace a bad remote entry - Cachely artifacts are immutable, so a poisoned or stale entry is removed by server-side eviction, not overwritten.
Disabling caching for a target
Skipping is per run; disabling is permanent and belongs in configuration. Set cache: false on the target - in targetDefaults for every project, or in a single project.json to scope it:
// nx.json - disable caching for one target everywhere
{
"targetDefaults": {
"deploy": { "cache": false }
}
}This is the right tool for targets that are not honestly cacheable: anything with side effects (deploys, publishes, database migrations), anything whose real output is not a file, and anything non-deterministic enough that replaying a previous result would be wrong. A task marked cache: true is a promise that the same inputs always justify the same outputs - if that is not true, disable caching rather than fight the hashes.
Troubleshooting low hit rates
A correctly wired remote cache with a low hit rate almost always has the same root cause: over-keying - something volatile is leaking into the task hash, so identical work produces different keys. The usual suspects:
- Inputs that are too broad. If a target's
inputsinclude the default "all project files" fileset, editing a README or a test invalidates production builds. Use theproductionnamed input for build targets and keep docs, specs, and config-that-does-not-matter out of it. - Environment leaks. A declared
envinput that changes every run - a build number, a timestamp variable, a per-runner path - re-keys every task. Declare only variables that genuinely change the output, and make CI set them deterministically. - Non-deterministic outputs upstream. A code generator that stamps dates, an install step that rewrites a file with unstable ordering, or a tool that embeds absolute paths makes a dependency's output differ per machine - and since downstream hashes include
^production, every consumer misses too. Fix the determinism at the source. - Divergent toolchains. If laptops run one Node version and CI another, a
runtimeinput splits the cache in two. Pin versions (.nvmrc) so everyone hashes alike.
Diagnose with evidence, not guesses: run the same task twice from the same commit on two machines and compare hashes (nx run <project>:<target> --verbose prints hash details), and watch your cache server's per-task hit rates to see which targets never hit. A cache nobody measures quietly decays as inputs drift - the measurement side is covered in How much can a managed remote cache reduce CI build times?
Related guides
- What is a remote cache?The pillar guide to content-hashed keys, local vs remote caching, and safety.
- Nx S3 cacheWhat to do after @nx/s3-cache was deprecated, and when a bucket still fits.
- Nx vs Turborepo cachingProtocol setup, security, and performance tradeoffs for choosing between them.
- Nx Cloud alternativeWhen Nx Cloud fits and when a focused managed remote cache is the better call.
- Improve cache hit rateDiagnose over-keying, env drift, unstable outputs, and missing outputs.