Minimal example for traefik with dashboard and basic auth

I'm trying to implement traefik with basic auth to protect the dashboard. I wrote a minimal example, but it doesn't work.

I generated the password like this:

$ htpasswd -nb admin password
admin:$apr1$vO3/IDvg$JrbhU9NSQub83/mQu9/fP1

docker-compose.yml

services:
  traefik:
    image: traefik:v2.10.1
    restart: unless-stopped
    ports:
      - 80:80
      - 8080:8080
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./traefik_dynamic.yml:/etc/traefik/traefik_dynamic.yml:ro

traefik.yml

providers:
  docker:
    exposedByDefault: false
    network: web
    watch: true
  file:
    filename: /etc/traefik/traefik_dynamic.yml
    watch: true

entryPoints:
  web:
    address: ":80"

api:
  dashboard: true
  insecure: true

traefik_dynamic.yml

http:
  middlewares:
    dashboard-auth:
      basicAuth:
        users:
          - "admin:$apr1$vO3/IDvg$JrbhU9NSQub83/mQu9/fP1"
  routers:
    api:
      rule: "Host(`localhost`)"
      entrypoints: ["web"]
      middlewares: ["dashboard-auth"]
      service: "api@internal"

I can browse to http://myserver/dashboard/ which loads without errors.

But it does not ask for a username:password. How do I fix that?

Try disabling the insecure mode:

Thanks. I just tried that but then it doesn't load the dashboard at all.

I think that option is for loading the dashboard over http (rather than https) - and for the minimal example I posted above, it's using http.

Simple Traefik example with dashboard.

Thanks. I want to use the config file, not the docker-compose with labels.

That said, I took your example, removed all the https stuff, and ran it. I got the same result - it loads the dashboard without asking for the username:password.

Here's the compose file:

services:
  traefik:
    image: traefik:v2.10.1
    ports:
      - 80:80
      - 8080:8080
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    command:
      - --api.dashboard=true
      - --api.insecure=true
      - --log.level=INFO
      - --providers.docker.exposedByDefault=false
      - --entrypoints.web.address=:80
    labels:
      - traefik.enable=true
      - traefik.http.routers.mydashboard.rule=Host(`localhost`)
      - traefik.http.routers.mydashboard.service=api@internal
      - traefik.http.routers.mydashboard.middlewares=myauth
      - traefik.http.middlewares.myauth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/

Can someone confirm whether the dashboard works without https and using the config file approach? (As I've shown in the minmal example above.)

This is a hack to quickly enable dashboard on port 8080, it will ignore middlewares, remove the line:

And the line was not in my config :wink:

Hi again and thanks. No, like I wrote above, without that the dashboard won't even load. There's no https in my config above.

Dashboard should be available at http://localhost/dashboard/.

Like I wrote above, it's not the case.

With that switch it loads without asking for username/password, without the switch it doesn't load.

the problem is easy to fix, you have to remove:

  • network: web because to don't have a web docker network
  • watch: true because this option is only for Swarm
  • insecure: true because you want to use the secured API.
docker-compose.yml
version: '3.9'

services:
  traefik:
    image: traefik:v2.10.1
    ports:
      - 80:80
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./traefik_dynamic.yml:/etc/traefik/traefik_dynamic.yml:ro
traefik.yml
providers:
  docker:
    exposedByDefault: false
  file:
    filename: /etc/traefik/traefik_dynamic.yml
    watch: true

entryPoints:
  web:
    address: ":80"

api:
  dashboard: true
traefik_dynamic.yml
http:
  middlewares:
    dashboard-auth:
      basicAuth:
        users:
          - "admin:$apr1$vO3/IDvg$JrbhU9NSQub83/mQu9/fP1"
  routers:
    api:
      rule: "Host(`localhost`)"
      entrypoints:
       - web
      middlewares:
       - dashboard-auth
      service: api@internal

I run those exact files and everything run fine.

launch traefik
$ docker-compose up --remove-orphans
[+] Running 1/0
 ✔ Container simple-traefik-1  Created                                                                                                                     0.0s 
Attaching to simple-traefik-1
simple-traefik-1  | time="2023-05-31T02:16:50Z" level=info msg="Configuration loaded from file: /etc/traefik/traefik.yml"
Dashboard access
$ curl http://localhost/dashboard/                        
401 Unauthorized

$ curl -u "admin:password" http://localhost/dashboard/
<!DOCTYPE html><html><head><title>Traefik</title><meta charset=utf-8><meta name=description content="Traefik UI"><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport co
ntent="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"><link rel=icon type=image/png href=statics/app-logo-128x128.png><link rel=icon type=image/png sizes=16x16 href=statics/icons/favicon-16x16.png><
link rel=icon type=image/png sizes=32x32 href=statics/icons/favicon-32x32.png><link rel=icon type=image/png sizes=96x96 href=statics/icons/favicon-96x96.png><link rel=icon type=image/ico href=statics/icons/favicon.ico><link rel=apple-to
uch-icon href=statics/icons/apple-icon-152x152.png><link rel=apple-touch-icon sizes=152x152 href=statics/icons/apple-icon-152x152.png><link rel=apple-touch-icon sizes=167x167 href=statics/icons/apple-icon-167x167.png><link rel=apple-tou
ch-icon sizes=180x180 href=statics/icons/apple-icon-180x180.png><link href=css/041f4ca8.f6e3d404.css rel=prefetch><link href=css/067758be.3de973dd.css rel=prefetch><link href=css/09354cb8.0e433876.css rel=prefetch><link href=css/09d4499
a.34653c53.css rel=prefetch><link href=css/11f98cd8.02647ec3.css rel=prefetch><link href=css/29ab06d1.ec33fe24.css rel=prefetch><link href=css/2a73a9da.e66a33dc.css rel=prefetch><link href=css/3e3ce03c.9b22e80f.css rel=prefetch><link hr
ef=css/46fd955e.9b22e80f.css rel=prefetch><link href=css/491024e9.9b22e80f.css rel=prefetch><link href=css/52875482.9b22e80f.css rel=prefetch><link href=css/6d73c73c.9b22e80f.css rel=prefetch><link href=css/77d911b4.9b22e80f.css rel=pre
fetch><link href=css/797e4f23.9b22e80f.css rel=prefetch><link href=css/b34404c8.9b22e80f.css rel=prefetch><link href=css/e036c7dc.1666b9a0.css rel=prefetch><link href=js/041f4ca8.4f70d06d.js rel=prefetch><link href=js/067758be.c7890581.
js rel=prefetch><link href=js/09354cb8.2e39e534.js rel=prefetch><link href=js/09d4499a.d0d6efba.js rel=prefetch><link href=js/11f98cd8.e511840d.js rel=prefetch><link href=js/29ab06d1.8f1fa71a.js rel=prefetch><link href=js/2a73a9da.b2faf
5c2.js rel=prefetch><link href=js/2d21e8fd.c00ac0e6.js rel=prefetch><link href=js/3e3ce03c.293f4729.js rel=prefetch><link href=js/46fd955e.343a8488.js rel=prefetch><link href=js/491024e9.5ba4955c.js rel=prefetch><link href=js/52875482.6
5e645a6.js rel=prefetch><link href=js/6d73c73c.d8a27bbb.js rel=prefetch><link href=js/77d911b4.8d5002b7.js rel=prefetch><link href=js/797e4f23.771f63ae.js rel=prefetch><link href=js/b34404c8.7a4b9be8.js rel=prefetch><link href=js/e036c7
dc.5d7ae0bf.js rel=prefetch><link href=css/app.3d2d5994.css rel=preload as=style><link href=js/app.0981e94c.js rel=preload as=script><link href=js/runtime.3de71a2a.js rel=preload as=script><link href=js/vendor.f60bcb86.js rel=preload as
=script><link href=css/app.3d2d5994.css rel=stylesheet></head><body><div id=q-app></div><script type=text/javascript src=js/app.0981e94c.js></script><script type=text/javascript src=js/runtime.3de71a2a.js></script><script type=text/java
script src=js/vendor.f60bcb86.js></script></body></html>%

The same thing but with only docker-compose (without the file provider)
I recommend this approach.

docker-compose.yml
version: '3.9'

services:
  traefik:
    image: traefik:v2.10.1
    ports:
      - 80:80
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    command:
      - --api
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
    labels:
      traefik.enable: 'true'

      # Dashboard
      traefik.http.routers.traefik.rule: Host(`localhost`)
      traefik.http.routers.traefik.entrypoints: web
      traefik.http.routers.traefik.service: api@internal
      traefik.http.routers.traefik.middlewares: dashboard-auth

      traefik.http.middlewares.dashboard-auth.basicauth.users: admin:$$apr1$$vO3/IDvg$$JrbhU9NSQub83/mQu9/fP1 # admin/password
Launch Traefik
$ docker-compose up --remove-orphans
[+] Running 1/0
 ✔ Container simple-traefik-1  Recreated                                                                                                                   0.0s 
Attaching to simple-traefik-1
simple-traefik-1  | time="2023-05-31T02:33:24Z" level=info msg="Configuration loaded from flags."
Dashboard access
$ curl http://localhost/dashboard/
401 Unauthorized

$ curl -u "admin:password"  http://localhost/dashboard/
<!DOCTYPE html><html><head><title>Traefik</title><meta charset=utf-8><meta name=description content="Traefik UI"><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"><link rel=icon type=image/png href=statics/app-logo-128x128.png><link rel=icon type=image/png sizes=16x16 href=statics/icons/favicon-16x16.png><link rel=icon type=image/png sizes=32x32 href=statics/icons/favicon-32x32.png><link rel=icon type=image/png sizes=96x96 href=statics/icons/favicon-96x96.png><link rel=icon type=image/ico href=statics/icons/favicon.ico><link rel=apple-touch-icon href=statics/icons/apple-icon-152x152.png><link rel=apple-touch-icon sizes=152x152 href=statics/icons/apple-icon-152x152.png><link rel=apple-touch-icon sizes=167x167 href=statics/icons/apple-icon-167x167.png><link rel=apple-touch-icon sizes=180x180 href=statics/icons/apple-icon-180x180.png><link href=css/041f4ca8.f6e3d404.css rel=prefetch><link href=css/067758be.3de973dd.css rel=prefetch><link href=css/09354cb8.0e433876.css rel=prefetch><link href=css/09d4499a.34653c53.css rel=prefetch><link href=css/11f98cd8.02647ec3.css rel=prefetch><link href=css/29ab06d1.ec33fe24.css rel=prefetch><link href=css/2a73a9da.e66a33dc.css rel=prefetch><link href=css/3e3ce03c.9b22e80f.css rel=prefetch><link href=css/46fd955e.9b22e80f.css rel=prefetch><link href=css/491024e9.9b22e80f.css rel=prefetch><link href=css/52875482.9b22e80f.css rel=prefetch><link href=css/6d73c73c.9b22e80f.css rel=prefetch><link href=css/77d911b4.9b22e80f.css rel=prefetch><link href=css/797e4f23.9b22e80f.css rel=prefetch><link href=css/b34404c8.9b22e80f.css rel=prefetch><link href=css/e036c7dc.1666b9a0.css rel=prefetch><link href=js/041f4ca8.4f70d06d.js rel=prefetch><link href=js/067758be.c7890581.js rel=prefetch><link href=js/09354cb8.2e39e534.js rel=prefetch><link href=js/09d4499a.d0d6efba.js rel=prefetch><link href=js/11f98cd8.e511840d.js rel=prefetch><link href=js/29ab06d1.8f1fa71a.js rel=prefetch><link href=js/2a73a9da.b2faf5c2.js rel=prefetch><link href=js/2d21e8fd.c00ac0e6.js rel=prefetch><link href=js/3e3ce03c.293f4729.js rel=prefetch><link href=js/46fd955e.343a8488.js rel=prefetch><link href=js/491024e9.5ba4955c.js rel=prefetch><link href=js/52875482.65e645a6.js rel=prefetch><link href=js/6d73c73c.d8a27bbb.js rel=prefetch><link href=js/77d911b4.8d5002b7.js rel=prefetch><link href=js/797e4f23.771f63ae.js rel=prefetch><link href=js/b34404c8.7a4b9be8.js rel=prefetch><link href=js/e036c7dc.5d7ae0bf.js rel=prefetch><link href=css/app.3d2d5994.css rel=preload as=style><link href=js/app.0981e94c.js rel=preload as=script><link href=js/runtime.3de71a2a.js rel=preload as=script><link href=js/vendor.f60bcb86.js rel=preload as=script><link href=css/app.3d2d5994.css rel=stylesheet></head><body><div id=q-app></div><script type=text/javascript src=js/app.0981e94c.js></script><script type=text/javascript src=js/runtime.3de71a2a.js></script><script type=text/javascript src=js/vendor.f60bcb86.js></script></body></html>%  

Thanks Idez! Unfortunately that is not working as expected.

curl http://localhost/dashboard/

  • Gives me 401

curl -u "admin:password" http://localhost/dashboard/

  • Gives me 401

If I add 8080:8080 to compose file, then:
curl -u "admin:password" http://localhost/dashboard/

  • It works! ...but only from inside that server
  • It doesn't work when I access it remotely: http://myserver:8080/dashboard/

Maybe I'm not understanding something important? Can I access the dashboard remotely, without https, and still have basic auth?

basic auth is not related to TLS (HTTPS).

If you are using my files, the port 8080 cannot work, it's impossible:

  • because port 8080 is not exposed
  • because http://localhost means port 80

If you want to access remotely you have to use a real domain and change the router rule: Host(`example.com`)

1 Like

Ok I will connect it to a domain and report my findings. Thanks for your advice!

BTW I was accessing it as http://xxx.xxx.xxx.xxx:8080/dashboard/

I think you don't provide all the information.

If you are using my files (I recommend just copy-paste my files) the port 8080 is not exposed so you cannot access it through port 8080.
But maybe to have something not related to Traefik that redirects port 8080 on port 80.

Or you are not using my files and you still have api.insecure: true and port 8080:8080.

Your context is not clear.

Hi, I am busy setting up new domain as you suggested... I am sure you are right that is the issue. :slight_smile:


But in the meantime, to answer your above comment:

I actually I did use your code exactly... copy-paste. And:

  • on server itself, curl http://localhost/dashboard/ gives 401 same as you
  • on server itself, curl -u "admin:password" http://localhost/dashboard/ gives 200/OK and dashboard same as you
  • BUT, on my computer, when I access remote server, curl -u "admin:password" http://xxx.xxx.xxx.xxx/dashboard/ it gives 404 (however when I add 8080, it loads but without username/password challenge)

So when I follow instructions exactly, it works inside server, but not when accessed from outside world.

No worries though - I will setup a domain and try again...

I am also having the same issue. I want to have my dashboard to have basic auth on "http://your-server-ip:8080/dashboard/". I get confused with traefik.yaml and toml files so I like everything to be on my docker-compose file as commands and/or labels.

Here is my compose file:

version: '3.8'

services:
  traefik:
    restart: unless-stopped
    image: traefik:latest
    command:
      ## Logs for debugging
      - --log.filePath=/var/logs/traefik.log
      - --log.level=INFO # (Default: error) DEBUG, INFO, WARN, ERROR, FATAL, PANIC
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.traefik.address=:8080"
      - "--certificatesresolvers.prodresolver.acme.email=${TRAEFIK_ACME_EMAIL}"
      - "--certificatesresolvers.prodresolver.acme.caserver=${TRAEFIK_ACME_SERVER}"
      - "--certificatesresolvers.prodresolver.acme.keytype=RSA4096"
      - "--certificatesresolvers.prodresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.prodresolver.acme.httpchallenge=true"
      - "--certificatesresolvers.prodresolver.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.prodresolver.acme.storage=/letsencrypt/acme.json"
      # traefik dashboard
      - --api.dashboard=true
      - --api.insecure=false
      - "--ping=true"
      - "--ping.entrypoint=ping"
      - "--global.checkNewVersion=true"
      - "--global.sendAnonymousUsage=false"
      - "--entryPoints.ping.address=:8082"
      - --providers.docker.exposedbydefault=false
      - "--providers.docker.endpoint=unix:///var/run/docker.sock"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    healthcheck:
      test: ["CMD", "wget", "http://localhost:8082/ping","--spider"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 5s
    volumes:
      - "./letsencrypt:/letsencrypt"
      - /var/run/docker.sock:/var/run/docker.sock:ro
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule: Host(`localhost`)"
      - "traefik.http.routers.traefik.entrypoints=web"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.traefik.middlewares=traefik-auth"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=${BASIC_AUTH}"
      - "traefik.http.routers.http-catchall.rule=HostRegexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.entrypoints=web"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
    networks:
      - traefik-network

networks:
  traefik-network:
    external: true

I am getting "404 page not found" on the web browser. Can you assist me?

What URL do you request? It seems strange to have a redirect to https, but at the same time only listen on http entrypoint web.

Compare to simple Traefik example.