If you call file_exists($path) and $path points at an Amazon S3 bucket, you’re not hitting a local disk—you’re making a network request. The single-line check becomes a round-trip to S3 that can add tens of milliseconds to every request.

PHP hides the remote store behind stream wrappers. Functions such as fopen(), file_exists(), is_dir(), unlink() and file_put_contents() route through these wrappers, which translate the calls into S3 API operations. The mapping is straightforward:

  • file_exists()HeadObject
  • is_dir()ListObjects
  • unlink()DeleteObject
  • file_put_contents()PutObject

Each call incurs the same latency as the corresponding S3 API request. When a script checks dozens of files, it creates an N+1-style pattern: one network hop for every single check.

Why it matters now

In a typical development environment the filesystem lives on the same machine, so the check returns almost instantly. In production, where assets sit on S3, the same code creates a performance cliff. The AWS SDK caches some results in memory, making repeated checks in a single PHP process appear fast. Most PHP deployments, however, spawn a fresh process for each web request, clearing the cache each time. The result: a full network round-trip for every distinct path on every request.

A WordPress site once took ten seconds to render its homepage. Database queries were quick, but the page triggered 73 individual S3 calls just to assemble the content. Each call was a cold request, turning a seemingly harmless file_exists() into a noticeable delay.

Mitigation strategies

  • Persist cache across requests – Move the in-process cache to a shared store such as Redis. When one request discovers that a key exists on S3, subsequent requests read the cached result instead of contacting S3 again.
  • Adopt a local-first workflow – Write files to a local disk, then sync them to S3 asynchronously. The main request path only touches the local filesystem; the network cost shifts to a background job.
  • Audit the codebase – Systematically search for file-system functions that may resolve to a remote wrapper. Flag any that appear inside tight loops or request-critical paths.
  • Inspect APM data – If response time spikes while database metrics stay flat, drill into the storage SDK timings. A sudden rise in SDK latency often points to hidden S3 calls.

The key takeaway: a function that looks like a microsecond local check can hide tens of milliseconds of network latency. Treat file_exists() on S3 as an external call, cache its result, and move the heavy lifting off the request path. Otherwise, hidden latency will keep slowing your site, one invisible network request at a time.