Reference
Why a redirect on /.well-known/ucp breaks discovery
One sentence in the specification rules out the workaround most platforms leave you. It is also the hardest failure to spot, because the profile looks perfect in a browser: your browser follows the redirect, and an agent is required not to.
The rule, in the specification's own words
“Published artifacts MUST be served over HTTPS. Profile endpoints MUST NOT use redirects (3xx).”
And the obligation on the other side, so it is clear this is not advisory on either end:
“Implementations MUST reject URLs not served over HTTPS. Implementations MUST NOT follow redirects (3xx).”
There is no status code that is acceptable here: not 301, not 302, not 307, not 308. The agent does not evaluate the destination, because it never requests it. It sees a 3xx, treats discovery as failed, and moves on.
Why the rule exists
A profile is an identity statement bound to an origin. It says “this domain speaks this protocol version and exposes these capabilities,” and in a signed deployment it also carries the public keys used to verify your messages. If an agent followed redirects, then whoever controlled a redirect rule on your domain — a CDN, a marketing tool, a misconfigured plugin, a former agency — could point your identity somewhere else, and the agent would accept it as coming from you.
The specification applies the same reasoning to every URL it dereferences during discovery, not only the profile. A platform must check that each schema URL's origin matches the namespace authority of the entity that declared it, and must not follow redirects when fetching it:
“A spec URL MUST be a validhttpsURL. A platform MUST NOT follow redirects (3xx) when fetching a schema URL, consistent with profile fetches.”
Read together, the two rules say that provenance comes from the origin that answered, not from a chain of hops. That is why the fix is never “redirect to the right place” and always “answer in place.”
The six redirects that catch stores out
| Cause | What happens |
|---|---|
| Apex to www, or www to apex | A site-wide canonical-host redirect applies to /.well-known/ like any other path. Whichever host the agent tries is the one that has to answer. |
| The vendor hand-off | A hosted UCP service that tells you to point the path at their domain with a 301. This is the single most common cause, and it cannot be made to work: the hop is the failure. |
| Trailing-slash normalisation | A framework or CDN that redirects /.well-known/ucp to /.well-known/ucp/, or the reverse. One hop is one hop. |
| Locale and geo routing | A rule that sends every request to /en-gb/ or to a regional host. Agents fetch from wherever they run, so this fails intermittently and looks like flakiness rather than a config error. |
| HTTP to HTTPS on the canonical host | Harmless on its own, since an agent requests HTTPS directly. It becomes a problem when the HTTPS host then redirects again, which is the usual Cloudflare-plus-origin redirect loop. |
| Login or consent walls | A staging password, a cookie banner or a geo-consent gate that answers with a 302 to a consent page. The path must be public and unauthenticated. |
Checking it in one command
# A conformant profile answers at the URL you asked for.
curl -sS -o /dev/null -w '%{http_code} %{num_redirects} %{url_effective}\n' \
https://yourstore.com/.well-known/ucp
# 200 0 https://yourstore.com/.well-known/ucp <- conformant
# 200 1 https://www.yourstore.com/.well-known/ucp <- discovery fails:
# curl followed a hop,
# an agent will not.Then check both hosts. An agent that learned about you from a product page on the apex domain will fetch the apex domain, and a store that only serves the profile on www is undiscoverable from half its own links.
# Both hosts have to answer directly. Check the one you do not think of
# as canonical, because an agent may only ever know that one.
for host in yourstore.com www.yourstore.com; do
printf '%s -> ' "$host"
curl -sS -o /dev/null -w '%{http_code} after %{num_redirects} hop(s)\n' \
"https://$host/.well-known/ucp"
doneRedirect, rewrite, proxy: the distinction that matters
A redirect tells the client to ask somewhere else, and the client is the one that makes the second request. A rewrite or a reverse proxy makes that second request on your server and returns the body under your own origin. To the agent, a rewrite is indistinguishable from a file sitting at the path: one request, one 200, no Location header. That is the whole trick.
# WRONG: the hop is the failure, even though the JSON at the far end
# is perfect and a browser shows it.
location = /.well-known/ucp {
return 301 https://ucp-vendor.example/profiles/yourstore.json;
}
# WRONG: the site-wide apex-to-www rule catches the path too.
server {
server_name yourstore.com;
return 301 https://www.yourstore.com$request_uri;
}# RIGHT: answer at the path. proxy_pass fetches upstream and returns the
# body under your own origin, so the agent sees one 200 and no Location.
location = /.well-known/ucp {
proxy_pass https://ucp-vendor.example/profiles/yourstore.json;
proxy_set_header Host ucp-vendor.example;
proxy_hide_header Cache-Control;
add_header Cache-Control "public, max-age=300" always;
default_type application/json;
}
# RIGHT: exempt the path before the host-canonicalisation rule runs.
server {
server_name yourstore.com;
location = /.well-known/ucp { try_files /ucp/profile.json =404; }
location / { return 301 https://www.yourstore.com$request_uri; }
}The same shape applies elsewhere: Apache Alias or RewriteRule without the [R] flag, a Cloudflare Worker or Vercel rewrite answering the route, an application route in a headless stack. The reference page for the profile has the configuration for each.
Check it on your own store
The scan follows the profile request without following redirects, and reports the chain it saw if there was one. That is the check this whole page is about, and it takes a few seconds on your own domain.
Run the free scanNo account, no email. It reports what it measured and says so when it could not measure something.