How to Detect Envoy Proxy on Any Website (API Guide)

September 12, 2026 · 10 min read

Envoy is an open-source edge and service proxy built for cloud-native applications. Unlike a web server that ships with a distribution and ends up running by default, Envoy arrives with a deployment decision behind it: someone chose an L7 proxy with a dynamic configuration API, usually because they are running services rather than a site. When Envoy shows up in a tech stack scan, it is telling you something specific about how the company builds.

This guide covers the exact header signals that confirm Envoy, why one of them is stricter than people expect, how to check a single domain with curl, how to run the same check through the DetectZeStack API at scale, and the one honest limitation that separates an Envoy detection from a Varnish or Nginx detection: there is no version number.

What Envoy Is and Why It Shows Up in a Tech Stack Scan

Envoy started at Lyft and became a CNCF graduated project. It runs in two broadly different positions, and the distinction matters when you read a scan result:

Either way, an Envoy detection is a strong signal that the company runs a service-oriented architecture on containers or Kubernetes, has a platform or infrastructure team, and is making deliberate choices about traffic management. That is a very different prospect profile from a site behind a general-purpose web server, which is why finding companies using Nginx and finding companies using Envoy select for different buyers even though both technologies land in the same detection category.

The Signals That Reveal Envoy

Envoy detection is pure header analysis. Two signals drive it, and either one on its own is conclusive.

SignalExample ValueWhat It Tells You
Server header, exact match server: envoy Envoy confirmed at the outermost hop
Upstream timing header x-envoy-upstream-service-time: 60 Envoy confirmed; value is upstream latency in ms
Local reply marker x-envoy-local-reply: true Supporting signal: Envoy generated the response itself
Server header, suffixed server: envoy-iad Operator-customized banner; needs a second signal

The server: envoy Response Header

Envoy sets server: envoy by default. The detection fingerprint matches that header value as an anchored exact match, not a substring, which is a detail worth internalizing because it changes how you write your own grep filters. A header of server: envoy matches. A header of server: envoy-iad does not match this rule, because the anchored pattern requires the value to begin and end with envoy.

Here is dropbox.com, which runs the default banner:

$ curl -sI https://www.dropbox.com | grep -i "^server"
server: envoy

Operators override the banner regularly, either by setting server_name in the HTTP connection manager or by stripping it entirely. That is exactly why the second signal exists.

Envoy-Specific Response Headers and Error Bodies

The x-envoy-upstream-service-time header reports how many milliseconds the upstream service took to respond, and Envoy is the only thing that emits it. The fingerprint matches on the header being present at all, with no constraint on the value, which makes it the more robust of the two signals: a company can rename its Server banner in one config line, but the timing header is a routing diagnostic that teams generally leave on.

lyft.com is the instructive case, because it fails the first check and passes the second:

$ curl -sI https://www.lyft.com | grep -iE "^(server|x-envoy|via)"
x-envoy-upstream-service-time: 60
server: envoy-iad
via: 1.1 4ab6332ad5f85c451b620ed19dfdcccc.cloudfront.net (CloudFront)

The server: envoy-iad banner is customized and would slip past an anchored match on its own. The timing header carries the detection. Note the third line too: there is an Amazon CloudFront edge in front, and it passed the origin headers through rather than rewriting them. That is not guaranteed behavior, which brings us to the main limitation.

One more header appears in Envoy deployments: x-envoy-local-reply: true marks a response that Envoy generated itself rather than proxying upstream, typically a redirect, a rate-limit rejection, or an error. It is a genuine Envoy fingerprint in practice, but it is not one of the two patterns the detection matches on, so treat it as corroboration when you are reading headers by hand.

Why Envoy Often Hides Behind a CDN Edge

Every external scanner sees the outermost hop and nothing beyond it. When a CDN sits in front of an Envoy tier, whether the origin's headers survive depends entirely on the edge's pass-through behavior. Some edges forward origin headers largely intact, as CloudFront did for lyft.com above. Others replace the Server header with their own value, and the origin becomes invisible.

Probing five well-known domains shows how common the masking case is:

$ for d in coinbase.com gitlab.com medium.com salesforce.com asana.com; do
    printf "%-18s " "$d"
    curl -sI --max-time 10 "https://$d" | grep -iE "^server:" | tr -d '\r'
  done
coinbase.com       server: cloudflare
gitlab.com         server: cloudflare
medium.com         server: cloudflare
salesforce.com     Server: AkamaiGHost
asana.com          server: Netlify

None of those five tells you anything about the origin proxy. The operational rule that follows is worth writing down: a negative Envoy result on a domain that shows a CDN is an unknown, not a confirmed miss. Keep those domains in a separate column. For reading the edge layer itself, see our guide to detecting the CDN and hosting provider of any website.

Manual Detection with curl and Browser DevTools

For a single domain, one command covers both signals at once:

$ curl -sI https://www.dropbox.com | grep -iE "^(server|x-envoy)"
server: envoy

A few things to watch for when you do this by hand. Use -I for a HEAD request, but fall back to curl -sD - -o /dev/null if a site returns different headers for HEAD than for GET, which some edges do. Follow redirects with -L, because the apex domain and the www host frequently terminate on different infrastructure. And check the headers on a real content path, not just the homepage, since API subdomains are often the ones fronted by Envoy while the marketing site sits on something else entirely.

In browser DevTools, open the Network tab, reload, click the document request, and read the Response Headers panel. The same two headers appear there. DevTools is convenient for one-off inspection and useless past about the third domain, which is the entire reason the API exists.

Detect Envoy with the DetectZeStack API

The API runs the full detection pass in a single call, covering HTTP headers and body fingerprints, DNS records, and TLS certificates, and returns structured JSON you can filter in a script. Envoy comes back as a named technology in the Reverse proxies category.

Single Domain Scan with /analyze

You can test right now against the public demo endpoint with no API key. It is IP-rate-limited, so use it for spot checks rather than bulk scans. This is a real, unedited response:

$ curl -s "https://detectzestack.com/demo?url=dropbox.com" \
  | jq '.technologies[] | select(.name == "Envoy")'
{
  "name": "Envoy",
  "categories": ["Reverse proxies"],
  "confidence": 100,
  "description": "Envoy is an open-source edge and service proxy, designed for cloud-native applications.",
  "website": "https://www.envoyproxy.io/",
  "icon": "Envoy.png",
  "cpe": "cpe:2.3:a:envoyproxy:envoy:*:*:*:*:*:*:*:*",
  "source": "http"
}

The authenticated /analyze endpoint returns the same shape for the whole stack. Swapping in your key gives you the full document, where meta carries the scan bookkeeping and cached and response_ms sit at the top level:

$ curl -s "https://detectzestack.p.rapidapi.com/analyze?url=dropbox.com" \
  -H "X-RapidAPI-Key: YOUR_KEY" \
  -H "X-RapidAPI-Host: detectzestack.p.rapidapi.com" \
  | jq '{domain, categories, meta, cached, response_ms}'
{
  "domain": "www.dropbox.com",
  "categories": {
    "Miscellaneous": ["HTTP/3"],
    "Payment processors": ["PayPal"],
    "Reverse proxies": ["Envoy"],
    "SSL/TLS certificate authority": ["DigiCert"],
    "Security": ["HSTS"]
  },
  "meta": {
    "status_code": 200,
    "tech_count": 5,
    "scan_depth": "full"
  },
  "cached": false,
  "response_ms": 3971
}

Yes/No Answer with /check?tech=Envoy

When all you need is a boolean for one technology on one domain, /check is the cheapest call. The tech parameter is case-insensitive and the response echoes back the canonical name:

$ curl -s "https://detectzestack.p.rapidapi.com/check?url=dropbox.com&tech=envoy" \
  -H "X-RapidAPI-Key: YOUR_KEY" \
  -H "X-RapidAPI-Host: detectzestack.p.rapidapi.com"
{
  "domain": "www.dropbox.com",
  "technology": "Envoy",
  "detected": true,
  "confidence": 100,
  "version": "",
  "categories": ["Reverse proxies"],
  "response_ms": 3971,
  "cached": false
}

Scanning a List of Domains with /analyze/batch

POST /analyze/batch accepts up to 10 URLs per request and analyzes them concurrently. Each entry in results[] carries either a full analysis under result or an error string for domains that could not be fetched. Here is a complete pipeline in bash, curl, and jq that reads domains.txt and writes every Envoy-confirmed domain to a CSV:

#!/usr/bin/env bash
# find-envoy.sh — filter a domain list down to Envoy-confirmed hosts
KEY="YOUR_KEY"
HOST="detectzestack.p.rapidapi.com"

echo "domain,tech_count,scan_depth" > envoy_hosts.csv

# Process domains.txt in batches of 10 (the /analyze/batch maximum)
xargs -n 10 < domains.txt | while read -r batch; do
  urls=$(printf '%s\n' $batch | jq -R . | jq -s '{urls: .}')
  curl -s -X POST "https://$HOST/analyze/batch" \
    -H "X-RapidAPI-Key: $KEY" \
    -H "X-RapidAPI-Host: $HOST" \
    -H "Content-Type: application/json" \
    -d "$urls" |
  jq -r '.results[]
    | select(.result != null)
    | . as $r
    | $r.result.technologies[]
    | select(.name == "Envoy")
    | [$r.result.domain,
       ($r.result.meta.tech_count | tostring),
       $r.result.meta.scan_depth]
    | @csv' >> envoy_hosts.csv
done

wc -l envoy_hosts.csv

A 1,000-domain list becomes 100 batch calls. The select(.result != null) guard skips domains that failed to resolve or timed out, which appear with an error field instead. For throughput planning, retry queues, and a production-ready Python version of this scanner, see how to batch scan 1,000 websites.

Comparing Two Domains with /compare

POST /compare takes 2 to 10 URLs and returns each domain's technologies alongside a unique list per domain and a shared list across all of them. It is the fastest way to show that two companies in the same market made different proxy decisions:

$ curl -s -X POST "https://detectzestack.p.rapidapi.com/compare" \
  -H "X-RapidAPI-Key: YOUR_KEY" \
  -H "X-RapidAPI-Host: detectzestack.p.rapidapi.com" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["dropbox.com", "stripe.com"]}' \
  | jq '{shared, per_domain: [.domains[] | {domain, unique}]}'

A live scan of those two returns Envoy in the Reverse proxies category for dropbox.com, while stripe.com returns Nginx filed under both Web servers and Reverse proxies. Same category, different architecture: one fronts services with a dedicated L7 proxy, the other fronts them with a general-purpose web server. Both scans also surface HSTS and a DigiCert certificate, which is what the shared array is for.

Reading the Response: Confidence, Category, and CPE

Three fields carry the weight when you read an Envoy result:

Using the Envoy CPE for Vulnerability Lookups

Every Envoy detection carries cpe:2.3:a:envoyproxy:envoy:*:*:*:*:*:*:*:*, the standardized identifier that maps a product to entries in the National Vulnerability Database. Envoy has a real CVE history, including several high-severity HTTP request-handling issues, so the identifier is genuinely useful.

The honest caveat: the version field for Envoy is always empty, because Envoy does not put its version in the Server header the way Nginx and Varnish often do. The CPE's version component stays a wildcard. DetectZeStack's /vulnerability endpoint only queries the NVD for technologies that have both a CPE and a non-empty version, so an Envoy detection will not generate CVE rows automatically. You get the product identifier, not a version-pinned verdict.

That is a limitation worth stating plainly rather than papering over. What the CPE still buys you is a correct, machine-readable product key: feed it to the NVD's own wildcard search to enumerate every Envoy CVE, then correlate against version data you obtain another way, such as an internal CMDB or a vendor questionnaire. For the full mechanics of CPE strings and where version-pinned matching does and does not work, see CPE identifiers explained for security teams.

What Envoy Detection Tells You About a Company

Because the same scan reports the proxy layer and the CDN layer together, the combination says more than either signal alone:

Scan ResultLikely SetupHow to Read It
Envoy, no CDN Self-operated edge proxy In-house platform team; owns its traffic layer end to end
Envoy plus a CDN CDN edge in front of an Envoy tier Layered architecture; the CDN passes origin headers through
Nginx, no Envoy General-purpose web server at the front Site-shaped rather than service-shaped infrastructure
CDN only, no proxy named Origin masked by the edge Unknown; Envoy may well be running behind it

These are heuristics, not rules, and they compound with the rest of the stack in the same response. Envoy next to a Kubernetes-shaped hosting footprint reads as a platform team; Envoy on a single marketing domain with nothing else modern in the stack more likely reflects a vendor's infrastructure than the company's own. If you are tracking whether a company migrates onto or off Envoy over time, tracking website tech changes via API covers the snapshot-and-diff approach.

Limitations and False Negatives

Four things will produce a wrong answer if you are not watching for them:

Conclusion and Next Steps

Detecting Envoy comes down to two headers: an exact server: envoy banner, and the presence of x-envoy-upstream-service-time. The second is the more durable of the two, as lyft.com's customized server: envoy-iad demonstrates. One /check call answers the single-domain question, /analyze/batch turns a raw domain list into an Envoy-confirmed list at 10 domains per call, and /compare puts two architectures side by side. The CPE identifier ships with every detection, with the honest asterisk that no version means no automated CVE matching.

The same pipeline generalizes to any technology in the detection database. Swap the jq filter and you are segmenting by CDN, CMS, or payment stack instead of proxy layer.

Related Reading

Try DetectZeStack Free

100 requests per month, no credit card required. Header, DNS, and TLS detection included on every plan.

Get Your Free API Key

Get API updates and tech detection tips

Join the mailing list. No spam, unsubscribe anytime.