How to Detect Envoy Proxy on Any Website (API Guide)
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:
- As an edge proxy. Envoy terminates public traffic at the front door, handling TLS, routing, rate limiting, and retries before requests reach anything internal. This is the deployment you can detect from outside, because the responses it generates carry its headers all the way back to the client.
- As a service mesh sidecar. Envoy is the data plane underneath Istio, Consul, and several managed mesh offerings, running as a sidecar next to each service. That traffic is internal, so an external scan cannot see it directly. What you sometimes see instead is a latency header leaking out through the edge, which is covered below.
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.
| Signal | Example Value | What 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:
confidenceis 100 for Envoy, because header matches are exact string comparisons rather than probabilistic inference. Lower-confidence entries elsewhere in the same scan come from the DNS layer at 80 and the TLS layer at 70.sourcereads"http", confirming a header-layer match. If you ever see Envoy from another source, something has changed in the detection pipeline.meta.scan_depthis the field that protects your data quality."full"means the HTTP fetch succeeded and header detection actually ran."partial"means only the DNS and TLS layers completed, so an absent Envoy entry tells you nothing at all. Route partial scans to a retry queue, never to a rejects file.
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 Result | Likely Setup | How 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:
- CDN header rewriting. The largest source of false negatives, covered above. Always record whether a CDN was detected alongside the negative result.
- A customized Server banner with the timing header disabled. Both signals can be suppressed by configuration. A company can run Envoy and be genuinely undetectable from outside.
- Service mesh sidecars are invisible. If Envoy runs only as an internal sidecar behind a different edge, no external scan will see it. Absence of Envoy at the edge says nothing about the mesh.
- Partial scans. When
meta.scan_depthis"partial", header detection did not run. This is the failure mode most likely to silently corrupt a large list, because the response looks structurally identical to a clean negative.
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
- How to Find Companies Using Nginx — The same header pipeline for the web server that shares Envoy's detection category
- Find Companies Using Varnish Cache — A reverse proxy that does advertise its version, and what that unlocks
- How to Detect the CDN and Hosting Provider of Any Website — Reading the edge layer that masks Envoy origins
- Find Companies Using Fastly — Another managed edge and the CNAME signal that identifies it
- CPE Identifiers Explained for Security Teams — Why a wildcard version component changes what you can automate
- How to Batch Scan 1,000 Websites for Tech Stack Data — Deep dive on /analyze/batch throughput, retries, and a Python scanner
- Track Website Tech Changes via API — Detecting when a company migrates onto or off a proxy
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