Skip to content
academia.sh

Lesson 03 / 16

Reverse Proxy and Forward Proxy

The same limit number produces two separate tails on two proxies; at limit 30 the reverse proxy leaves 6 subjects unprotected and 1160 units idle, the forward proxy counts that same 1160 units as throttled legitimate demand, and because it never sees 9 of the forty subjects and 430 units, its own report shows only 993 units.

Contents

In the previous two lessons, the balancer stood in the same place throughout: in front of the forty subjects, deciding on their behalf. Whoever sent the load, the lever was on the subjects’ side, and its purpose was to protect them.

An intermediary is not condemned to this stance. The same mechanism can be turned around: it can stand in front of the clients and decide on their behalf. This lesson’s question is not what the proxy does — it is on whose behalf it does it, and how that choice turns the same number into two separate tails.

The Same Intermediary, Two Stances

Both are intermediaries: they take a request, apply their own decision, pass it to the other side. The distinction is where they stand.

The reverse proxy stands in front of the subjects. To an outside viewer, it is the only address; the forty subjects behind it are not directly visible. The reasons the server side puts it there can be listed: feeding the request into a distribution rule, terminating the encrypted connection here, caching the response, and closing off the topology behind it from the outside. The decision is made on the subjects’ behalf. It is made without asking the subject whether it wants this — the subject is not, after all, the party that chose the intermediary in front of it.

The forward proxy stands in front of the clients. Every outgoing request passes through it; the address the destination side sees is the proxy’s, not the client’s. The reasons the client side puts it there can also be listed: limiting which destinations can be reached, logging outgoing requests, caching a frequently requested response, and hiding the client from the destination. The decision is made on the clients’ behalf.

The relationship between the load balancer and the reverse proxy, and the reverse proxy’s production configuration, were covered in the System Design and Distributed Systems curriculum and the Server-Side Fundamentals course; that account is not repeated here. The difference is one sentence: there the proxy’s role and configuration were measured, here the tail a single limit number leaves on forty subjects.

On the configuration side, the two resemble each other, and this is exactly why they get confused:

# taught configuration transcript, not run

reverse-proxy {
  listen      *:443
  upstream    subject-01 ... subject-40
  limit       concurrent 30 / subject
  purpose     protect the subject
}

forward-proxy {
  listen      internal-net:3128
  egress      every destination
  limit       rate 30 / client
  purpose     limit and hide the client
}

The Lever Is a Single Number

The lever is this: a single limit number, applied to forty subjects at once. On the reverse proxy this number is a concurrency limit — at most this much work passes to a subject at the same time. On the forward proxy it is a rate limit — a client can put out at most this much work at the same time.

The question of why forty separate limits are not written is a fair one, and its answer is outside the measurement. Forty separate numbers could be written; the moment they are, forty separate maintenance burdens are born. When a subject’s capacity changes, the line belonging to it also has to change; if it does not, the limit silently turns wrong and no gauge reports this. Operations gravitating toward a single number is not a shortcut, it is the direct result of maintenance cost — a single line sits in one place and watches forty subjects at once. The lever-to-subject ratio this course measures comes from exactly this: a single line’s cost is a tail, and forty lines’ cost is that, over time, forty lines stop genuinely describing forty subjects.

The tail exists in both stances, but its name and its owner change.

On the reverse proxy, a limit larger than a subject’s capacity does not protect it: the subject overruns its own capacity before reaching the limit, and the limit never engages. A limit smaller than a subject’s capacity keeps it below capacity; the difference is unused capacity.

On the forward proxy, the same two conditions read in reverse. A client whose demand is above the limit gets throttled, and the difference is rejected legitimate demand. A client whose demand stays below the limit is not touched by the limit at all.

There is one more difference, and it produces the measurement’s sharpest column. The reverse proxy cannot be bypassed: it is the only path to the subjects, all forty of the forty pass in front of it. The forward proxy can be bypassed; a client using it is a configuration choice, and some subjects are set up to exit directly.

The measurement’s assumptions:

  • TM14 — The forty subjects are the course’s fixed set and are not changed.
  • TM15 — In the reverse-proxy fiction, the subject is a server and the capacity field is the amount of work it can serve — the same reading as in the previous two lessons.
  • TM16 — In the forward-proxy fiction, the subject is a client and the capacity field is read as its legitimate demand. The set does not change, the reading does; this is the condition for the two stances to be comparable on the same numbers.
  • TM17 — The limit is a single number applied per subject; separate limits are not written for individual subjects. The lever’s singleness rests in this assumption.
  • TM18 — The subjects that bypass the forward proxy are the fiction’s special-marked devices. Bypassing is not a flaw, it is a recorded configuration choice; the proxy neither limits nor sees their traffic.
  • TM19 — The reverse proxy is the single entry point; no subject can bypass it.
  • TM20 — The measurement is not a network measurement; no connection is opened. What is counted is the limit’s arithmetic over forty subjects.

The Measurement

"""Same limit, two proxies: whose behalf the decision is on changes the tail."""
SEED = 20260812
CLASSES = ("interactive", "batch", "standby")


def generator(seed):
    d = seed % 2147483646 + 1

    def r(n):
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return r


def subjects(count=40, seed=SEED):
    r, result = generator(seed), []
    for i in range(count):
        result.append({"no": i + 1, "capacity": 20 + r(81), "delay": 5 + r(45),
                       "class": CLASSES[r(3)], "special": r(9) == 0})
    return result


def reverse(subs, limit):
    """Subject is a server; the limit is set on its behalf. Tail: unprotected and idle."""
    unprotected = sum(1 for s in subs if s["capacity"] < limit)
    idle = sum(max(0, s["capacity"] - limit) for s in subs)
    return unprotected, idle


def forward(subs, limit, passing):
    """Subject is a client; the limit is set on its behalf. Tail: throttled and never-seen."""
    g = [s for s in subs if s["no"] in passing]
    throttled = sum(1 for s in g if s["capacity"] > limit)
    units = sum(max(0, s["capacity"] - limit) for s in g)
    return throttled, units


subs = subjects()
passing = {s["no"] for s in subs if not s["special"]}
allsub = {s["no"] for s in subs}
print(f"subjects {len(subs)} | total demand {sum(s['capacity'] for s in subs)} | "
      f"passing forward proxy {len(passing)} | bypassing proxy {len(subs) - len(passing)} "
      f"({sum(s['capacity'] for s in subs if s['special'])} units)")
print()
print(f"{'limit':>6s} | {'reverse proxy':^22s} | {'forward proxy (31 pass)':^24s} | "
      f"{'if all forty passed':^27s}")
print(f"{'':>6s} | {'unprotect.':>10s} {'idle':>11s} | "
      f"{'throttled':>8s} {'throttled units':>15s} | {'throttled':>8s} {'units':>11s} "
      f"{'equal':>5s}")
for limit in (30, 45, 60, 75, 90):
    k, b = reverse(subs, limit)
    ki, bi = forward(subs, limit, passing)
    kt, bt = forward(subs, limit, allsub)
    print(f"{limit:6d} | {k:10d} {b:11d} | {ki:8d} {bi:15d} | {kt:8d} {bt:11d} "
          f"{sum(1 for s in subs if s['capacity'] == limit):5d}")
print()
print(f"subjects not passing the reverse proxy: {len(subs) - len(allsub)} "
      f"(it is the single entry point, all forty pass) | "
      f"subjects the forward proxy never sees: {len(subs) - len(passing)}, "
      f"{sum(s['capacity'] for s in subs if s['special'])} units")
subjects 40 | total demand 2336 | passing forward proxy 31 | bypassing proxy 9 (430 units)

 limit |     reverse proxy      | forward proxy (31 pass)  |     if all forty passed    
       | unprotect.        idle | throttled throttled units | throttled       units equal
    30 |          6        1160 |       27             993 |       33        1160     1
    45 |         12         711 |       23             620 |       28         711     0
    60 |         23         354 |       15             326 |       17         354     0
    75 |         29         128 |       10             125 |       11         128     0
    90 |         34          17 |        4              17 |        4          17     2

subjects not passing the reverse proxy: 0 (it is the single entry point, all forty pass) | subjects the forward proxy never sees: 9, 430 units

The Same Arithmetic, Two Names

The table’s most important feature is that two of its columns are identical, digit for digit. The reverse proxy’s “idle” column reads 1160, 711, 354, 128, 17; the rightmost “if all forty passed” units column also reads 1160, 711, 354, 128, 17. The same numbers, because both are the same sum: the positive part of the difference between each subject’s capacity and the limit.

The only thing that changes is that sum’s name. On the reverse proxy, this number is capacity left unused for the sake of protection: the subject could have served more, the limit did not allow it. On the forward proxy, the exact same number is rejected legitimate demand: the client would have asked for more, the limit did not allow it. The measure did not change; whose behalf it was set on did.

This has a direct consequence. Raising the limit reduces protection on the reverse proxy — unprotected subjects go from 6 at 30 to 34 at 90. The same move reduces throttling on the forward proxy — throttled subjects drop from 33 at 30 to 4 at 90. Turning the lever the same direction carries an opposite meaning in the two stances, and in a single dashboard the two show up as the same number.

In practice, this means the limit’s correct value cannot be read off from the measurement. The answer to “thirty or ninety” depends on which column in the table is deemed acceptable — and what makes that decision is not the lever itself, it is whose behalf you pull the lever on. This is also why the two sides cannot agree on the same number: what one calls protection, the other calls rejected demand.

The Complementary Tail

The two tails are each other’s complement, and their totals can be verified in the table. At limit 30, there are 6 unprotected, 33 that would have been throttled if all forty passed, and 1 subject exactly equal to the limit: six plus thirty-three plus one makes forty. At limit 90, 34 + 4 + 2 = 40. None of the three rows in between has a tie, and the two columns fill out to forty directly: 12 + 28, 23 + 17, 29 + 11.

What this means is: every one of the forty subjects, at every limit value, is in exactly one of the two tails. A subject staying below the limit is unprotected on the reverse proxy; one staying above it is throttled on the forward proxy. There is no limit value at which a subject escapes both, and there is no number that rescues all forty at once. Subjects exactly equal to the limit show up in neither column — one or two subjects in a set of forty, that is, one or two times the smallest measurable difference (1/40 = 0.025).

The only thing a single-number lever can do is choose where to split the forty subjects between these two tails. Not splitting is not an option the lever has.

What the Forward Proxy Cannot See

The last line gives the second difference. Because it is the only path to the subjects, the reverse proxy sees all forty at once; the number bypassing it is 0. The forward proxy, on the other hand, carries the traffic of only 31 of the forty subjects: 9 subjects are set up to exit directly, and their 430 units of demand never pass through the proxy at all.

The report’s counterpart to this is the middle columns. At limit 30, the forward proxy’s own report says 993 units were throttled. If all forty subjects passed through the proxy, this number would be 1160. The 167-unit gap in between is not missing — it was never counted. It shows up in the proxy’s report neither as throttled nor as free, because that traffic was never touched at all.

The same gap exists in the throttled-subject column too: the report says 27, the real number is 33. This is the structural weakness of a lever that decides on the client’s behalf — for it to be able to enforce its decision, the client has to agree to pass through it. A lever deciding on the server’s behalf does not need this consent, because it is already sitting on the path.

It should also be added that the nine subjects bypassing is not a flaw; it is a recorded choice, and it has a reason. What is a flaw is that the report never mentions these nine at all: a coverage report that does not say what falls outside its coverage shows the wrong total even if it counts what it does cover correctly.

The Distinction the Proxy Erases

The counterpart to address hiding on the other side is also a lever problem, and it is often overlooked in operations.

The 31 subjects passing through the forward proxy show up as a single address in the message the destination side sees. The destination side applies its own lever — a rate limit, a concurrency limit, an access rule — to that single address. Thirty-one of the forty subjects have landed on one subject in the other side’s ledger. A destination wanting to throttle its heaviest client throttles all thirty-one at once; wanting to block one of them, it blocks all thirty-one at once. The ratio this course measures — one lever, how many subjects — breaks a second time behind the proxy, and the other side does not know how many subjects it is touching.

On the reverse proxy, the same erasure works in the opposite direction. To an outside viewer, the forty subjects behind it are a single address; which subject answered does not appear in the message. Closing off the topology is already the desired outcome, but its side effect hits the measurement directly: which subject a response came from cannot be extracted from the message. If this information is not carried separately, the forty subjects’ tail collapses behind a single average, and none of the tables in the previous two lessons could be built.

The two proxies’ caching ability also carries the same distinction. The reverse proxy caches on the subjects’ behalf: a response produced once is given to many clients. The forward proxy caches on the clients’ behalf: a response received once is given to many clients’ requests to the same destination. In both cases, the cached copy can go to a request it was not produced for. The per-person feature flag governing this decision was measured in the Application Layer Protocols course and is not repeated here; what matters in this lesson is that the caching decision, too, is a single lever applied to all forty subjects at once.

Summary

  • The reverse proxy decides on the subjects’ behalf, the forward proxy on the clients’; both are intermediaries and their configurations look alike, the distinction is where they stand.
  • A single limit number produces unprotected subjects and idle capacity on the reverse proxy, throttled subjects and rejected legitimate demand on the forward proxy.
  • Idle capacity and rejected demand are the same sum: both columns read 1160, 711, 354, 128, 17; only the number’s name changes.
  • The two tails are complementary: at every limit value, unprotected, throttled, and exactly-at-limit subjects add up to forty (6 + 33 + 1, 34 + 4 + 2); no number rescues all forty at once.
  • The reverse proxy cannot be bypassed and sees all forty; 9 subjects bypass the forward proxy, 430 units never pass through it, and its report shows 993 instead of 1160, 27 instead of 33.
  • The proxy also erases the address distinction: the 31 subjects behind the forward proxy land on a single address at the destination side, and the destination’s lever touches all thirty-one at once; behind the reverse proxy, which subject a response came from does not survive in the message.

Next Step

In these three lessons, the lever stood at a single point throughout: where the request was forced to pass. The reverse proxy in front of the subjects, the forward proxy in front of the clients, but both at a single point. The next lesson multiplies this point: if content gets distributed across many copies standing close to the client, the lever no longer adjusts distribution but what stands where. The measure changes too — the question is not who gets throttled, but which subject the edge never sees: content waiting for its first request under the pull model, content never requested at all under the push model.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close