You add an address to a blocklist, but the request still gets through. Or every visitor gets blocked at once. Both outcomes can come from evaluating the proxy’s address instead of the client’s.

Before choosing a filtering tool, trace where the address came from and why you trust it.

Separate the connection from the claim

Consider this fictional path:

browser -> edge proxy -> Traefik -> application

Traefik’s connected peer is the edge proxy. A forwarded header can describe an earlier client, but headers are data. A directly connected caller can send its own X-Forwarded-For unless the receiving boundary handles that input deliberately.

The diagram starts with establishing the address because an accurate allow/deny lookup cannot repair an untrusted input.

Decide before forwardingEvaluate the client address at the proxy before forwarding. When another proxy is upstream, trusted forwarding headers become part of that decision. Decide before forwarding RequestEstablish client addressIP policyEvaluate the allowlistForwardAddress is allowedRejectAddress is denied
Evaluate the client address at the proxy before forwarding. When another proxy is upstream, trusted forwarding headers become part of that decision.
View full-size diagram (opens in a new tab)

Work through the policy input

Traefik’s ipAllowList normally compares allowed ranges with the request’s remote address. Its ipStrategy options can select an address from a forwarding chain. Choosing a depth requires understanding your actual trusted hops. See the IPAllowList reference.

Suppose the edge has address 192.0.2.10 and the client is 198.51.100.25. A lookup against 192.0.2.10 answers whether the edge is allowed. It does not distinguish that client from another visitor using the same edge.

A lookup against a caller-supplied 198.51.100.25 has the opposite problem: it may distinguish visitors using claims they chose themselves.

Pause and predict

A direct caller adds an allowed address to X-Forwarded-For. Should that be enough to pass an IP policy?

Need a hint?

Who is authorized to assert an earlier client's identity?

Show the reasoning
No. Trust must begin at the connected peer and the known proxy chain. Otherwise the caller selects the identity used by the policy. Restrict trusted proxies, handle incoming forwarded headers deliberately, and test direct access separately.

Test classification without a network

An allowlist names permitted sources. A blocklist names denied sources. Start by testing that logic independently from header handling:

from ipaddress import ip_address, ip_network

blocked = [ip_network("198.51.100.0/24"), ip_network("2001:db8:1::/48")]

def denied(address):
    candidate = ip_address(address)
    return any(candidate in network for network in blocked)

cases = {
    "198.51.100.25": True,
    "203.0.113.25": False,
    "2001:db8:1::25": True,
    "2001:db8:2::25": False,
}
for address, expected in cases.items():
    actual = denied(address)
    assert actual == expected, address
    print(address, "deny" if actual else "allow")

These are documentation addresses. Save the code as classify.py and run python3 classify.py. Each assertion checks an expected decision. This program deliberately doesn’t parse headers or serve requests; those are separate trust and integration problems.

Put a decision service in the path only when needed

Traefik ForwardAuth can delegate a decision to another service: a successful 2xx response permits forwarding; other responses are returned to the caller. See the ForwardAuth contract.

If you implement a blocklist there, the service becomes a dependency on the request path. Specify how it receives a trustworthy address, how long the proxy waits, and how failure appears to the operator. Do not turn an unavailable decision into an unconditional approval.

The original version of this post used an nginx lookup without fully establishing the forwarded-client boundary. A small configuration is only useful when its input means what you think it means.

Try a four-case integration check

Use a backend that returns a known marker. Verify an allowed client, a blocked client, a direct caller with a forged forwarding header, and an unavailable policy service. Record both the proxy response and whether the backend received a request.

Apply the idea

A blocked request times out. Have you proved that the blocklist rejected it?

Need a hint?

A dead backend can also produce a failed request.

Show the reasoning
No. First establish that the backend works through an allowed path. Then correlate the denied request with the policy decision and absence of backend traffic. A failed request is an observation; the cause needs separate evidence.

You are ready to adopt the policy when you can name the trusted address source and demonstrate both successful access and deliberate rejection.