ScreenshotNeo

BlogEngineering

HTTP 417 Expectation Failed: What It Means

HTTP 417 means a server or intermediary rejected an expectation in your request. Learn why it happens and how to fix Expect: 100-continue safely.

By the ScreenshotNeo team29 September 20269 min read

HTTP 417 Expectation Failed: What It Means

HTTP 417 Expectation Failed is a client-error response. It means that a server or intermediary could not meet an expectation stated in the request’s Expect header. The URL may exist and the application may be healthy; the failure can happen at any inbound hop before the request reaches the origin.

The standardized expectation is Expect: 100-continue. A client sends headers first, waits for 100 Continue, and only then transmits a potentially large request body. If a server, reverse proxy, load balancer, gateway, or older HTTP/1.0 hop cannot support that behavior, it can return 417. The usual client-side fix is to retry without the expectation, provided the request can be sent safely.

What HTTP 417 means

RFC 9110 defines 417 as indicating that “the expectation given in the request’s Expect header field … could not be met by at least one of the inbound servers.” See the RFC 9110 definition. The status belongs to the 4xx class, so the request needs to change before retrying.

Question Answer
Is the URL necessarily wrong? No. 417 identifies a rejected request expectation, not a missing resource.
Is the origin necessarily down? No. An intermediary may generate the response before the origin sees the request.
Which header matters? Expect, commonly with the value 100-continue.
What is the first fix? Inspect the outgoing request and retry without Expect: 100-continue when safe.

How the Expect handshake works

The Expect header lets a client state a behavior it needs from the server. RFC 9110 currently defines only 100-continue as a standard expectation. Other values are unsupported unless a server has an extension that explicitly understands them; an unsupported value can result in 417.

A 417 can be generated by an intermediary before the origin receives the request.
A 417 can be generated by an intermediary before the origin receives the request.

The 100-continue sequence

  1. The client opens a connection and sends request headers, including Expect: 100-continue and a body length.
  2. The server or an intermediary decides whether it can accept the request and its body.
  3. If acceptable, it sends an interim 100 Continue response.
  4. The client sends the request body.
  5. The server returns the final response, such as 201, 400, or 500.

This saves bandwidth when a body is large and the server might reject the request based on headers alone. It also introduces a negotiation step that every HTTP hop must handle correctly. MDN notes that common browsers generally do not send this header, while some command-line clients and libraries may add it automatically. Read the MDN Expect reference.

POST /upload HTTP/1.1
Host: example.test
Content-Length: 104857600
Content-Type: application/octet-stream
Expect: 100-continue

...100 MB body after a 100 Continue response...

Why a 417 response happens

1. An unsupported Expect value

A request such as Expect: handshake-v2 asks for behavior that is not defined by HTTP and may not be implemented by the receiving server. Remove the header or use the server’s documented extension.

2. A server rejects 100-continue

Some application servers accept ordinary POST requests but do not implement the interim response correctly. They may return 417 immediately, close the connection, or pass an inconsistent response through a proxy.

3. A reverse proxy or gateway rejects it

NGINX, an API gateway, a cloud load balancer, a WAF, or a vendor edge can inspect and reject Expect before forwarding the request. The application logs then show no request, which can make the problem look mysterious.

4. An HTTP/1.0 hop is in the chain

RFC 9110 gives an HTTP/1.0 intermediary as an example of a hop that may not support expectations. Protocol conversion can therefore cause a 417 even when the client and origin each support HTTP/1.1.

5. A client library adds the header by default

Command-line tools and SDKs sometimes enable 100-continue for requests with a body. The header might not appear in your application code, so inspect the wire request rather than assuming the source code is complete.

Diagnose 417 step by step

  1. Capture the complete request. Record the method, URL, headers, protocol version, body size, and response headers. Use a verbose client mode, a proxy trace, or a packet capture in a safe environment.
  2. Search specifically for Expect. Check casing-insensitively for Expect and record its exact value. Also check redirects and retries: a later attempt may be generated by a different library.
  3. Identify the responder. Look at Server, gateway request IDs, tracing headers, and edge logs. Compare timestamps across the client, proxy, load balancer, and origin.
  4. Replay without the expectation. Remove the header and repeat the request with the same authentication and content headers. If it succeeds, the handshake is the cause.
  5. Test each hop. Send the request directly to the origin from a controlled network, then through the normal gateway. A difference isolates the rejecting intermediary.
  6. Check retry safety. For POST, determine whether the operation is idempotent or protected by an idempotency key before automatically resending a body.

Inspecting with cURL

# Show request and response headers, including Expect behavior
curl --verbose --http1.1 \
  -H 'Expect: 100-continue' \
  -H 'Content-Type: application/json' \
  --data-binary @payload.json \
  https://api.example.test/items

# Retry without the expectation
curl --verbose --http1.1 \
  -H 'Expect:' \
  -H 'Content-Type: application/json' \
  --data-binary @payload.json \
  https://api.example.test/items

In cURL, an empty Expect: header removes the generated value. Compare the two verbose traces and note whether a proxy, rather than the origin, sends 417.

Python requests

import requests

url = "https://api.example.test/items"
with open("payload.json", "rb") as body:
    response = requests.post(
        url,
        data=body,
        headers={
            "Content-Type": "application/json",
            "Expect": "",  # prevent 100-continue negotiation
        },
        timeout=(10, 90),
    )

print(response.status_code)
print(response.headers)
print(response.text[:1000])

Use a fresh file stream for each retry. A consumed stream can produce an empty or truncated second request.

Node.js fetch

import { readFile } from "node:fs/promises";

const payload = await readFile("payload.json");
const response = await fetch("https://api.example.test/items", {
  method: "POST",
  headers: {
    "content-type": "application/json",
    "expect": "",
  },
  body: payload,
});

console.log(response.status, Object.fromEntries(response.headers));
console.log(await response.text());

Node’s built-in fetch uses an HTTP implementation that can differ from older SDKs. If a lower-level agent or framework adds Expect, configure that component or send the request through a tested HTTP client that exposes continue behavior.

Fixes and configuration choices

Fix Use it when Trade-off
Remove Expect on the request You control the client and the body can be sent immediately The body is transmitted before the server validates headers
Disable automatic 100-continue An SDK inserts the header Requires client-specific configuration
Enable support in the proxy or origin The handshake is valuable for large uploads Requires coordinated deployment and protocol testing
Upgrade or bypass an old intermediary An HTTP/1.0 hop or legacy gateway is identified May require network changes
Retry without the header The response is specifically 417 to 100-continue Only safe when replaying the method is safe

RFC 9110 says a client receiving 417 for a request containing 100-continue should repeat the request without that expectation. Treat that as a protocol recommendation, not permission to duplicate an unsafe transaction. Preserve authorization, idempotency keys, content integrity checks, and application-level retry limits.

Proxy and server checks

  • Confirm the proxy forwards or deliberately strips Expect consistently.
  • Check whether HTTP/2 or HTTP/3 translation changes how the request is buffered.
  • Review maximum body size and buffering settings; a gateway may expose a header problem while enforcing another limit.
  • Compare edge and origin logs using a request ID. If the origin has no entry, keep investigating the inbound chain.
  • Do not “fix” the issue by removing authentication, TLS verification, or request validation.

Edge cases developers often miss

Large, non-repeatable request bodies

A streamed body from a pipe, camera, or generated archive may not be replayable. Buffer it to durable storage, use an idempotency key, or negotiate a resumable upload before retrying. Never assume a library can rewind a stream.

Removing 100-continue eliminates the extra handshake when the request is safe to send directly.
Removing 100-continue eliminates the extra handshake when the request is safe to send directly.

Redirects

A client may send the first request to one host and a redirected request to another. Header forwarding rules differ by method and security boundary. Capture every hop and verify that credentials and the body are handled as intended.

Expect values added by middleware

Framework middleware, upload helpers, and service meshes can mutate headers after your handler runs. Log at the socket or HTTP-client boundary when possible.

417 versus other 4xx responses

If removing Expect changes the response to 413, 401, 403, or 400, the expectation problem is resolved and the new status identifies the next issue. Do not keep retrying 417 blindly; classify responses and cap attempts.

Reliability, performance, and cost considerations

100-continue can reduce wasted bandwidth for very large bodies that are likely to be rejected by authentication, size, or policy checks. For small JSON requests, the extra round trip often adds latency without meaningful savings. Choose per endpoint rather than enabling it globally.

Measure the complete transaction: time to headers, time waiting for 100 Continue, upload duration, final response time, and retry count. Alert on a rise in 417 responses at the edge, where it can reveal a proxy rollout or client-library change.

When retries are needed, use bounded exponential backoff only for transient transport failures; a deterministic 417 will not heal by waiting. Keep request bodies available for the retry, and record whether the first body was sent so your application can reason about duplicate side effects.

Or skip the browser setup

If you are diagnosing URL capture, you can avoid maintaining a browser, consent handling, and screenshot retries with ScreenshotNeo. Its API accepts one GET request and returns PNG, JPEG, WebP, or PDF. The request itself does not require a browser-side Expect handshake:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for the complete parameter list. Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Troubleshooting checklist

  • 417 appears only for uploads: inspect whether the upload client enables 100-continue; retry once with Expect: removed.
  • Direct origin works, public hostname fails: inspect the reverse proxy, WAF, CDN, and protocol conversion.
  • No Expect header in application logs: log before the request leaves the client; middleware may add it later.
  • Retry creates duplicate records: add an idempotency key or stop automatic retries for that operation.
  • Removing Expect reveals 413: address gateway or origin body-size limits; 417 was only the first visible failure.
  • Only one region fails: compare regional load balancers, service-mesh versions, and network paths.
  • Response has no useful server header: use tracing IDs and synchronized timestamps to identify the responding hop.

FAQ

Does HTTP 417 mean the server is offline?

No. A reachable server or intermediary deliberately returned a client-error response. The origin might never have received the request.

Do browsers usually cause 417?

Common browsers generally do not send Expect: 100-continue. The header is more often introduced by command-line tools, SDKs, upload libraries, or infrastructure.

Can I always delete the Expect header?

Usually for ordinary requests, but confirm the server contract and replay safety. Removing it sends the body immediately and may increase wasted bandwidth for rejected large uploads.

Is 417 the same as 100 Continue?

No. 100 is an interim response allowing the body to follow; 417 is the final error response saying the requested expectation could not be met.

Which log proves the cause?

The most useful evidence is the raw outgoing request, the 417 response headers, and correlated logs from every proxy and the origin. Together they show who received and rejected the expectation.

Key points

  • 417 specifically concerns an unmet expectation in the Expect request header.
  • Expect: 100-continue is the standardized case and negotiates transmission of a request body.
  • Any inbound server or intermediary can generate the response.
  • Inspect the wire request, then retry without the expectation when the operation is safe to replay.
  • If the header is injected by infrastructure, fix that component instead of repeatedly retrying the application.