Skip to content

Routing diagnostic traffic to a debug only TPS

Jeremy Echols edited this page Jul 24, 2026 · 2 revisions

Sometimes you need to reproduce a problem against a TPS that is built or configured differently from production -- extra logging, a patched binary, a tweaked token budget or lifetime -- without exposing that instance to the public or disturbing real traffic. The trick is to run a second "debug" TPS on its own port and let your front proxy send only your requests to it, keyed on a cookie that only you set.

Say production TPS listens on :18888 and an always-debug instance listens on :18889. You want :18889 to handle a request only when it carries the cookie debug-tps=secret-key; everyone else keeps hitting :18888. In Caddy, add a cookie matcher in front of the normal TPS handler and route matching requests to the debug port:

https://{$SITE_HOST:localhost} {
  tls internal
  log

  # The paths that go through TPS at all.
  @protected {
    path /protected/* /static-protected/*
  }

  # ...of those, the ones carrying our private debug cookie go to the
  # always-debug TPS on 18889. Use header_regexp (not a loose
  # `header Cookie *...*` substring match) so an unrelated cookie value can't
  # accidentally satisfy it.
  @debug_tps {
    path /protected/* /static-protected/*
    header_regexp Cookie "(^|;\s*)debug-tps=secret-key(\s*;|$)"
  }

  # Order matters: handle blocks are mutually exclusive and evaluated top to
  # bottom, so the more specific debug matcher has to come first.
  handle @debug_tps {
    reverse_proxy 127.0.0.1:18889
  }

  handle @protected {
    reverse_proxy 127.0.0.1:18888
  }

  handle {
    reverse_proxy app:8080
  }
}

Set the cookie yourself when you want to opt into the debug path, e.g. from the browser devtools console (document.cookie = "debug-tps=secret-key; path=/") or with curl:

curl -b "debug-tps=secret-key" https://your.host/protected/search?q=test

A few things to keep in mind:

  • Keep the debug port private. Bind the debug TPS to localhost (or an internal interface) and never publish it directly. The only way in should be through Caddy, which is what enforces the cookie gate. Treat secret-key as an actual secret -- anyone who knows it can route themselves to the debug instance.
  • Tokens are per-instance. The request budget and IP-switch state live in each instance's memory, so a session that bounces between :18888 and :18889 is tracked separately on each. If you want a token solved on one instance to be accepted by the other (so flipping the cookie on/off doesn't force a fresh challenge), give both instances the same JWT_SIGNING_KEY and matching TOKEN_BIND_* settings. If you want them fully independent, give them different signing keys.
  • It's just a router. TPS doesn't know about the debug-tps cookie at all; Caddy makes the routing decision and TPS only ever sees the request that was forwarded to it. You can use any cookie name and value you like, as long as the matcher and the cookie you set agree.

The most browser-agnostic way to set the cookie is from the dev-tools JavaScript console (usually F12, then the "Console" tab) while you're on the protected site — cookies are scoped to the site's domain, so you have to be on it. Paste a line like:

document.cookie = "debug-tps=secret-key; path=/; max-age=86400";
  • path=/ makes the cookie apply to every path on the site (the default would scope it to just the current page's path).
  • max-age is in seconds; 86400 is one day, so the cookie sticks around instead of vanishing when you close the tab. Use max-age=0 to delete it when you're done debugging.
  • If your protected content spans subdomains, add ; domain=.example.com to cover them all.

This works the same in every major browser and in private/incognito windows (just re-run it for that session). Reload the page after setting the cookie.

Clone this wiki locally