What is Turborepo remote caching?
Turborepo caches every task it runs. Out of the box that cache is local: when turbo run build executes a task, it stores the task's outputs and terminal logs under .turbo/cache (and node_modules/.cache/turbo), keyed by a hash of the task's inputs. Re-run the same task with nothing changed and turbo replays the stored result in milliseconds, printing the original logs as if the task had run.
Remote Caching is the second layer: a shared, network-accessible cache that every machine consults before falling back to a real run. When a task's hash misses locally, turbo asks the remote cache server; a hit downloads and unpacks the artifact, a miss runs the task and uploads the result so the next machine - a teammate's laptop or a clean CI runner - gets the hit. The terms Turborepo remote cache and Turborepo remote caching describe the same feature; Vercel's docs capitalize it as Remote Caching.
The payoff concentrates in CI. Ephemeral runners start with an empty .turbo directory on every job, so without a remote cache they rebuild the whole affected graph every time - even when the previous pipeline built identical code minutes earlier. With a warm remote cache, a pull-request pipeline only truly executes the tasks whose inputs the diff actually changed. (For the tool-neutral fundamentals - what a remote cache is, how it differs from a CI cache like actions/cache - see the complete guide to remote build caching.)
How turbo decides what is cached
Every cache lookup starts with a hash, and understanding what feeds it is the difference between a 90% hit rate and a cache that never hits. For each task, turbo computes a key from:
- The package's source files - by default everything in the package, narrowed by the task's
inputsglobs inturbo.jsonif you declare them. - The hashes of upstream tasks - a task that
dependsOn: ["^build"]folds its dependencies' hashes into its own, so a change deep in the graph correctly invalidates everything downstream. - Environment variables you declare - the task-level
envand root-levelglobalEnvlists inturbo.json. Only declared variables participate; an undeclared variable changing does not (and cannot) invalidate the cache, which is why declaring the right ones matters. - Global dependencies - files listed in
globalDependencies(shared tsconfigs,.envfiles, root configs) that should invalidate every task when they change. - The lockfile and the task definition itself - the resolved external dependencies of the package and the command turbo will run. Bump a dependency or edit the pipeline and the key changes.
Because the key is a content hash of the inputs, there is no invalidation logic to get wrong: a changed input produces a different key, and stale entries are simply never looked up again. What turbo stores under the key is the task's declared outputs (plus the captured terminal output), packed into a compressed tar. One important asymmetry: turbo only caches successes - a task that exits non-zero is never stored, so the cache can never replay a failure.
Setting up remote caching
The protocol
Turborepo's remote cache speaks an open HTTP API - the Vercel Remote Cache protocol - documented in an OpenAPI spec. The CLI issues GET / PUT / HEAD requests to /v8/artifacts/:hash with a Bearer token, a POST existence query to check several hashes at once, and a POST /v8/artifacts/events analytics call. Metadata rides on x-artifact-* headers: x-artifact-duration carries the task's real execution time, x-artifact-tag carries the signature when signing is enabled. Any server implementing this surface works with an unmodified turbo binary - which is exactly what makes self-hosted and managed alternatives possible.
The Vercel-hosted default
If you deploy on Vercel, the hosted Remote Cache is the zero-setup path and there is no reason to overthink it:
npx turbo login # authenticate with your Vercel account
npx turbo link # link the repo to a Vercel team scopeFrom then on, local runs and Vercel builds share the team's cache automatically. It is free on all Vercel plans (subject to fair-use guidelines).
Any conforming server: the three environment variables
To point turbo at a different server - self-hosted or managed - skip login/link entirely and set three environment variables (or the equivalent --api, --token, and --team flags):
TURBO_API=<cache server URL>
TURBO_TOKEN=<access token>
TURBO_TEAM=<team slug>TURBO_API is the server base URL, TURBO_TOKEN is the Bearer token every request carries, and TURBO_TEAM is a slug the CLI requires but most standalone servers ignore - they resolve the workspace from the token, not the team name. With Cachely the configuration is:
TURBO_API=https://remote.cachely.dev
TURBO_TOKEN=<token from the Cachely dashboard>
TURBO_TEAM=<any non-empty value>The token identifies your workspace, so the team value genuinely does not matter. The docs walk through creating a workspace and generating the token; the same token also serves the Nx cache API, so one workspace covers both tools.
Artifact signing: verifying what you download
A remote cache asks builds to trust downloaded artifacts, and turbo ships a mechanism to make that trust cryptographic rather than assumed. Enable it in turbo.json and give every machine a shared secret:
// turbo.json
{
"remoteCache": { "signature": true }
}
# every machine that uploads or downloads
TURBO_REMOTE_CACHE_SIGNATURE_KEY=<your secret>On upload, turbo computes an HMAC-SHA256 tag over the artifact with that key and sends it in the x-artifact-tag header. On download, it recomputes the HMAC and rejects any artifact whose tag does not verify. Because the key never leaves your machines, this closes a specific gap: even a compromised or malicious cache server cannot hand your builds an artifact it forged - it does not hold the key. The server's only job is to store the tag and echo it back faithfully; Cachely persists the signature headers with each artifact and returns them on every hit, so signature: true configs verify unchanged.
Signing is cheap insurance for any shared cache, and close to mandatory if you point multiple repos or partially trusted pipelines at one server.
Your options: Vercel-hosted, self-hosted, managed
Because the protocol is open, you have three realistic ways to run a Turborepo remote cache, each right for someone:
- Vercel Remote Cache. Free on all Vercel plans, zero setup (
turbo login+turbo link), integrated with Vercel builds. If your team already lives on Vercel and you have no special token-scoping or data-locality needs, it is a fine default - be suspicious of anyone who tells you otherwise. Its limits are the flip side of its convenience: cache access is tied to Vercel accounts and team membership, tokens are user-scoped rather than pipeline-scoped, and there is no per-project hit-rate or savings reporting. - Self-hosted / DIY. Several open-source servers implement the protocol (the community
turborepo-remote-cacheproject is the best known) with storage backends like S3, GCS, or the local filesystem. You get full control and your data never leaves your infrastructure - and you own everything that entails: hosting, scaling, TLS, token issuance and rotation, immutability enforcement, monitoring, and upgrades. A shared static token in a config file is the common failure mode; treat it with the same care as a deploy credential. - Managed standalone. A service that implements the protocol and operates the storage, auth, and safety properties for you, with no Vercel account in the loop. Cachely is in this category: read-only tokens for untrusted builds enforced at the API, immutable content-addressed artifacts, per-project hit-rate and time-saved insights, and one cache shared by Turborepo and Nx under a single workspace token.
The Cachely vs Vercel Remote Cache page lays the first and third options side by side, and the comparison hub covers the wider field.
Security: tokens, immutability, and cache poisoning
A remote cache is a supply-chain component: whatever it serves gets unpacked, linked, and shipped by trusted builds. The attack class that matters is cache poisoning - an attacker with write access plants a malicious artifact under a hash that a trusted build will later look up. The widely discussed advisory here, CVE-2025-36852 (CREEP), was filed against Nx cache setups, but the attack is tool-agnostic: any cache where untrusted builds (fork pull requests are the classic case) share a write credential with trusted builds has the same exposure, Turborepo included.
Three properties close it, in order of importance:
- Read-only tokens for untrusted builds. PR and fork pipelines get a token that can read (so they stay fast) but never write - enforced by the server, not by convention. The Vercel-hosted cache scopes tokens to users, not pipelines; with Cachely read-only is a token attribute checked at the API.
- Immutable, content-addressed artifacts. Once a hash exists, it cannot be silently replaced by a later write, so a known-good artifact stays good.
- Artifact signing (above) as the client-side backstop: even a write that slips through is rejected on download without a valid HMAC.
The trust model Cachely enforces - and the reasoning behind it - is documented on the security page. One property worth repeating for any option you choose: a remote cache never needs your source code. It stores task outputs and hashes; there is no repository to connect.
CI setup and hit-rate tuning
GitHub Actions example
Remote caching in CI is just the same three variables in the job environment - no cache actions, no restore keys:
jobs:
build:
runs-on: ubuntu-latest
env:
TURBO_API: https://remote.cachely.dev
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: my-team
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with: { node-version: 22, cache: npm }
- run: npm ci
- run: npx turbo run build lint testUse a read-only token as TURBO_TOKEN for pull-request workflows and a read-write token only on trusted branches. The same pattern works on any CI provider - it is only environment variables.
Getting the hit rate up
Most disappointing hit rates trace back to over-keying - something volatile leaking into the hash. The usual suspects:
- Env var hygiene. Declare in
env/globalEnvexactly the variables that change your outputs - no more, no fewer. Declaring something that differs per machine or per run (a runner ID, a timestamp-ish value) busts the cache on every build; failing to declare one your build actually reads causes stale hits instead. turbo's strict env mode helps surface undeclared reads. - Prune
inputs. By default every file in the package feeds the hash - including READMEs and test fixtures that abuildtask never reads. Narrowinginputs(for examplesrc/**plus the relevant configs) stops documentation edits from rebuilding the world. - Keep
globalDependencieshonest. Everything listed there invalidates every task. Root configs that genuinely affect all outputs belong; a frequently touched file that does not belongs anywhere else. - Watch a run with
--summarize. The run summary shows each task's hash, its cache status (local hit, remote hit, miss), and the inputs that fed it - the fastest way to answer "why did this miss?".
The same measurement advice from the general remote cache guide applies: track hit rate and time saved, because a cache nobody measures quietly decays as inputs drift. Cachely's insights report both per project, from the real x-artifact-duration timings turbo uploads.
Clearing, forcing, and scoping the turbo cache
There is no turbo cache clean command. Cache control in Turborepo happens through flags on turbo run and through the cache directory itself, and the flag most guides still teach has been deprecated.
Forcing a re-run instead of clearing
The usual reason to reach for "clear the cache" is to prove a task really executes. --force does that directly: it ignores existing artifacts, re-executes every task in the run, and overwrites the entries it replaces.
npx turbo run build --force # ignore hits, re-execute, overwrite entries
TURBO_FORCE=true npx turbo run build # same thing via env, handy in CIBecause --force writes as it goes, it repairs a bad entry rather than just skipping it - usually what you actually wanted.
Use --cache, not --no-cache or --remote-only
--no-cache and --remote-only are deprecated. Both are replaced by a single --cache flag that takes explicit read/write permissions per source, defaulting to local:rw,remote:rw. Omitting a source disables both reading and writing for it:
npx turbo run build --cache=local:rw # local only; remote off entirely
npx turbo run build --cache=remote:rw # remote only (the old --remote-only)
npx turbo run build --cache=local:r,remote:r # read from both, write to neither
npx turbo run build --cache= # no caching at all (the old --no-cache)The read/write split is the part worth internalising: remote:r is how you give pull-request builds cache hits without letting untrusted code write artifacts that a protected branch might later reuse. That is a security property, not a performance tweak, and it pairs with a read-only token as described above.
Where the local cache lives
Local artifacts land in .turbo/cache inside the repository, so deleting that directory is the closest thing to a cache clean:
rm -rf .turbo/cache # empty the local cache
npx turbo run build --cache-dir=/tmp/turbo # or point it elsewhere
TURBO_CACHE_DIR=/tmp/turbo npx turbo run build # same, via envDeleting .turbo/cache does not touch the remote cache, so the very next run can still restore the same outputs over the network. If you need to prove a task executes locally, use --force or --cache= rather than deleting directories and drawing conclusions from a remote hit.
Clearing and cleaning the turbo cache
Searches for clear turbo cache, turbo clean cache, and turborepo clear cache are all looking for a command that does not exist: there is no turbo clean or turbo cache clean. Clearing the cache means the two mechanisms above - rm -rf .turbo/cache to empty the local cache, and --force (or TURBO_FORCE=true) to ignore existing artifacts, re-execute, and overwrite the stale entries in place.
For nearly every real motivation - a suspected bad entry, proving a task runs - --force is the better tool, because deleting .turbo/cache leaves the remote cache untouched and the next run simply restores the same outputs, which is why a "cleared" turbo cache can still replay instantly.
Related guides
- What is a remote cache?The pillar guide to content-hashed keys, local vs remote caching, and safety.
- Nx vs Turborepo cachingProtocol setup, security, and performance tradeoffs for choosing between them.
- Cachely vs Vercel Remote CacheTurborepo remote caching without a Vercel account, plus read-only tokens.
- Improve cache hit rateDiagnose over-keying, env drift, unstable outputs, and missing outputs.