The complete guide

Turborepo remote cache: the complete guide to remote caching with turbo

Turbo remote caching gives you one of the easiest remote cache setups of any build tool: three environment variables and every machine on your team - laptops, CI runners, ephemeral containers - shares one Turborepo remote cache. This guide explains how turbo hashes tasks, how the Remote Caching protocol works, how to run it with or without Vercel (self-hosted or managed), how artifact signing protects cache trust, and how to tune your hit rate in CI.

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 inputs globs in turbo.json if 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 env and root-level globalEnv lists in turbo.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, .env files, 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 scope

From 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-cache project 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 test

Use 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 / globalEnv exactly 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 a build task never reads. Narrowing inputs (for example src/** plus the relevant configs) stops documentation edits from rebuilding the world.
  • Keep globalDependencies honest. 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 CI

Because --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 env

Deleting .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

Try a Turborepo remote cache on your monorepo
Free for developers. Three environment variables and your next build starts sharing cache.
Start freeSee pricing
FAQ

Turborepo remote cache: frequently asked questions

Do I need a Vercel account to use Turborepo remote caching?
No. Turborepo speaks an open remote cache API (the Vercel Remote Cache protocol), and any server that implements it works. Point TURBO_API at the server, set TURBO_TOKEN to its access token, and set TURBO_TEAM to any non-empty value. Vercel hosts the default implementation, but the CLI does not require it.
Is Turborepo remote caching free?
Vercel's hosted Remote Cache is free on all Vercel plans, subject to fair-use guidelines - a fine default if you already deploy on Vercel. Standalone options vary: open-source servers are free software but you pay for hosting and upkeep; managed services like Cachely have a free developer tier and flat paid plans.
How does TURBO_TEAM work with a self-hosted or standalone cache server?
It is usually just a slug the CLI insists on. With the Vercel-hosted cache it selects which Vercel team scope to use; standalone servers typically identify the workspace from the token instead and ignore the team value. Cachely works this way: TURBO_TEAM can be any non-empty string because the token already resolves to exactly one workspace.
What happens on a Turborepo cache miss?
turbo simply runs the task locally, exactly as it would with caching disabled, then uploads the fresh outputs so the next machine gets a hit. A miss never fails or blocks a build - remote caching is an optimization, not a dependency.
Does Turborepo cache failed tasks?
No. turbo only caches successful task executions. A task that exits non-zero is never stored, so a remote cache can never replay a failure - the worst case of a miss or an unreachable cache is a normal local run.
How do signed Turborepo artifacts work?
Set signature: true in the remoteCache section of turbo.json and export TURBO_REMOTE_CACHE_SIGNATURE_KEY. turbo computes an HMAC-SHA256 tag over each artifact with that key on upload and verifies it on download, rejecting anything that does not match. The key never leaves your machines, so even the cache operator cannot forge a valid artifact.
Can Nx and Turborepo share one remote cache?
With Cachely, yes. The same workspace token authenticates the Nx self-hosted cache API and the Turborepo protocol, and Turborepo artifacts are namespaced separately from Nx artifacts so keys can never collide. One subscription and one dashboard cover both tools.
How do I protect a Turborepo remote cache from cache poisoning?
Give untrusted builds - fork and pull-request pipelines - a token that can read but never write, enforced by the server, and prefer a cache with immutable content-addressed artifacts so an existing key can never be overwritten. Artifact signing adds a second, client-side layer: a poisoned artifact without a valid HMAC is rejected before it is unpacked.
How do I clear the Turborepo cache?
There is no turbo cache clean command. Local artifacts live in .turbo/cache inside the repository, so deleting that directory empties the local cache; --cache-dir or TURBO_CACHE_DIR moves it elsewhere. Deleting it does not touch the remote cache, so the next run can still restore the same outputs over the network. If your goal is to prove a task really executes, use turbo run --force, which ignores existing artifacts, re-executes, and overwrites the entries it replaces.
How do I disable Turborepo caching for one run?
Use the --cache flag. --no-cache and --remote-only are deprecated in favour of explicit read/write permissions per source, defaulting to local:rw,remote:rw. Pass --cache=local:rw for local only, --cache=remote:rw for remote only, --cache=local:r,remote:r to read from both and write to neither, or an empty --cache= for no caching at all. Omitting a source disables both reading and writing for it.