Repository: traefik/traefik (language: go)

Issue:
Dashboard not accessible since last update (v3.3.0)
### Welcome!

- [x] Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
- [x] Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any.

### What did you do?

Hello guys,

I updated to 3.3.0 but after this, dashboard is not reachable anymore.
After rolling back to 3.2.3, dashboard is working fine.

<img width="1463" alt="Image" src="https://github.com/user-attachments/assets/30a1c4ac-734c-42fe-8314-ba9eea8233fe" />

Other applications seams to work fine, except dashboard when running 3.3.0.

Regards,


### What did you see instead?

Nothing, it tries to download a file.

### What version of Traefik are you using?

Version:      3.3.0
Codename:     saintnectaire
Go version:   go1.23.4
Built:        2025-01-06T11:09:52Z
OS/Arch:      linux/amd64

### What is your environment & configuration?

```yaml
global:
  checkNewVersion: true
  sendAnonymousUsage: false

serversTransport:
  insecureSkipVerify: true

# -- (Optional) Change Log Level and Format here...
#     - loglevels [DEBUG, INFO, WARNING, ERROR, CRITICAL]
#     - format [common, json, logfmt]
log:
 level: INFO
 format: json
 filePath: /logs/traefik.log

# -- (Optional) Enable Accesslog and change Format here...
#     - format [common, json, logfmt]
accesslog:
  format: json
  filePath: /logs/access.log

# -- (Optional) Enable API and Dashboard here, don't do in production
api:
  dashboard: true
#  insecure: true
# -- Change EntryPoints here...
entryPoints:
  web:
    address: :80
    # -- (Optional) Redirect all HTTP to HTTPS
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: :443
    forwardedHeaders:
      trustedIPs:
        - "172.18.0.0/24"
        - "192.168.1.0/24"  # Adjust to restrict IPs if needed
    http:
      tls:
        # Generate a wildcard domain certificate
        certResolver: production
        domains:
          - main: xxxxxx.fr
            sans:
              - '*.xxxxxxx.fr'
      middlewares:
        - traefik-rate-limit@file
        - crowdsec-bouncer-traefik-plugin@file
        - securityHeaders@file
        - authelia@file
        - compress@file
    http3: {}
  # Ajout d'un point d'entree sur le port 8080
  metrics:
    address: :8080

#  mqtt:
#    address: :1883

#  mqtts:
#    address: :8883

# Ajout de la partie metrique qui concerne Prometheus
metrics:
  prometheus:
    # Nom du point d'entree defini au dessus
    entryPoint: metrics
    # On configure la latence des metriques
    buckets:
      - 0.1
      - 0.3
      - 1.2
      - 5.0
    # Ajout des metriques sur les points d'entree
    addEntryPointsLabels: true
    # Ajout des services
    addServicesLabels: true
    # Ajout des routeurs
    addRoutersLabels: true

#tracing:
#  otlp:
#    http:
#      endpoint: http://localhost:4318/v1/traces

# -- Configure your CertificateResolver here...
certificatesResolvers:
  staging:
    acme:
      email: xxxxxxxx@gmail.com
      storage: /etc/traefik/certs/acme.json
      caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
#      -- (Optional) Remove this section, when using DNS Challenge
#      httpChallenge:
#        entryPoint: web
#       -- (Optional) Configure DNS Challenge
      dnsChallenge:
        provider: ionos
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"
  production:
    acme:
      email: xxxxxx@gmail.com
      storage: /etc/traefik/certs/acme.json
      caServer: "https://acme-v02.api.letsencrypt.org/directory"
#       -- (Optional) Remove this section, when using DNS Challenge
#       httpChallenge:
#         entryPoint: web
#       -- (Optional) Configure DNS Challenge
      dnsChallenge:
        provider: ionos
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"
providers:
  providersThrottleDuration: 2s

  # File provider for connecting things that are outside of docker / defining middleware
  file:
    filename: /etc/traefik/fileConfig.yml
    watch: true

  # Docker provider for connecting all apps that are inside of the docker network
  docker:
    watch: true
    network: br-dams # Add Your Docker Network Name Here
    # Default host rule to containername.domain.example
    defaultRule: "Host(`{{ index .Labels \"com.docker.compose.service\"}}.xxxxxxxxx.fr`)"
    exposedByDefault: false
    endpoint: "tcp://dockersocket:2375" # Uncomment if you are using docker socket proxy

experimental:
  plugins:
    crowdsec-bouncer-traefik-plugin:
      moduleName: "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
      version: "v1.3.5"
    geoblock:
      moduleName: "github.com/PascalMinder/geoblock"
      version: "v0.2.8"
```

Add more configuration information here.


### If applicable, please paste the log output in DEBUG level

N/A


Discussion / hints:
FYI, here's the docker compose & middleware used for security : 

```yaml
services:
  traefik:
    image: traefik:v3.2.3
    container_name: traefik
    ports:
      - 80:80
      - 443:443
      - 8080:8080
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./config:/etc/traefik
      - ./logs/:/logs/
      - ./ssl-certs:/etc/traefik/certs
      - ./captcha.html:/captcha.html
      - ./ban.html:/ban.html
    environment:
      TZ: Europe/Paris
      IONOS_API_KEY: xxxxxxxxxxx
      DOCKER_HOST: dockersocket
    restart: always
    labels:
      traefik.http.routers.api.rule: Host(`traefik.xxxxxx.fr`)    # Define the subdomain for the traefik dashboard.
      traefik.http.routers.api.entryPoints: websecure    # Set the Traefik entry point.
      traefik.http.routers.api.service: api@internal    # Enable Traefik API.
      traefik.enable: true   # Enable Traefik reverse proxy for the Traefik dashboard.

# Security headers
securityHeaders:
  headers:
    customResponseHeaders:
      X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex"
      server: ""
      X-Forwarded-Proto: "https"
    accessControlAllowCredentials: false
    sslProxyHeaders:
      X-Forwarded-Proto: "https"
    referrerPolicy: "strict-origin-when-cross-origin"
    hostsProxyHeaders:
      - "X-Forwarded-Host"
    contentTypeNosniff: true
    browserXssFilter: true
    forceSTSHeader: true
    stsIncludeSubdomains: true
    stsSeconds: 63072000
    stsPreload: true
```

It is fixed to v3.2.3 to avoid any update from Watchtower. Before it was set to : latest.

Regards,
Hello,

I've opened similar issue in #11414 . Could you check if disabling your  `contentTypeNosniff: true` header make it ok with v3.3.0 ?

Regards,
Removing compress middleware solved the issue, like advised in #11414.
I'll keep this one opened as the #11414 has been closed.

<img width="1454" alt="Image" src="https://github.com/user-attachments/assets/23fcc3ca-91ea-4a22-8219-739537ac9963" />

Regards,
Hey there,

Thanks for the bug report.

We have put it on our (maintainer) plate to reproduce.
We'll keep you updated on this ticket once we have moved forward on it.

I have a similar issue. Upgraded from 3.2.5 to 3.3.1 and suddenly I only see this html when trying to access the dashboard:
[https://pastebin.com/4p5mYrmK](https://pastebin.com/4p5mYrmK)

Downgraded to 3.2.5 and the dashboard came back up.

Edit: in the dynamic config disabling contentTypeNosniff fixed the issue for 3.3.1 in my setup
`contentTypeNosniff: false`

Fix patch:
diff --git a/pkg/api/dashboard/dashboard.go b/pkg/api/dashboard/dashboard.go
index 21eeee8fb0..b44a14bba1 100644
--- a/pkg/api/dashboard/dashboard.go
+++ b/pkg/api/dashboard/dashboard.go
@@ -29,14 +29,10 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		assets = webui.FS
 	}
 
-	// allow iframes from traefik domains only
+	// Allow iframes from traefik domains only.
 	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
 	w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;")
 
-	// The content type must be guessed by the file server.
-	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
-	w.Header().Del("Content-Type")
-
 	if r.RequestURI == "/" {
 		indexTemplate, err := template.ParseFS(assets, "index.html")
 		if err != nil {
@@ -45,6 +41,8 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 
+		w.Header().Set("Content-Type", "text/html; charset=utf-8")
+
 		apiPath := strings.TrimSuffix(h.BasePath, "/") + "/api/"
 		if err = indexTemplate.Execute(w, indexTemplateData{APIUrl: apiPath}); err != nil {
 			log.Error().Err(err).Msg("Unable to render index template")
@@ -55,6 +53,10 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	// The content type must be guessed by the file server.
+	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
+	w.Header().Del("Content-Type")
+
 	http.FileServerFS(assets).ServeHTTP(w, r)
 }
 
@@ -84,13 +86,11 @@ func Append(router *mux.Router, basePath string, customAssets fs.FS) error {
 	router.Methods(http.MethodGet).
 		Path(dashboardPath).
 		HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			// allow iframes from our domains only
+			// Allow iframes from our domains only.
 			// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
 			w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;")
 
-			// The content type must be guessed by the file server.
-			// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
-			w.Header().Del("Content-Type")
+			w.Header().Set("Content-Type", "text/html; charset=utf-8")
 
 			apiPath := strings.TrimSuffix(basePath, "/") + "/api/"
 			if err = indexTemplate.Execute(w, indexTemplateData{APIUrl: apiPath}); err != nil {
@@ -103,7 +103,7 @@ func Append(router *mux.Router, basePath string, customAssets fs.FS) error {
 	router.Methods(http.MethodGet).
 		PathPrefix(dashboardPath).
 		HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			// allow iframes from traefik domains only
+			// Allow iframes from traefik domains only.
 			// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
 			w.Header().Set("Content-Security-Policy", "frame-src 'self' https://traefik.io https://*.traefik.io;")
 
@@ -113,5 +113,6 @@ func Append(router *mux.Router, basePath string, customAssets fs.FS) error {
 
 			http.StripPrefix(dashboardPath, http.FileServerFS(assets)).ServeHTTP(w, r)
 		})
+
 	return nil
 }


Test patch: