TLS 1.0 and 1.1 have been deprecated for years, and compliance regimes like PCI DSS require TLS 1.2 as a minimum. The risky part of setting a minimum TLS version is not the config change. It is finding out, before you enforce the floor, which clients still negotiate old protocol versions. Flip minVersion blind and you will discover the legacy client population from user complaints.
Traefik already counts every request by negotiated TLS version and cipher suite. That counter is the inventory tool you need: watch it, identify the holdouts, remediate or accept them, then enforce the floor with confidence.
The signal: traefik_entrypoint_requests_tls_total
Traefik exposes a counter at the entrypoint level that breaks TLS requests down by negotiated protocol version and cipher suite:
traefik_entrypoint_requests_tls_total{tls_version, tls_cipher, entrypoint}
Each increment records one request that arrived over a TLS connection using that version and cipher on that entrypoint. The tls_version label carries values like 1.0, 1.1, 1.2, and 1.3; tls_cipher carries the negotiated cipher suite name. Because it is a counter, you work with rates: a nonzero rate on tls_version="1.0" or tls_version="1.1" means legacy connections are serving traffic right now.
Two things this signal tells you:
- Legacy clients. Sustained TLS 1.0/1.1 traffic almost always means real clients that cannot or do not negotiate anything newer: old embedded devices, ancient Java or OpenSSL stacks in partner integrations, forgotten cron jobs, legacy mobile apps.
- Possible downgrade or scanning activity. A sudden spike in old-version traffic or unusual ciphers, without a known client change, can indicate protocol downgrade attempts or scanner traffic. This is rarer than plain legacy clients, but it is why this is a security signal and not just a hygiene one.
The same TLS breakdown exists at the router and service levels (traefik_router_requests_tls_total, traefik_service_requests_tls_total), which is useful when you need to know which applications the legacy clients are actually reaching rather than just which port they hit. Per-router and per-service metrics require the corresponding label options to be enabled and add cardinality, so enable them deliberately.
Procedure: inventory, attribute, enforce, verify
The safe order of operations is measure first, enforce last.
1. Confirm the metric is being collected
Scrape Traefik’s metrics endpoint and check the series exists:
# Confirm the TLS breakdown is exported
curl -s http://localhost:8080/metrics | grep traefik_entrypoint_requests_tls_total
If you get nothing, Prometheus metrics are not enabled, or entrypoint metrics are not collected for that entrypoint. Fix that first; without the series you are blind for everything below.
2. Baseline the version distribution
Before changing anything, establish what normal looks like over at least a week. Legacy clients often connect infrequently: a nightly batch job, a weekly partner sync. A one-hour snapshot will miss them.
# Request rate by TLS version across all entrypoints
sum by (tls_version) (rate(traefik_entrypoint_requests_tls_total[5m]))
You want this as a time series, not a point query. A nonzero 1.0/1.1 rate that appears at 02:00 every night is a batch client, not background noise.
3. Attribute the legacy traffic
The metric tells you old versions are in use; it does not tell you by whom. Attribution requires the access log:
- Enable access logs with fields that capture the TLS version and cipher per request, plus client source address.
- If Traefik sits behind a CDN or cloud load balancer, the source address in the log is the intermediary’s, not the real client’s. Use the forwarded header (
X-Forwarded-Foror equivalent) for attribution, and note that the intermediary may also be terminating TLS before Traefik. In that case the version Traefik sees is the hop from the intermediary, not the end client. - Group legacy-version log entries by source and by requested host/path. In practice the population is small: a handful of sources account for nearly all of it.
For each source, classify it: upgradeable (owned client, update the TLS library), partner (needs outreach and a deadline), or unknown (treat as hostile, do not accommodate).
4. Remediate or accept
For owned clients, fix the client. Common culprits are ancient OpenSSL, old Java runtimes with TLS 1.2 disabled by default, and embedded firmware that will never be updated. For partner clients, give notice with a cutoff date. For anything genuinely unfixable, decide explicitly whether to carve out an exception (a dedicated entrypoint or router with a lower floor, fronting only that integration) or to let it break. An unwritten exception policy is how floors never get enforced.
5. Enforce the floor
Set the minimum version in the TLS options of the dynamic configuration:
# Dynamic configuration (file provider, KV, or Kubernetes CRD)
tls:
options:
default:
minVersion: VersionTLS12
Valid values are VersionTLS10, VersionTLS11, VersionTLS12, and VersionTLS13. Operational notes that bite people:
- TLS options cannot be set via Docker labels or similar tag-based providers. They must come from a file provider, KV provider, or the TLSOption CRD in Kubernetes.
- The
defaultTLS option is special: only one option nameddefaultmay exist across all Kubernetes namespaces, and when referencing it you do not specify a provider namespace. Extradefaultoptions are silently dropped. - You can scope stricter options to specific routers instead of globally if you are running a staged rollout (new floor on the public API first, internal entrypoints later).
- In v3, the
preferServerCipherSuitesoption was removed; do not carry it over from v2 configs.
6. Verify enforcement
Do not trust the config. Probe it:
# Should fail with a protocol version alert
curl -v --tls-max 1.0 https://your-host/ -o /dev/null
# Should fail
curl -v --tls-max 1.1 --tlsv1.1 https://your-host/ -o /dev/null
# Should succeed
curl -v --tls-max 1.2 --tlsv1.2 https://your-host/ -o /dev/null
Caveat: on newer distributions the client TLS library may itself refuse to offer TLS 1.0/1.1 (security level policies), so a probe failure does not necessarily prove the server rejected it. Run the probes from a client you control, or interpret failures alongside the metric.
After enforcement, the legacy series should go to zero. If tls_version="1.0" still shows a nonzero rate after minVersion is set, something else is terminating TLS in front of Traefik (a cloud load balancer or CDN with its own TLS policy). A known discrepancy: external scanners like SSL Labs may report old versions as enabled even with minVersion correctly configured, because the scan reaches the service through a path that terminates TLS before Traefik. Verify with curl --tls-max against the actual entrypoint rather than trusting the scanner.
7. Watch for fallout
Watch overall request rate and the 4xx/5xx distribution for a few days after enforcement. Clients that cannot complete the handshake simply vanish from the request counters: a dropped TLS connection produces no HTTP request and no HTTP status code. The symptom of a lockout is missing traffic, not error traffic. If a normally present source disappears from the access logs after the change, that is your locked-out client.
flowchart TD
A[Watch TLS version metric] --> B{1.0 or 1.1 traffic?}
B -- no --> C[Enforce minVersion safely]
B -- yes --> D[Attribute via access logs]
D --> E{Client type?}
E -- owned --> F[Upgrade client TLS stack]
E -- partner --> G[Notify with cutoff date]
E -- unknown/hostile --> H[Do not accommodate]
F --> I[Enforce minVersion VersionTLS12]
G --> I
H --> I
I --> J[Verify with curl probes and metric at zero]Weak ciphers: the same signal, second axis
The tls_cipher label deserves its own pass:
- Inventory the cipher distribution alongside the version distribution. Any occurrence of a known-weak suite (RC4, DES, export-grade) in production traffic is worth a ticket.
- You can restrict TLS 1.2-and-below cipher suites via the
cipherSuitesfield in the TLS options. TLS 1.3 cipher suites are not configurable in Traefik: the Go TLS implementation enables the supported safe set and does not allow overrides, and TLS 1.2 suites cannot be used with 1.3 or vice versa. - There are community reports of CBC suites still being offered in v3 despite an explicit restricted
cipherSuiteslist. Treat cipher-suite filtering as something to verify with a probe (openssl s_client -cipheragainst the entrypoint), not something to assume from config. - A sudden appearance of unusual cipher values, especially with a spike in old protocol versions, is the downgrade/scanning pattern. Correlate with source addresses in the access log before treating it as an attack; it is usually a scanner, which is noise, but confirm it.
Common pitfalls
- Enforcing before inventorying. The most common mistake. The metric exists precisely so you can find the holdouts first. One week of version-distribution data is the minimum; a month is better for catching monthly batch cycles.
- Assuming zero means zero. If something upstream terminates TLS (CDN, cloud LB), Traefik sees the inner hop’s TLS version. The legacy client may be talking TLS 1.0 to the CDN while Traefik sees 1.2. Check the front-most terminator’s metrics too.
- Expecting an error signal for locked-out clients. Failed handshakes produce no request log line and no HTTP status. Monitor for absent traffic, not for errors.
- Forgetting the router/service dimension. Entrypoint-level data tells you old versions exist on :443. If you run many services behind one entrypoint, enable router-level TLS metrics during the attribution phase so you know which application the legacy clients depend on, then decide whether to keep them on (cardinality cost).
- Treating cipher config as verified. Always probe after setting
cipherSuitesorminVersion. Config applied does not mean behavior enforced.
Signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
rate(traefik_entrypoint_requests_tls_total{tls_version="1.0"}) and {tls_version="1.1"} | The compliance signal: any sustained rate means legacy clients or downgrade activity | Any nonzero sustained rate; new appearance after being zero |
traefik_entrypoint_requests_tls_total by tls_cipher | Exposes weak cipher usage (RC4, DES, export-grade) and cipher distribution anomalies | Known-weak suites present; sudden distribution shift |
traefik_entrypoint_requests_tls_total by tls_version (full distribution) | Baseline for what your client population negotiates; validates that 1.2/1.3 dominate | 1.2 share dropping without a client change |
| Overall entrypoint request rate around enforcement | Handshake failures leave no HTTP trace; lockouts show as vanished traffic | A known source or route going quiet after a minVersion change |
| Router/service TLS counters (if enabled) | Attributes legacy versions to specific applications | Old versions concentrated on one router: targeted remediation |
Severity guidance from the playbook: sustained TLS 1.0/1.1 traffic is ticket-level (a compliance/security finding to remediate during business hours). Cipher distribution anomalies are info-level unless weak ciphers are confirmed in real traffic, which is ticket-level. Neither should page on its own; a confirmed downgrade attack in progress is a security incident handled through your security process, not a metric threshold.
How Netdata helps
- Netdata charts the TLS version and cipher breakdown from
traefik_entrypoint_requests_tls_totalper entrypoint, so a nonzero 1.0/1.1 rate is visible immediately rather than buried in a PromQL query you run quarterly. - ML anomaly detection on the version series flags a new appearance of an old version (or an unusual cipher) without you having to define what unusual means per entrypoint.
- Correlating the TLS version series with overall entrypoint request rate around a
minVersionchange makes the “client vanished” lockout pattern visible: request count drops for one population with no corresponding error spike. - Long retention on these counters supports the inventory-before-enforcement workflow: you can look back a month and confirm whether the 02:00 legacy spike is a recurring batch client or a one-off.
Related guides
- Traefik 404 not found: requests arriving with no matching router
- Traefik 502 Bad Gateway: when the backend is unreachable or returns garbage
- Traefik 503 Service Unavailable: no healthy backends left in the pool
- Traefik 504 Gateway Timeout: the backend is alive but too slow
- Traefik 5xx error rate: telling Traefik-generated errors from backend errors
- Traefik access log blocking: when logging stalls request handling
- Traefik ACME challenge failed: HTTP-01, DNS-01, and TLS-ALPN-01 renewal errors
- Traefik acme.json permissions and corruption: renewal silently blocked
- Traefik ACME lock contention: stuck distributed locks blocking renewal
- Traefik ACME rate limit: too many certificates already issued for this domain
- Traefik backend connection pool: keep-alive, MaxIdleConnsPerHost, and reuse
- Traefik cannot assign requested address: ephemeral port exhaustion






