---
title: 'Reverse Proxy and Forward Proxy'
source: 'https://academia.sh/en/courses/network-operations/reverse-proxy-and-forward-proxy'
course: 'Network Operations and Automation'
language: en
updated: '2026-08-17T18:07:16+00:00'
license: 'CC BY-SA 4.0'
---

# 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.

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:

```text
# 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

```python
"""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.
