Traefik Rewrite-Body Plugin Compression Fix
Problem
The packruler/rewrite-body Traefik plugin (used for injecting analytics scripts
like rybbit into HTML responses) fails to decompress gzip-compressed responses from
backends. Despite the monitoring.types = ["text/html"] filter, the plugin attempts
to decompress ALL responses before checking content type. When decompression fails,
it corrupts the response body, breaking:
- WebSocket upgrade handshakes
- Authentication flows (HA Companion app's
external_authcallback) - Mobile app connectivity (while browser appears to work due to auto-reconnect)
Context / Trigger Conditions
- Traefik logs show:
Rewrite-Body | ERROR ... Error loading content: flate: corrupt input before offset 5 - Mobile apps (e.g., Home Assistant Companion) fail while browser works
- HA Companion app shows repeated
GET /?external_auth=1requests (auth loop) - WebSocket connections (
/api/websocket) are very short-lived (seconds instead of minutes) - HTTP 499 errors on API calls (client disconnects due to corrupted responses)
- Using
packruler/rewrite-bodyplugin v1.2.0 withmonitoring.types = ["text/html"]
Misleading Symptoms
- HTTP/3 (QUIC) may appear to be the cause because HTTP/3 requests show 499 errors. This is a red herring - the rewrite-body plugin corruption affects all protocols.
- WebSocket issues may look like a timeout or proxy configuration problem.
- The
monitoring.types = ["text/html"]config suggests the plugin should only touch HTML, but it still processes all responses for decompression before filtering.
Solution
Step 1: Create a strip-accept-encoding middleware
Add a Traefik middleware that removes Accept-Encoding from requests, forcing
backends to send uncompressed responses that the plugin can safely process:
# In traefik/middleware.tf
resource "kubernetes_manifest" "middleware_strip_accept_encoding" {
manifest = {
apiVersion = "traefik.io/v1alpha1"
kind = "Middleware"
metadata = {
name = "strip-accept-encoding"
namespace = kubernetes_namespace.traefik.metadata[0].name
}
spec = {
headers = {
customRequestHeaders = {
"Accept-Encoding" = ""
}
}
}
}
depends_on = [helm_release.traefik]
}
Step 2: Add middleware to routes with rewrite-body
In the ingress factory middleware chain, add strip-accept-encoding BEFORE the
rewrite-body middleware:
var.rybbit_site_id != null ? "traefik-strip-accept-encoding@kubernetescrd" : null,
var.rybbit_site_id != null ? "${var.namespace}-rybbit-analytics-${var.name}@kubernetescrd" : null,
The order matters: strip-accept-encoding must come first so the request reaches the backend without Accept-Encoding, and the uncompressed response then passes through the rewrite-body plugin.
Verification
- Check Traefik logs for absence of
flate: corrupt inputerrors:kubectl logs -n traefik -l app.kubernetes.io/name=traefik --tail=200 | grep -i "flate\|rewrite-body" - Verify the middleware chain includes strip-accept-encoding before rybbit:
kubectl get ingress -n <namespace> <name> -o jsonpath='{.metadata.annotations.traefik\.ingress\.kubernetes\.io/router\.middlewares}' - Test mobile app connectivity (HA Companion, etc.)
Notes
- This affects ALL services using the rewrite-body plugin, not just HA
- The fix is applied conditionally:
strip-accept-encodingis only added to the middleware chain whenrybbit_site_idis set, so services without analytics are unaffected - Both
ingress_factoryandreverse_proxy/factorymodules need the fix - Traefik may still compress responses to clients via its own compression middleware; the strip only affects the backend request
- The plugin's
monitoring.typesfilter works for deciding what to rewrite, but decompression is attempted on all responses regardless
See Also
ingress-factory-migration- Covers the ingress factory module that creates rybbit analytics middlewarestraefik-http3-quic- HTTP/3 configuration (not the cause, but often a red herring when debugging this issue)