A reverse proxy gives you a place to apply request policy before traffic reaches WordPress. To use it well, you need to understand which router matches, which middleware runs, and whether callers can bypass the proxy.

We’ll learn those mechanics with a disposable HTTP backend before connecting them to a real application.

Follow one request

An entrypoint is a listening address. A router selects requests using rules such as hostname and path. Middleware can examine or modify the request before a service sends it to a backend.

For WordPress, a public route and an administrative route may share a backend while applying different policies. The diagram shows the common path. Policy belongs on the route the request actually selects.

One front door, clear boundariesA reverse proxy handles the public connection and routing. The application still owns its own authentication, authorization, and correctness. One front door, clear boundaries FOLLOW THE ARROWSClientHTTPS requestProxyTLS and routingApplicationHandle the requestResponseReturn through proxy
A reverse proxy handles the public connection and routing. The application still owns its own authentication, authorization, and correctness.
View full-size diagram (opens in a new tab)

Build the smallest observable backend

In an empty working directory:

mkdir -p demo-site/wp-admin
printf 'public-demo\n' > demo-site/index.html
printf 'admin-demo\n' > demo-site/wp-admin/index.html
python3 -m http.server 8081 --bind 127.0.0.1 --directory demo-site

This is a static fixture, not WordPress. It makes routing visible without adding a database or login flow.

In the same working directory, create traefik.yml:

entryPoints:
  web:
    address: "127.0.0.1:8080"
providers:
  file:
    filename: dynamic.yml
accessLog: {}

Create dynamic.yml:

http:
  routers:
    public:
      entryPoints: [web]
      rule: "Host(`wp.example.test`)"
      priority: 10
      service: demo
    admin:
      entryPoints: [web]
      rule: "Host(`wp.example.test`) && (Path(`/wp-admin`) || PathPrefix(`/wp-admin/`) || Path(`/wp-login.php`))"
      priority: 100
      middlewares: [local-admin]
      service: demo
  middlewares:
    local-admin:
      ipAllowList:
        sourceRange: ["127.0.0.1/32"]
  services:
    demo:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:8081"

Start traefik --configFile=traefik.yml from that directory. Both listeners are loopback-only, and this exercise does not expose a dashboard or mount a container runtime socket.

Explicit priorities make the overlapping route decision visible. Review the Traefik routing rules when adapting the example.

Predict, request, inspect

curl --noproxy '*' -H 'Host: wp.example.test' http://127.0.0.1:8080/
curl --noproxy '*' -H 'Host: wp.example.test' http://127.0.0.1:8080/wp-admin/

Expect public-demo and admin-demo. The second request matches both routers, but the higher-priority admin router wins. Its IP allowlist permits the local client.

Now change the admin allowlist to ["192.0.2.0/24"], a documentation range that excludes this loopback client. Restart the lab proxy so the change is unambiguous. Repeat both requests. The public request should work; the admin request should return 403. Check the backend log: the rejected request should not arrive there.

Pause and predict

The admin router has middleware, but its priority is lower than the public router's. Does its existence protect matching requests?

Need a hint?

Middleware runs on the router that wins selection.

Show the reasoning
No. If the public router wins, its middleware chain applies. Protecting a route requires both correct matching and correct selection. A configuration file containing an access rule is not proof that traffic uses it.

Transfer the model to WordPress

Real deployment adds TLS, application authentication, persistent state, and a backend network that callers cannot reach around the proxy. An IP allowlist is not a user identity check. WordPress still needs its own authorization and updates.

Test the full workflow. Some themes and plugins use /wp-admin/admin-ajax.php for public features. A blanket restriction on that path can break legitimate behavior. An exception needs its own reviewed route and test, rather than weakening the entire admin policy.

Rate limits are another separate control. A token-bucket limit can constrain request bursts, but it doesn’t provide general denial-of-service protection. Choose its grouping and thresholds from workload behavior; see the RateLimit reference.

Try a bypass check

Apply the idea

The proxy blocks an administrative request, but the same caller can reach the backend directly. Is the boundary complete?

Need a hint?

Draw every route from the caller to the application.

Show the reasoning
No. The backend path bypasses the proxy decision. Restrict that path to the intended proxy or enforce the required controls at the backend too. Verify both the approved entry path and attempted direct access.

Stop both foreground processes and remove the demo directory when finished. Next, learn which address a proxy policy actually evaluates.