Any Traefik instance with a public entrypoint is being scanned right now. Requests for /.env, /.git/config, /wp-admin, /phpMyAdmin, and /actuator arrive continuously from botnets and research scanners, and on a correctly configured instance they all get the same answer: a 404 generated by Traefik itself, because no router matches. That is normal background radiation, and paging on it will burn out your on-call in a week.

The operator problem is not “are we being scanned” but “did anything change that makes this scan dangerous.” The three states that matter are: a probe for a sensitive path returns 200 with content, scanning volume or targeting shifts from generic to focused, or request patterns show smuggling or SSRF indicators that Go’s strict parser does not fully neutralize because of what sits behind Traefik. This guide covers how to separate those states from noise, what to check first, and which signals are worth alerting on.

What the baseline looks like

When a request matches no router, Traefik returns 404 at the entrypoint level, before any service is selected. This shows up in traefik_entrypoint_requests_total{code="404"} and in the access log. It does not appear in service-level metrics, because no service was ever chosen. This is the exact opposite of a backend returning 404 for a missing resource, which shows up in traefik_service_requests_total{code="404"} and means the request was routed. Confusing these two layers sends investigations to the wrong team, and the distinction is your first scanning filter: entrypoint 404s on paths like /.env are uninteresting, service 404s or 200s on those paths are not.

In JSON access logs, an unmatched request has no associated router or service. If your access log fields are configured to include router and service names, entries from scanners will have them empty. If they are not empty for a request to /.git/config, that request was routed somewhere, and you need to find out where.

Noise versus a targeted attack

The useful triage axis is not the path list, which every scanner on the internet shares, but volume, diversity, and success.

SignalBackground noiseTargeted or escalated
Source distributionMany IPs, low request count eachOne or few IPs, high request count
Path diversitySmall set of well-known pathsMany distinct paths, or paths matching your real route structure
Response codesOverwhelmingly entrypoint 404Probes that return 200, 301, or 403 instead of 404
TimingConstant low rate, day and nightBursts, or scans that start right after a new route or DNS record is published
User agentsKnown scanner agents, empty agentsBrowser-like agents, or agents mimicking your own clients

The single highest-severity condition: a request for a path that should not exist returns 200 with content. A /.env that returns environment variables, a /.git/config that returns repository configuration, or an /actuator endpoint that returns Spring internals is not scanning noise anymore. It is an exposure, and it usually means a backend was deployed with a permissive route (for example a broad PathPrefix("/") router) that forwards everything to an application serving those files.

Also watch for requests that get past a middleware chain you expected to block them. Encoded path variants (such as requests containing %2e%2e or encoded slashes) have historically bypassed routing rules and middleware on specific Traefik versions. If a request path in the access log contains encoded traversal or encoded restricted characters and the response is not a 404, check your Traefik version against the security advisories for path-traversal and path-normalization bypasses. Several such CVEs were patched across the v2.11 and v3.x lines in 2025, so an unpatched instance may be routing requests its configuration says it should not.

Quick checks

These are read-only and safe to run during an incident.

# Probe paths that must always return 404 from the edge
for p in /.env /.git/config /wp-admin /actuator /phpMyAdmin; do
  printf '%s -> ' "$p"
  curl -s -o /dev/null -w '%{http_code}\n' "https://your-traefik-host$p"
done

# Entrypoint-level 404 rate (scanning pressure and route correctness)
curl -s http://localhost:8080/metrics | grep 'traefik_entrypoint_requests_total' | grep 'code="404"'

# Service-level 404s and 4xx (backend-generated; different meaning)
curl -s http://localhost:8080/metrics | grep 'traefik_service_requests_total' | grep -E 'code="4..'

# Scan for exploit-path hits in a JSON access log
jq -r 'select(.RequestPath | test("(\\.env|\\.git|wp-admin|phpMyAdmin|actuator)")) |
  [.ClientHost, .RequestPath, .DownstreamStatus] | @tsv' /var/log/traefik/access.log | \
  sort | uniq -c | sort -rn | head -30

# Find sensitive-path requests that did NOT return 404 (the ones that matter)
jq -r 'select(.RequestPath | test("(\\.env|\\.git|actuator)")) |
  select(.DownstreamStatus != 404) |
  [.ClientHost, .RequestPath, .DownstreamStatus, .RouterName] | @tsv' /var/log/traefik/access.log

# Check for odd Host headers (SSRF probing, host-header attacks)
jq -r '.RequestHost' /var/log/traefik/access.log | sort | uniq -c | sort -rn | head -20

Two notes on these checks. First, the jq field names depend on your access log configuration; Traefik’s JSON access log field names are version- and configuration-dependent, so adjust to what your log actually emits. Second, if Traefik sits behind a CDN or cloud load balancer, ClientHost will be the CDN’s address. You need the X-Forwarded-For or X-Real-IP header captured in the access log to see the real source.

Triage flow

flowchart TD
  A[Spike in exploit-path requests] --> B{Response codes?}
  B -->|All entrypoint 404| C{Volume or targeting changed?}
  B -->|200 / 301 / 403 on sensitive path| D[PAGE: exposure or routing bypass]
  C -->|No, steady background rate| E[Log and ignore; this is internet noise]
  C -->|Yes, single source or many distinct paths| F[TICKET: targeted reconnaissance]
  D --> G[Identify matched router in access log]
  D --> H[Check Traefik version against path-traversal advisories]
  F --> I[Correlate source IP across 404 and 403 counts]
  F --> J[Check for smuggling indicators in request headers]
  G --> K[Fix over-broad route or remove exposed file from backend]

Request smuggling indicators

Request smuggling is a different class of problem from path scanning. It exploits disagreement between how the front-end proxy and the back-end server parse a single HTTP request, so the backend sees a different request boundary than Traefik did. A successful smuggle can bypass authentication middleware, poison caches, or reach routes that Traefik’s router chain would have rejected.

Traefik is built on Go’s net/http, which has strict HTTP parsing. Go prioritizes Transfer-Encoding over Content-Length per RFC 9112 and rejects conflicting multiple Content-Length values. This blocks most classic CL.TE and TE.CL smuggling variants at the entrypoint, before a router is even evaluated. If your backends also parse strictly, the front door is largely closed.

The residual risk lives in two places:

  • Lenient backends. Node.js and some Java servlet containers accept request shapes that Go rejects or normalizes. If Traefik normalizes a request one way and the backend interprets it another way, the middleware chain (authentication, IP allow lists, rate limits) can be bypassed for the smuggled request.
  • HTTP/2 to HTTP/1.1 downgrade. Clients commonly speak HTTP/2 to Traefik while Traefik speaks HTTP/1.1 to backends. HTTP/2 frame boundaries are unambiguous, but after downgrade the request is re-serialized into HTTP/1.1 headers, and header ambiguity (H2.CL and H2.TE style disagreements) re-enters the picture. Go’s strict parser does not protect you here because the disagreement is between the downgraded byte stream and the backend’s parser.

What to look for in access logs and request data:

  • Requests with conflicting Content-Length and Transfer-Encoding headers. Standard access logs do not capture request headers by default; you need access log fields configured to include them, or a middleware that inspects them.
  • Host headers that do not match any configured router, especially Host values containing private IP addresses, localhost, or internal service names. This is SSRF and host-header probing.
  • Header names using underscore aliases of trusted forwarding headers (for example X_Forwarded_Host instead of X-Forwarded-Host). Backends that normalize underscores and dashes equivalently can be tricked into trusting injected forwarding context. Check your Traefik version against the header-sanitization advisories.
  • Unusually long header values or methods your service never legitimately receives (TRACE, DELETE on a read-only API).

Keep patching current. In 2025, a critical Go net/http smuggling CVE (bare LF in chunked coding) and multiple Traefik path-normalization CVEs were disclosed with fixes across the supported release lines. An edge proxy that is six months behind on patches has known, published bypasses that scanners actively probe for. Traefik’s default Server response header also discloses version information, which makes version-targeted scanning cheaper for an attacker.

Signals to monitor

SignalWhy it mattersWarning sign
traefik_entrypoint_requests_total{code="404"}Baseline scanning pressure; also catches route lossSustained rate more than 5% of total entrypoint requests, or sudden 3x jump over baseline
traefik_service_requests_total{code=~"4.."}401 spikes suggest credential stuffing; 403 spikes suggest authorization probingSustained 4xx rate more than 5x baseline
Sensitive-path requests returning non-404 in access logsThe difference between noise and exposureAny 200 with content on /.env, /.git/*, /actuator
Per-source 404/403 aggregation in access logsSeparates distributed noise from one focused scannerA single source generating hundreds of 404/403 across many distinct paths in a short window
traefik_entrypoint_requests_tls_total by version and cipherSudden appearance of unusual ciphers or old TLS versions can indicate downgrade probingSustained TLS 1.0/1.1 traffic, or abrupt cipher distribution shift
Host header distribution in access logsSSRF and host-header attack surfaceHost values that are IPs, localhost, or unknown domains at non-trivial volume

A practical detection rule for focused scanning, adapted to whatever log pipeline you run: alert when a single source address produces more than roughly a hundred 404 or 403 responses across more than roughly fifty distinct paths within a polling window. Distributed botnets each send a handful of requests and never trip a per-source threshold; a human-driven or single-origin scanner trips it quickly. Tune both numbers to your traffic.

Response and hardening

Fix the exposure, not the scanner. If a sensitive path returns 200, the fix is on your side: remove the file from the backend image, tighten the router so only intended paths match, or add an explicit deny rule. Blocking the scanning IP is cosmetic; the next botnet arrives in minutes.

Verify middleware actually applies to the probed path. Encoded-path bypasses mean a request can reach a backend without passing through the middleware chain attached to the “obvious” router. Test with encoded variants of your protected paths, not just the plain form, and patch Traefik if the behavior differs.

Reduce fingerprintability. Suppress or genericize the Server header so version-targeted scanning gets less signal. Confirm the dashboard, /api/*, and /debug/pprof/* are not reachable from the public network; an exposed API hands an attacker your complete route table and backend addresses.

Constrain what backends accept. Smuggling succeeds in the gap between parsers. Where possible, terminate strict parsing at the backend too, reject ambiguous requests at the application layer, and avoid forwarding hop-by-hop header ambiguity through the downgrade path.

Do not alert on raw 404 volume. Entrypoint 404s from scanning are constant. Alert on state changes: non-404 responses to sensitive paths, per-source threshold breaches, and 404-rate shifts that correlate with recent deployments (which suggest route misconfiguration rather than scanning; see the related guide on unmatched requests).

How Netdata helps

  • Netdata collects Traefik’s Prometheus metrics per second, so the code label on entrypoint and service request counters lets you split Traefik-generated 404s (scanning, route loss) from backend-generated 404s (application behavior) without log spelunking first.
  • Per-code request rate dashboards make the scanning baseline visible, so a deviation, whether a focused scanner or a sudden 401/403 burst, stands out against established normal rather than a static threshold.
  • ML anomaly detection on the entrypoint 404 and 4xx series flags rate shifts that correlate with deployments, which is how you catch “this 404 spike is a broken route, not a botnet.”
  • Correlating entrypoint 404 rate with traefik_config_last_reload_success on one dashboard separates scanning noise from provider desync, which produces the same 404 symptom for a completely different reason.
  • TLS version and cipher distribution charts surface downgrade-probing patterns alongside the request-rate data, so security signals and traffic signals are compared in the same time window.