Skip to content
Control Plane Labs

203 Non-Authoritative Information

Success response defined by RFC 9110 §15.3.4.

Last updated July 27, 2026

Common causes at a glance

  • A carrier or ISP proxy recompressing images on a metered mobile link
  • A corporate content filter or malware scanner rewriting response bodies
  • An enterprise TLS-inspecting middlebox injecting or stripping markup
  • An application incorrectly reusing 203 to mean data came from a cache or third party

Reproduce it

curl -i -x http://transforming-proxy.internal:3128 http://www.example.com/photo.jpg

What 203 Non-Authoritative Information means

203 is a disclaimer attached to an ordinary success. The origin returned 200; an intermediary then recompressed the image, stripped a tracking script, rewrote the HTML, or ran the payload through a malware filter, and changed the status to warn you the bytes are no longer authoritative.

That warning matters. If the content was signed, the signature no longer verifies. If you cache the response, it is only valid for clients reached through the same proxy chain, because a different path would produce different bytes. The status is the only signal; nothing else says the content was touched.

What the spec says

RFC 9110 §15.3.4 defines 203 as indicating the enclosed content was modified from the origin’s 200 response by a transforming proxy, and notes the consequence: future cache validation requests may only be applicable along the same request path. The transformation rules are in §7.7, which says a proxy that transforms content MAY inform downstream recipients by changing the status to 203, and MUST NOT transform at all when the response carries the no-transform directive (RFC 9111 §5.2.2.6). Like 200, a 203 is heuristically cacheable (§4.2.2).

Where you actually see it

On the open internet 203 is rare, and where it turns up is telling. Mobile carriers running transcoding proxies over cleartext HTTP historically downgraded image quality and occasionally announced it with 203. Corporate networks are the other habitat: TLS-inspecting appliances and content filters sometimes set 203 honestly, though most leave you with a silently altered 200. The third sighting is misuse. Developers read “Non-Authoritative Information” without reading the section and use 203 to mean “this came from cache” or “this came from a vendor”. No client or cache treats it that way. If a bug only reproduces on one corporate network and the payload looks subtly wrong, 203 is worth checking for.

How to debug it

You cannot see a transformation the proxy hides, so compare paths. Fetch the same URL through the suspect network and directly, and diff the bytes:

curl -s https://www.example.com/app.js | sha256sum
curl -s -x http://proxy.corp.internal:3128 https://www.example.com/app.js | sha256sum

Differing digests with matching ETag values proves an intermediary is rewriting content. Check Via and vendor-specific response headers for the appliance name. To stop the transformations, send Cache-Control: no-transform from the origin; a compliant proxy must then leave the body alone. HTTPS end to end without a corporate trust anchor removes the opportunity entirely, which is why 203 has largely disappeared from the public web. The header inspector shows what a request picks up in transit.

Working examples

curl

# Compare the origin response against the same URL through a proxy.
curl -sI https://www.example.com/photo.jpg | head -1
curl -sI -x http://proxy.corp.internal:3128 https://www.example.com/photo.jpg | head -1

# A 203 plus a Via header names the intermediary that rewrote the body:
curl -sD - -o /dev/null -x http://proxy.corp.internal:3128 \
  http://www.example.com/photo.jpg | grep -iE '^(HTTP|via|warning)'

Python (requests)

import hashlib
import requests

url = "https://www.example.com/app.js"
proxies = {"https": "http://proxy.corp.internal:3128"}

direct = requests.get(url, timeout=10)
viaproxy = requests.get(url, proxies=proxies, timeout=10)

print(direct.status_code, viaproxy.status_code)  # 200 vs possibly 203
for label, r in (("direct", direct), ("proxy", viaproxy)):
    print(label, hashlib.sha256(r.content).hexdigest()[:16], r.headers.get("Via"))

if viaproxy.status_code == 203:
    print("content was transformed in transit; signatures will not verify")

Node (fetch)

// fetch() reports 203 as res.ok === true, so check the code explicitly.
const res = await fetch("http://www.example.com/photo.jpg");

console.log(res.status, res.ok); // 203 true

if (res.status === 203) {
  console.warn("transformed by", res.headers.get("via") ?? "an unnamed proxy");
  console.warn("do not verify signatures or trust byte-for-byte equality here");
}

const buf = new Uint8Array(await res.arrayBuffer());
console.log("bytes:", buf.length, "declared:", res.headers.get("content-length"));

Reference and tooling

The authoritative list of assigned status codes is theIANA HTTP Status Code Registry. For a searchable copy with the semantics summarised, use theHTTP status code reference on this site. To see exactly what a server is sending, theheader inspector shows the raw status line and response headers, and thecurl builder assembles the request flags for you.

Other 2xx codes