We use analytics cookies.We use cookies to analyze site traffic and improve your experience.
Loading...
System Design Masterclass · Load Balancing Proxies
Free preview: the first 2 parts of this 9-part lesson.
Already bought the course? Sign in to read the whole lesson.
this lesson is part of
System Design Masterclass
Unlock this lesson and the 751 other paid lessons
$25 once, by PayPal
See the System Design courseread the rest of this lesson in
System Design Masterclass
Unlock this lesson and the 751 other paid lessons
$25 once, by PayPal
See the System Design coursein Load Balancing Proxies
This lesson is part of the full System Design Masterclass. Unlock it along with 769 other lessons for a one-time payment.
One-time payment by card or PayPal
Get Lifetime AccessOne-time payment: UPI, card or netbanking
Get Lifetime Access18 Foundation lessons are free, no payment needed. Try them now
It is easy and good
“It is really nice and good Please join the course”
A path is in the address bar. A hostname is in public . Both are facts you have published, and both get bookmarked, linked and indexed.
A header is none of those things, and that is the entire reason to route on one.

It can express a distinction you do not want to announce, it can change from one request to the next, and it costs no DNS record and no certificate.
That invisibility comes with two consequences, and the rest of this lesson is those two consequences. The first is that a client can invent a header and you cannot tell. The second is that making a response depend on a header makes every cache key in front of you wrong.
Neither is subtle. Both are one request to demonstrate.
Here is what a backend actually receives.

A plain curl invented an internal-sounding header and an X-Forwarded-For of its own choosing, and both arrived at the backend untouched.
Read the third block again. There is nothing special about X-Internal-Role. The X- prefix is a convention, the word "internal" is a word, and none of it is enforced by anything. A client can write that header, and by the time it reaches a backend it is indistinguishable from one your edge wrote.

There is no field marking a header as internal. The backend has no way to tell which hop wrote it.
So the edge has to. And it has to do it in a specific order.

Any other order leaves a case where a value the client wrote reaches a backend that trusts it.
The word doing the work there is "unconditionally". A very common rule reads "set the header if it is not already present", which sounds careful and is the opposite. A client that sets it first now controls its own routing.
There is a second reason to strip rather than to overwrite selectively: write the rule as a deny-list of internal prefixes and you will miss one, probably the one somebody adds next year. Write it as an allow-list of the headers you forward and you cannot, because everything not on the list is dropped by default.

Together they turn a field anybody can write into a field only your infrastructure can write.
The duplicate-header row is worth connecting to the capture. Sent straight at the backend, two copies of one header arrived joined as v1,v2. Sent through the , only v2 survived. One hop dropped a copy, which means two components in the chain were looking at two different requests. If one of them routes and the other authorises, that gap is the bug.
None of this means client headers are unusable. It means knowing which kind you have.

The bottom three are perfectly good to route on. They are not good to authorise on, and the difference is the one that gets forgotten.
The line is not between standard headers and custom ones. It is between values something verified and values something asserted.