HTTP 417 is Expectation Failed: at least one server on the request’s path couldn’t meet the expectation in its Expect header (RFC 9110 section 15.5.18). The only expectation HTTP defines is 100-continue (section 10.1.1), and a server that receives any other may answer 417.
Where it comes from
- An unknown expectation. Node’s
httpserver answers 417 to anyExpectother than100-continueunless you handle thecheckExpectationevent. Go’s server answers 417 to one too, with no hook to change it. - 100-continue through an old intermediary. An HTTP/1.0 proxy can’t send a 100 response. RFC 9110 says a 417 to a
100-continuemerely shows that the chain doesn’t support expectations, for example because it passes through an HTTP/1.0 server. - .NET Framework’s
HttpWebRequestsendsExpect: 100-continueon POSTs by default, so behind such a proxy it fails withThe remote server returned an error: (417) Expectation Failed.
What the client should do
RFC 9110 says a client that gets a 417 in response to 100-continue should repeat the request without the expectation. Most clients have a switch for it:
curl -H 'Expect:' -T backup.tar https://example.com/uploads/
ServicePointManager.Expect100Continue = false; // .NET Framework, before the first request
request.Headers.ExpectContinue = false; // HttpClient
Dropping Expect costs little: the client just sends the body without waiting for permission, which is what it would do after a timeout anyway.
Sending it
Node answers unknown expectations itself; handle the event when you want a body that says what went wrong:
server.on('checkExpectation', (req, res) => {
res.writeHead(417, { 'Content-Type': 'text/plain' });
res.end('Only "Expect: 100-continue" is supported.\n');
});
Gotchas
- Not a validation error. A body that fails your rules is a 400 or a 422; 417 is only about the
Expectheader. - One retry, not a loop. A client that keeps sending
Expectafter a 417 will keep getting it. - Caching. 417 isn’t heuristically cacheable.
Search 100 417 in the tool below to compare the two.