Repository: traefik/traefik (language: go)

Issue:
Allow to add path prefix for both api and dashboard endpoints
### Do you want to request a *feature* or report a *bug*?

Feature

### What did you expect to see?

As #5374 has been fixed, now dashboard assets can be served under any path prefix, but it continues to fetch api data from `/api`, which seems to be determined at build time or hard coded (?)

I would suggest to add something like `--api.pathprefix=/path/prefix` then these endpoints will be served under `/path/prefix/api` and `/path/prefix/dashboard`. Which will make them easier to integrate into existing system, for example:

-  session cookie under some path prefix.
-  avoid conflict with existent endpoints.



Discussion / hints:
Hi! I'm Træfiker :robot: the bot in charge of communication regulation.

Thanks for your interest in Traefik!

We dedicate the issue tracker to bug reports and feature requests only. My advanced AI has spotted that your issue might be a configuration problem or relates to something that doesn't look like a bug.

To confirm this, please join our [Community Forum](https://community.containo.us/) and reach out to us on the [Traefik section](https://community.containo.us/c/traefik).

In case I'm wrong (well, that would be embarrassing :sweat_smile:), my developers will re-open the issue and fix me!

In the meantime, you can double check [Traefik's documentation](https://docs.traefik.io/).
  
Following the [conversation in the Community Forum](https://community.containo.us/t/how-to-add-path-prefix-to-api-endpoints/2974):

From my point of view, this is more a bug report than a feature request. Here's why:

The dashboard should work with relative URLs **in general**. #5374 was fixed regarding dashboard URLs to dashboard assets only. What's missing now is:

* A fix regarding dashboard URLs to the API endpoint (currently absolute ``/api`` instead of relative ``api``).
* A fix regarding the redirect URL: With a path prefix of ``/traefik``, the dashboard currently redirects ``${HOST}/traefik`` to ``${HOST}/dashboard`` (using the absolute ``/dashboard``) instead of ``${HOST}/traefik/dashboard`` (using the relative ``dashboard``).

Only fixing all absolute URLs will effectively allow to put any and all Traefik related endpoints behind a path prefix.
The goal is to be able to host the dashboard at http://mysite/my/crazy/random/prefix. I suggest this as a test case that the implementation (bugfix) has worked. if you can navigate to that url and have the dashboard loaded and behave, then chances are the fix has worked.
Are there any updates concerning this issue. I would love to host bot my dashboard and my api under the PathPrefix /traefik, cause I want to use the path /api myself. 
@kdankert You can use `traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip` to expose traefik-dashboard at a path. 

```
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: traefik-ingress
  namespace: kube-system
  annotations:
    kubernetes.io/ingress.class: traefik
    traefik.ingress.kubernetes.io/rule-type: PathPrefixStrip
spec:
  rules:
  - host: host.traefik.com
    http:
      paths:
      - path: /traefik
        backend:
          serviceName: traefik-dashboard
          servicePort: 80
```

The above worked for me.
I know. Figured that out later, but my Problem ist that i cant do the same with the api, as if i try to, the Dashboard tries to access the api at the wrong endpoint (/api instead of /traefik/api) 
On traefik:v2.1.1 this use case is working for me. This is how it looks like:

`compose-traefik.yml`

```yml
    labels:
        #___ core configs
      - traefik.enable=true
        #___ set traefik dashboard + API. The API is available at: mydomain.club/api/http/routers
      - "traefik.http.routers.traefik.rule=Host(`mydomain.club`) && (PathPrefix(`/traefik`) || PathPrefix(`/api`))"
      - "traefik.http.routers.traefik.service=api@internal"
        #___ set TLS (https)
      - "traefik.http.routers.traefik.tls=true"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls.certresolver=leresolver"
        #___ use these middlewares
      - "traefik.http.routers.traefik.middlewares=traefik-strip"
        #___ specific middleware for traefik (it enables the dashboard at mydomain.com/traefik)
      - "traefik.http.middlewares.traefik-strip.stripprefix.prefixes=/traefik"
```

@pascalandy issue is that it is not possible currently to set api url to be `/traefik/api`
@lafriks oh I missed that! I jumped in the conversation when I saw this comment by @kdankert

> Are there any updates concerning this issue. I would love to host bot my dashboard and my api under the PathPrefix /traefik
@lafriks but this is exactly what I guess a few people would need. I want to host an own api under the path /api. I really require this. Unfortunately I don't understand go and how traefik is written, else I would try to implement it myself.
I'm currently in the same situation, I use only free DNS services as DuckDNS, but when I try to use different applications on different paths, some of them have already a path as /api that interfere with traefik api...
Nothing yet? This is really necesary :( 2 years later and nothing
@scratchmex 

If there are any news the issue will be progressed and you will see it here. It is currently assigned so it means someoneis going to work on it. As a rule, predictions as to when something is going to be fixed or added are not given.

> Nothing yet? This is really necesary :( 2 years later and nothing

2 years?
In our case we need to have multiple traefik 2+ deployed. Currently it is not possible to access the dashboard as the /api path cannot be changed. Hope this issue does not take very long because this would force me to a different solution.
@ldez would you accept a PR for this?

The idea would be to expose an additional endpoint. Which returns a JSON containing the API endpoint. This info can then be fetched by the static assets from a global var.
I am curious why this simple fix cannot be used?
There is used this config in the app
``` JSON
config: {
  "env": "production",
  "apiUrl": "/api"
}
```
path to the *api* can be made relative...
``` JSON
config: {
  "env": "production",
  "apiUrl": "../api"
}
@mshgh good point. This issue fix at least worked for my case.
@mshgh according to @ldez #6609 this approach does not work. But I don’t know the simple case he is referring to. As I build the container and it was working smoothly.

Do you have an idea?
I am not 100% sure about that comment either, but I would understand it more "we don't like it and don't want to do it this way" rather then it doesn't work. As, technically, it does.

I see the problem here https://github.com/containous/traefik/blob/master/webui/src/_helpers/APP.js as at the built time (into some *bundleXYZ.js*) path to API is set as a constant.

Idea: if inside the go code there exists *bundleXYZ.js* as a string following pseudo-code will work.
1st change the file above to ```... apiUrl: `API_PATH_PREFIX${process.env.APP_API}` ...```
2nd in the go code use `http.send_response_to_client(bundleXYZ_js.replace("API_PATH_PREFIX", GetOsEnvVariableValueOrEmptyStringIfNotExists("API_PATH_PREFIX"))`
I am sure it can be done nicer, not a golang developer.

In other word the JS served to the client with API url in it is dynamically generated.
This would lead to a `sed` like operation on every request, i don't think search and replace in a bundled file is any better. Lets wait maybe he will answer my question. Anyway thanks for the feedback. 
Something like `window.apiUrl` could be used to get it from html file where it could be assigned based on configuration
Or another solution (not the easiest one ...) is to develop a middleware that rewrite the HTML response: "replace all ``/api`` path by ``/prefix/path`` 
hi,
traefik v2.2.0 has the same issue.
```toml
[http.routers.api]
  rule = "Host(`192.168.56.10`) && PathPrefix(`/traefik`)"
  entrypoints = ["http"]
  service = "api@internal"
  middlewares = ["traefik"]
[http.middlewares]
    [http.middlewares.traefik.stripPrefix]
        prefixes = ["/traefik"]
```
`/traefik/api/rawdata` can be accessed
but `/traefik/dashboard/` cann't be accessed

I'd also say this is a bug, not a feature request. First thing I tried to do when migrating to Traefik v2 was to try to serve the dashboard at `/traefik` but that didn't work. The way this should be solved is to expose the api location (potentially specified in a new parameter if it can't be determined by routing rules) in a dynamically generated page that the static js references. Either a dynamic js page or dynamic html with a data element. Assuming the api is always going to be located at /api is extremely limiting.
This is very annoying
@Zireael agree
It is even more annoying if you consider how simply this can be fixed. I've mentioned it in this thread already - https://github.com/containous/traefik/issues/5853#issuecomment-604247872

I just cannot follow perspective of maintainer(s) of this application. This is all controlled via config.
* currently: `/api` says _I (the UI) demand to have API placed in the root of current domain_
* proposed: `../api` says _I (the UI) demand to have API placed in the same path as I am_

Or, maybe, the idea here is API located in root is cool, API located in subpath is lame...
Imagine if the PHPMyAdmin docker image didn't have the PMA_ABSOLUTE_URI option.
We are getting close to one year without this option in traefik v2.x.
Possible workaround:

```
- traefik.http.routers.traefik-http.rule=PathPrefix(`/dev/traefik`) || HeadersRegexp(`Referer`, `.*\/dev\/traefik\/.*`)
```

@korneil I don't see how that is workaround, problem that javascript has hardcoded `/api` path
@lafriks, a hack if you want to use traefik with prefix before it is implemented. If `/api` is called from the browser sending Referer, it will route that request to traefik.
Thank you, @korneil, this rule captures all requests made by the dashboard. (: My traefik instance is now redirecting `http` to `https`, hosting the dashboard behind the path `/traefik/` while protecting it with digest authentication. Here is the configuration I have in my docker-stack.yml file:

```
services:
  traefik:
    image: traefik:2.2.6
    networks:
      - proxy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    secrets:
      - auth_digest
    ports:
      - 80:80
      - 443:443
    deploy:
      placement:
        constraints: [node.role==manager]
      labels:
        - traefik.enable=true
        - traefik.docker.lbswarm=true
        - traefik.http.routers.http.entrypoints=http
        - traefik.http.routers.http.rule=HostRegexp(`{host:.+}`)
        - traefik.http.routers.http.middlewares=redirect-to-https
        - traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
        - traefik.http.routers.traefik.tls=true
        - traefik.http.routers.traefik.entrypoints=https
        - traefik.http.routers.traefik.rule=Host(`domain.com`) && (PathPrefix(`/traefik/`) || Headers(`Referer`, `https://domain.com/traefik/dashboard/`))
        - traefik.http.routers.traefik.middlewares=traefik
        - traefik.http.middlewares.traefik.chain.middlewares=traefik-digest-auth,traefik-strip-prefix
        - traefik.http.middlewares.traefik-digest-auth.digestauth.usersfile=/run/secrets/auth_digest
        - traefik.http.middlewares.traefik-strip-prefix.stripprefix.prefixes=/traefik
        - traefik.http.routers.traefik.service=api@internal
        - traefik.http.services.traefik.loadbalancer.server.port=8080
    command:
      --entrypoints.http.address=:80
      --entrypoints.https.address=:443
      --api.dashboard
      --providers.docker.swarmmode
      --providers.docker.exposedbydefault=false
      --providers.docker.network=proxy
```
Kubernetes + Jinja2 version:

traefik.rou.yml.j2
```yaml
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-dashboard
  namespace: {{ traefik.api.namespace }}
spec:
  entryPoints:
    - web
    - websecure
  routes:
  - match: Host(`{{ traefik.api.hostname }}`) && PathPrefix(`{{ traefik.api.pathprefix }}`) || Headers(`Referer`, `https://{{ traefik.api.hostname }}{{ traefik.api.pathprefix }}/dashboard/`)
    kind: Rule
    services:
    - name: api@internal
      kind: TraefikService
    middlewares:
    - name: traefik-mw
  tls:
    secretName: {{ traefik.api.tlsSecretName }}
```

traefik.mw.yml.j2
```yaml
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: traefik-mw
  namespace: {{ traefik.api.namespace }}
spec:
  stripPrefix:
    prefixes:
      - {{ traefik.api.pathprefix }}
```
> Thank you, @korneil, this rule captures all requests made by the dashboard. (: My traefik instance is now redirecting `http` to `https`, hosting the dashboard behind the path `/traefik/` while protecting it with digest authentication. Here is the configuration I have in my docker-stack.yml file:
> 
> ```
> services:
>   traefik:
>     image: traefik:2.2.6
>     networks:
>       - proxy
>     volumes:
>       - /var/run/docker.sock:/var/run/docker.sock:ro
>     secrets:
>       - auth_digest
>     ports:
>       - 80:80
>       - 443:443
>     deploy:
>       placement:
>         constraints: [node.role==manager]
>       labels:
>         - traefik.enable=true
>         - traefik.docker.lbswarm=true
>         - traefik.http.routers.http.entrypoints=http
>         - traefik.http.routers.http.rule=HostRegexp(`{host:.+}`)
>         - traefik.http.routers.http.middlewares=redirect-to-https
>         - traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
>         - traefik.http.routers.traefik.tls=true
>         - traefik.http.routers.traefik.entrypoints=https
>         - traefik.http.routers.traefik.rule=Host(`domain.com`) && (PathPrefix(`/traefik/`) || Headers(`Referer`, `https://domain.com/traefik/dashboard/`))
>         - traefik.http.routers.traefik.middlewares=traefik
>         - traefik.http.middlewares.traefik.chain.middlewares=traefik-digest-auth,traefik-strip-prefix
>         - traefik.http.middlewares.traefik-digest-auth.digestauth.usersfile=/run/secrets/auth_digest
>         - traefik.http.middlewares.traefik-strip-prefix.stripprefix.prefixes=/traefik
>         - traefik.http.routers.traefik.service=api@internal
>         - traefik.http.services.traefik.loadbalancer.server.port=8080
>     command:
>       --entrypoints.http.address=:80
>       --entrypoints.https.address=:443
>       --api.dashboard
>       --providers.docker.swarmmode
>       --providers.docker.exposedbydefault=false
>       --providers.docker.network=proxy
> ```

The right line also for /traefik/api without host
- traefik.http.routers.traefik.rule=PathPrefix(`/traefik/`) || PathPrefix(`/api`) || Headers(`Referer`, `http://.*\/traefik\/.*`)
(Do not use " otherwise yaml will complain)

The problem is that you are referring in any case to /api so api will answer on /traefik/api/rawdata and also on /api/rawdata
It is not a real solution, the fix need to be applied at code level.

This feature really needs to be added soon. 
I have "2000" routes starting with `/api/` in many of my services. 
I really want to use traefik to route to my services, but I cannot justify putting serveral months of work into changing /api/ in our spagetti code, across several projects.
> This feature really needs to be added soon.
> I have "2000" routes starting with `/api/` in many of my services.
> I really want to use traefik to route to my services, but I cannot justify putting serveral months of work into changing /api/ in our spagetti code, across several projects.

I don't use path-based routing for traefik's API requests, so you don't have to change routes in your app; just use my swarm template, not the one @zopar mentioned above, and your traefik instance will route only those requests made from the dashboard to the traefik instance. The ones that don't have the `Referer` header with the value of your dashboard URL will be routed to your app.
Thanks, @barhun, I :heart: you

This is what worked for me. 

I have an argotunnel infront of traefik, which is why the `Referer` url must contain https.
`TUNNEL_HOSTNAME=domain.com` the hostname is important for getting this to work, because `.*` catch all, doesn't seem to be working in header (referer) "parsing"

```
  traefik:
    image: $TRAEFIK_IMAGE
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.swarmmode=true"
      - "--providers.docker.constraints=Label(`com.docker.stack.namespace`, `${PROJECT_NAME}`)"
      - "--api=true"
      - "--api.insecure=true"
      - "--entrypoints.http.address=:80"
      - "--entrypoints.http.forwardedheaders.insecure=true"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      labels:
        traefik.enable: 'true'

        traefik.http.routers.traefik.rule: (PathPrefix(`/admin/traefik/`) || Headers(`Referer`, `https://${TUNNEL_HOSTNAME}/admin/traefik/dashboard/`))
        traefik.http.routers.traefik.service: api@internal
        traefik.http.routers.traefik.entrypoints: http
        traefik.http.routers.traefik.middlewares: strip-admin-traefik

        traefik.http.services.treafik.loadbalancer.server.port: 8080

        traefik.http.middlewares.strip-admin-traefik.stripprefix.prefixes: /admin/traefik

Still hope this bug is fixed.
Hi everyone,

the issue is a bug from my point of view, too.

We've used the [workaround](https://github.com/traefik/traefik/issues/5853#issuecomment-660658516) by @korneil – but had to change the rule to:

```
- traefik.http.routers.traefik-http.rule=PathPrefix(`/traefik`) || HeadersRegexp(`Referer`, `.*/traefik/.*`)
```

(we changed the escaped slashes `\/` within the Regex to regular slashes `/`)

Would be great, if the issue could find a bugfix soon =)

Best
Martin
I too, still consider this a bug. Hard-coding the api endpoint in the dashboard app as '/api' is obviously a mistake.  I have hundreds of services which expose an '/api' enpoint.  While not optimal, could you simply change the endpoint that the dashboard consumes from '/api' to something less common? Even changing it to '/traefik-dashboard-api' would work for most of my use cases and it wouldn't break the actual '/api' endpoint.  What we really need a simple override as a command option for traefik itself.  `--api.dashboard.api.endpoint` which defaults to '/api' but can be overridden to whatever you need it to be...  I realize that the client is a static html bundle, but it could be implemented using the same header trickery as mentioned above, where you pass the endpoint in as header and the client picks it up in real time...
I would say it's a bug too. `/api` is a very common prefix. It should be editable to avoid conflicts with other services.
Is there any update with getting this bug fixed? Look like the issue has been around for awhile and many of us are still running into it.
I concur - it's also confusing when you are showing the "services"
dashboard to new developers and you have to explain "well these are
traefik's internal endpoints (which you will never consume, or even care
about) - and these are the /api endpoints for the application you are
actually working on" - but, I digress ;-)

Any updates on this...? 
@juliens @ldez @emilevauge As was asked in slack, I'm continuing the conversation here. The PR that was attempted solved the problem in the majority of cases and could be improved later if it does not solve for edge cases. Sure, the PR did not provide a perfect solution, but as it sits, there has been no solution for over a year, blocking many of us.

Let's either reopen and merge the PR, fixing this problem for the majority of users facing it, or offer advice to the community as to how the PR could be improved. Mentoring and guiding community members into becoming more established contributors is good for everybody.
@joejulian 

The PR was not deemed acceptable because it would (among other things) break existing legitimate configurations. For example:

```yml
version: '3.7'

services:
  traefik:
    image: traefik:v2.4
    ports:
      - '80:80'
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./conf:/etc/traefik/conf
    command:
      - --api.dashboard
      - --entrypoints.web.address=:80
      - --providers.file.directory=/etc/traefik/conf
```

```toml
#dynamic
[http.routers]
  [http.routers.my-router]
    rule = "Host(`traefik.localhost`) && PathPrefix(`/api`)"
    service = "api@internal"
    entrypoints = ["web"]

  [http.routers.my-router2]
    rule = "Host(`traefik.localhost`) && PathPrefix(`/foo/bar`)"
    service = "dashboard@internal"
    middlewares = ["stripprefix"]
    entrypoints = ["web"]

[http.middlewares.stripprefix.stripPrefix]
  prefixes = ["/foo/bar"]
```

currently works in Traefik, i.e. you'll get the dashboard displayed at /foo/bar. But it would break with the proposed PR, i.e. the dashboard would fail to load its resources, as it would look for them at /foo/api when they are in fact located at /api.
This is just an example, but as far as some of us remember, there would also be other breaking scenarii with the proposed PR.


Additionally, and to answer your request of advice, there currently are ways (with middlewares, and plugins) to handle some of the requested cases. For example, to prefix both the dashboard and the API with /traefik, one could do:

```yml
version: '3.7'

services:
  traefik:
    image: traefik:v2.4
    ports:
      - '80:80'
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./conf:/etc/traefik/conf
    command:
      - --api.dashboard
      - --pilot.token=xxx
      - --experimental.plugins.rewritebody.modulename=github.com/traefik/plugin-rewritebody
      - --experimental.plugins.rewritebody.version=v0.3.1
      - --entrypoints.web.address=:80
      - --providers.docker
      - --providers.file.directory=/etc/traefik/conf
```

```toml
[http.routers]
  [http.routers.my-router]
    rule = "PathPrefix(`/traefik`)"
    middlewares = ["stripprefix", "rewrite"]
    service = "api@internal"
    entrypoints = ["web"]

[http.middlewares]
  [http.middlewares.rewrite-foo.plugin.rewritebody]
    [[http.middlewares.rewrite.plugin.rewritebody.rewrites]]
    regex = "/api"
    replacement = "/traefik/api"

  [http.middlewares.stripprefix.stripPrefix]
    prefixes = ["/traefik"]
```
Thanks, @mpl. That makes sense why the PR is inadequate as it could break existing user configurations.

Unfortunately the regex replacement breaks the actual api that the customer is trying to present to their users. The goal is to host a customer-facing application at /api but have traefik at some other prefix, `/traefik` to use your example. If you just redirect or rewrite `/api` to `/traefik` you interfere with that ability.

Could the static string be replaced with something dynamic so a new flag could be added to the cli that could customize that path? Any rough advice as to how that could be done?
@joejulian Did you try before sending your message?

Because if I understand your use case correctly, since the rewrite middleware only applies on the resources exposed by the `my-router` router (here the traefik api and dashboard), you can still have a customer-facing api on `/api`.


I haven't. I was trying to grok that from a kubernetes standpoint. It's entirely possible I've misread it. I'll give it a try and report back. Thanks, @juliens.
:tada: Yes, this solves it! Thanks @mpl, @juliens, and @ldez. This is really helpful and I appreciate not just the help, but the plugin and the local plugin integration that you added that makes this possible in air-gapped clusters.
Do I assume rightly that this fix only works with the Enterprise edition of Traefik?
@JSAnyone The [plugins](https://doc.traefik.io/traefik/v2.5/plugins/) are available in Traefik (OSS) and the [plugin rewritebody](https://github.com/traefik/plugin-rewritebody) is open source and free.

The [solution](https://github.com/traefik/traefik/issues/5853#issuecomment-896933164) that we have explained is available for all Traefik users or customers.

I'm sorry, I mixed up Traefik Enterprise and Traefik Pilot. Thanks for making that clear!
In v2.5.0, we introduced local plugins then you can use plugin without Traefik Pilot
The Replacement is sometimes really confusing: My normale API-Service with the Pathprefix `/api` is now of course displayed in the Traefik-Dashboard with the Pathprefix`/traefik/api`, since `/api` of course also here ist replaced by `/traefik/api`. But apart from this, I’m happy that it’s working.
For @jsanyone and anyone who wants to use a subpath and not have the body rewrite plugin showing /traefik/api for other routes in the dashboard. The following example uses /traefik as the subpath.

```
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: traefik-strip-prefix
spec:
  stripPrefix:
    prefixes:
    - /traefik

---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: traefik-rewrite-api
spec:
  plugin:
    rewrite:
      rewrites:
      - regex: /api
        replacement: /traefik/api
      - regex: (`.*?)/(?:traefik/(api))
        replacement: $1/$2

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-dashboard
spec:
  entryPoints:
  - websecure
  routes:
  - kind: Rule
    match: Host(`www.example.com`) && PathPrefix(`/traefik`)
    middlewares:
    - name: traefik-strip-prefix
    - name: traefik-rewrite-api
    services:
    - kind: TraefikService
      name: api@internal
  tls: {}

```


Is there any possible that the PathPrefix could be a field in access log configuration? So that, the PathPrefix could be generated in access log. And also, it could be used as a filter.
in v2, we should use
```
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: traefik-strip-prefix
spec:
  stripPrefix:
    prefixes:
    - /traefik

---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: traefik-rewrite-api
spec:
  replacePathRegex:
    regex: /api
    replacement: /traefik/api

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-dashboard
spec:
  entryPoints:
  - websecure
  routes:
  - kind: Rule
    match: Host(`www.example.com`) && PathPrefix(`/traefik`)
    middlewares:
    - name: traefik-strip-prefix
    - name: traefik-rewrite-api
    services:
    - kind: TraefikService
      name: api@internal
  tls: {}
```

However, this didn't work for me.

The dashboard frontend is still sending `/api` requests instead of `/traefik/api` to the backend, which I think is expected, but the response is 404.

 I checked the logs in the traefik controller. Controller couldn't find the routes for `/api` so it just returned 404. It makes sense too, right?

Does anyone know how is this setup supposed to work?


Just landed here after getting very frustrated by not being able to properly serve the Dashboard under `/traefik/dashboard` instead of `/dashboard`. As it has been reported already, the problem is the hard-coded `apiUrl='/api'` in the webapp.

The workaround suggested in https://github.com/traefik/traefik/issues/5853#issuecomment-896933164 is not very nice for us because it forces us to make (and maintain) a custom image derived from the Traefik official Docker image to include the [`rewritebody`](https://github.com/traefik/plugin-rewritebody) plugin. It can be argued that with Traefik Pilot it is not necessary, but then we are forced in turn to register and use an account, and our Traefik instance to access the Internet.

I hope the Traefik team can reconsider this issue and provide a solution similar to how is done in [Prometheus](https://www.robustperception.io/using-external-urls-and-proxies-with-prometheus/). I'm sure a non-neglectable amount of users will be very happy with support for custom prefixes for the Dashboard/API :).

EDIT: In the case of Prometheus, it is _very_ easy to achieve this (simplified example):
```yaml
services:
  prometheus:
    image: prom/prometheus:v2.37.0
    command: >-
      --web.external-url=/prometheus/
    deploy:
      labels:
        traefik.enable: 'true'
        traefik.http.routers.prometheus.rule: PathPrefix(`/prometheus/`)
        traefik.http.services.prometheus.loadbalancer.server.port: 9090
```

If such an option existed in Traefik, it would be quite similar to configure and get it working:
```yaml
# this configuration is hypothetical! it does not work currently
services:
  traefik:
    image: traefik:v2.8.1
    environment:
      TRAEFIK_API: 'true'
      TRAEFIK_API_EXTERNAL_URL: '/traefik/'  # hypothetical option
    deploy:
      labels:
        traefik.enable: 'true'
        traefik.http.routers.traefik.rule: PathPrefix(`/traefik/`)
        traefik.http.routers.traefik.service: 'api@internal'
        traefik.http.services.traefik.loadbalancer.server.port: 1
```

@hhromic Please try the solution in https://github.com/traefik/traefik/issues/5853#issuecomment-663914401 with the Referer matcher.
> @hhromic Please try the solution in [#5853 (comment)](https://github.com/traefik/traefik/issues/5853#issuecomment-663914401) with the Referer matcher.

Ah somehow missed that solution using the Referer matcher! Indeed it does work!
Interesting "hack" though, the endpoint is still exposed under `/api` but only to the specific referer of the Dashboard.

Even though I still think a proper "clean" solution should be implemented, this one seems "good enough" for now.
Thanks!
If you don't want to use plugins, here is a complete example that works on K8S/K3S with traefik v.2.6.2

```yaml
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: traefik-strip-prefix
spec:
  stripPrefix:
    prefixes:
    - /traefik

---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: traefik-dashboard
spec:
  entryPoints:
  - web
  routes:
  - kind: Rule
    match: PathPrefix(`/traefik`) || HeadersRegexp(`Referer`, `.*/traefik/.*`)
    middlewares:
      - name: traefik-strip-prefix
    services:
    - kind: TraefikService
      name: api@internal
```
> If you don't want to use plugins, here is a complete example that works on K8S/K3S with traefik v.2.6.2
> 
> ```yaml
> ---
> apiVersion: traefik.containo.us/v1alpha1
> kind: Middleware
> metadata:
>   name: traefik-strip-prefix
> spec:
>   stripPrefix:
>     prefixes:
>     - /traefik
> 
> ---
> apiVersion: traefik.containo.us/v1alpha1
> kind: IngressRoute
> metadata:
>   name: traefik-dashboard
> spec:
>   entryPoints:
>   - web
>   routes:
>   - kind: Rule
>     match: PathPrefix(`/traefik`) || HeadersRegexp(`Referer`, `.*/traefik/.*`)
>     middlewares:
>       - name: traefik-strip-prefix
>     services:
>     - kind: TraefikService
>       name: api@internal
> ```

This is working for me on k3s `v1.24.4+k3s1`, with the default traefik donfiguration.
> If you don't want to use plugins, here is a complete example that works on K8S/K3S with traefik v.2.6.2
> 
> ```yaml
> ---
> apiVersion: traefik.containo.us/v1alpha1
> kind: Middleware
> metadata:
>   name: traefik-strip-prefix
> spec:
>   stripPrefix:
>     prefixes:
>     - /traefik
> 
> ---
> apiVersion: traefik.containo.us/v1alpha1
> kind: IngressRoute
> metadata:
>   name: traefik-dashboard
> spec:
>   entryPoints:
>   - web
>   routes:
>   - kind: Rule
>     match: PathPrefix(`/traefik`) || HeadersRegexp(`Referer`, `.*/traefik/.*`)
>     middlewares:
>       - name: traefik-strip-prefix
>     services:
>     - kind: TraefikService
>       name: api@internal
> ```

This is the way, for other apps as well. Still working for k3s version `v1.27.7+k3s2`; just had to create the Middleware and specify the port to my service.
Hello everyone, 

I agree we don't have a perfect solution for this one, and only workarounds.
I'm adding the tag documentation so as a consequence we officially document these workarounds.
My work around was to route both of
  - any path prefix `/traefik`
  - any request with header `Referer` containing `/traefik` because the dashboard front end will send his requests to `/api` with that header: `Referer: http://127.0.0.1/traefik/dashboard/`
A Traefik v3.0 dynamic configuration that could work in my case:
```
http:
  routers:
    dashboard:
      # Catch any '/traefik' prefixed path or any request that has got a 'Referer' header set to '/traefik'
      rule: PathPrefix(`/traefik/`) || HeaderRegexp(`Referer`, `/traefik`)
      # this rule should be evaluated before any other potential request to '/api' having no Referer header
      priority: 999
      middlewares:
        - traefik-mid
      service: api@internal

  middlewares:
    # Because the traefik api and dashboard have hardcoded path prefixes /api and /dashboard, strip the '/traefik' path prefix.
    traefik-mid:
      stripPrefix:
        prefixes:
          - "/traefik"
```
its kind of ironic that the actual traefik dashboard itself doesn't play nicely behind a proxy with a path prefix :)
@JeromeGillard your workaround works in v2.11 as well, but i had to use HeadersRegexp (with an S)

thanks
> @JeromeGillard your workaround works in v2.11 as well, but i had to use HeadersRegexp (with an S)
> 
> thanks

You're welcome.

For reference: https://doc.traefik.io/traefik/master/migration/v2-to-v3/#new-v3-syntax-notable-changes
Thank you [@korneil ](#issuecomment-660658516), [@barhun](#issuecomment-663914401), [@zopar](#issuecomment-713610856), [@firecow ](#issuecomment-748849591), [@JeromeGillard](#issuecomment-1935608542) !

With the help of their answers I could serve traefik dashboard and api on a subdirectory
`https://www.example.com/adm/traefik` and `http://10.0.0.1:8080`:

	command:
      - "--api.insecure=true"
      - "--api.dashboard=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
    labels:
      - "traefik.enable=true"
      # HeadersRegexp is needed to serve both, Api and Dashboard
      - "traefik.http.routers.dashboard.rule=Host(`www.example.com`) && (PathPrefix(`/adm/traefik`) || HeadersRegexp(`Referer`, `/traefik`))"
      - "traefik.http.routers.dashboard.entrypoints=websecure"
      - "traefik.http.routers.dashboard.tls=true"
      - "traefik.http.routers.dashboard.service=api@internal"
      - "traefik.http.routers.dashboard.middlewares=dashboard-strip"
      - "traefik.http.middlewares.dashboard-strip.stripprefix.prefixes=/adm/traefik"
      - "traefik.http.services.dashboard.loadbalancer.server.port=8080"
```yml
http:
  routers:
    traefik:
      rule: PathPrefix(`/traefik`) || HeaderRegexp(`Referer`, `.*/traefik/.*`)
```

`HeadersRegexp` has been renamed to `HeaderRegexp` in [v3](https://doc.traefik.io/traefik/migration/v2-to-v3/#new-v3-syntax-notable-changes)
Hey there,

If a community member would like to build it, let us know, and we will work with you to make sure you have all the information needed so that it can be merged.  

We prefer to work with our community members at the beginning of the design process so that we can make sure that we are aligned and can move quickly with the review and merge process.  Let us know here or create a PR before you start, and we will work with you there.  

Don’t forget to check out the [contributor docs](https://github.com/traefik/contributors-guide/blob/master/pr_guidelines.md) and link the PR to this issue.
lol come on, just fix this already :)

its been 5 years. it would have been perfect for v3.0.

Fix patch:
diff --git a/docs/content/operations/dashboard.md b/docs/content/operations/dashboard.md
index c2b3c21e91..b7df89025c 100644
--- a/docs/content/operations/dashboard.md
+++ b/docs/content/operations/dashboard.md
@@ -87,8 +87,44 @@ rule = "Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashb
 ??? example "Dashboard Dynamic Configuration Examples"
     --8<-- "content/operations/include-dashboard-examples.md"
 
+### Custom API Base Path
+
+As shown above, by default Traefik exposes its API and Dashboard under the `/` base path,
+which means that respectively the API is served under the `/api` path,
+and the dashboard under the `/dashboard` path.
+
+However, it is possible to configure this base path:
+
+```yaml tab="File (YAML)"
+api:
+  # Customizes the base path:
+  # - Serving API under `/traefik/api`
+  # - Serving Dashboard under `/traefik/dashboard`
+  basePath: /traefik
+```
+
+```toml tab="File (TOML)"
+[api]
+  # Customizes the base path:
+  # - Serving API under `/traefik/api`
+  # - Serving Dashboard under `/traefik/dashboard`
+  basePath = "/traefik"
+```
+
+```bash tab="CLI"
+# Customizes the base path:
+# - Serving API under `/traefik/api`
+# - Serving Dashboard under `/traefik/dashboard`
+--api.basePath=/traefik
+```
+
+??? example "Dashboard Under Custom Path Dynamic Configuration Examples"
+    --8<-- "content/operations/include-dashboard-custom-path-examples.md"
+
 ## Insecure Mode
 
+!!! warning "Please note that this mode is incompatible with the [custom API base path option](#custom-api-base-path)."
+
 When _insecure_ mode is enabled, one can access the dashboard on the `traefik` port (default: `8080`) of the Traefik instance,
 at the following URL: `http://<Traefik IP>:8080/dashboard/` (trailing slash is mandatory).
 
diff --git a/docs/content/operations/include-dashboard-custom-path-examples.md b/docs/content/operations/include-dashboard-custom-path-examples.md
new file mode 100644
index 0000000000..6767afc45b
--- /dev/null
+++ b/docs/content/operations/include-dashboard-custom-path-examples.md
@@ -0,0 +1,83 @@
+```yaml tab="Docker & Swarm"
+# Dynamic Configuration
+labels:
+  - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && PathPrefix(`/traefik`)"
+  - "traefik.http.routers.dashboard.service=api@internal"
+  - "traefik.http.routers.dashboard.middlewares=auth"
+  - "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
+```
+
+```yaml tab="Docker (Swarm)"
+# Dynamic Configuration
+deploy:
+  labels:
+    - "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && PathPrefix(`/traefik`)"
+    - "traefik.http.routers.dashboard.service=api@internal"
+    - "traefik.http.routers.dashboard.middlewares=auth"
+    - "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
+    # Dummy service for Swarm port detection. The port can be any valid integer value.
+    - "traefik.http.services.dummy-svc.loadbalancer.server.port=9999"
+```
+
+```yaml tab="Kubernetes CRD"
+apiVersion: traefik.io/v1alpha1
+kind: IngressRoute
+metadata:
+  name: traefik-dashboard
+spec:
+  routes:
+  - match: Host(`traefik.example.com`) && PathPrefix(`/traefik`)
+    kind: Rule
+    services:
+    - name: api@internal
+      kind: TraefikService
+    middlewares:
+      - name: auth
+---
+apiVersion: traefik.io/v1alpha1
+kind: Middleware
+metadata:
+  name: auth
+spec:
+  basicAuth:
+    secret: secretName # Kubernetes secret named "secretName"
+```
+
+```yaml tab="Consul Catalog"
+# Dynamic Configuration
+- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`) && PathPrefix(`/traefik`)"
+- "traefik.http.routers.dashboard.service=api@internal"
+- "traefik.http.routers.dashboard.middlewares=auth"
+- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
+```
+
+```yaml tab="File (YAML)"
+# Dynamic Configuration
+http:
+  routers:
+    dashboard:
+      rule: Host(`traefik.example.com`) && PathPrefix(`/traefik`)
+      service: api@internal
+      middlewares:
+        - auth
+  middlewares:
+    auth:
+      basicAuth:
+        users:
+          - "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
+          - "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0"
+```
+
+```toml tab="File (TOML)"
+# Dynamic Configuration
+[http.routers.my-api]
+  rule = "Host(`traefik.example.com`) && PathPrefix(`/traefik`)"
+  service = "api@internal"
+  middlewares = ["auth"]
+
+[http.middlewares.auth.basicAuth]
+  users = [
+    "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/",
+    "test2:$apr1$d9hr9HBB$4HxwgUir3HP4EsggP/QNo0",
+  ]
+```
diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md
index 7e18a9b8bd..25c5a57f1f 100644
--- a/docs/content/reference/static-configuration/cli-ref.md
+++ b/docs/content/reference/static-configuration/cli-ref.md
@@ -42,6 +42,9 @@ Access log format: json | common (Default: ```common```)
 `--api`:  
 Enable api/dashboard. (Default: ```false```)
 
+`--api.basepath`:  
+Defines the base path where the API and Dashboard will be exposed. (Default: ```/```)
+
 `--api.dashboard`:  
 Activate dashboard. (Default: ```true```)
 
diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md
index 61f0a28b07..362c34591c 100644
--- a/docs/content/reference/static-configuration/env-ref.md
+++ b/docs/content/reference/static-configuration/env-ref.md
@@ -42,6 +42,9 @@ Access log format: json | common (Default: ```common```)
 `TRAEFIK_API`:  
 Enable api/dashboard. (Default: ```false```)
 
+`TRAEFIK_API_BASEPATH`:  
+Defines the base path where the API and Dashboard will be exposed. (Default: ```/```)
+
 `TRAEFIK_API_DASHBOARD`:  
 Activate dashboard. (Default: ```true```)
 
diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml
index 9bfa02e1ed..d0338aba10 100644
--- a/docs/content/reference/static-configuration/file.toml
+++ b/docs/content/reference/static-configuration/file.toml
@@ -294,6 +294,7 @@
       name1 = "foobar"
 
 [api]
+  basePath = "foobar"
   insecure = true
   dashboard = true
   debug = true
diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml
index 1fb8a7f303..b74bd56454 100644
--- a/docs/content/reference/static-configuration/file.yaml
+++ b/docs/content/reference/static-configuration/file.yaml
@@ -330,6 +330,7 @@ providers:
       name0: foobar
       name1: foobar
 api:
+  basePath: foobar
   insecure: true
   dashboard: true
   debug: true
diff --git a/pkg/api/dashboard/dashboard.go b/pkg/api/dashboard/dashboard.go
index a0ea9eb965..21eeee8fb0 100644
--- a/pkg/api/dashboard/dashboard.go
+++ b/pkg/api/dashboard/dashboard.go
@@ -1,36 +1,88 @@
 package dashboard
 
 import (
+	"fmt"
 	"io/fs"
 	"net/http"
 	"strings"
+	"text/template"
 
 	"github.com/gorilla/mux"
+	"github.com/rs/zerolog/log"
 	"github.com/traefik/traefik/v3/webui"
 )
 
+type indexTemplateData struct {
+	APIUrl string
+}
+
 // Handler expose dashboard routes.
 type Handler struct {
+	BasePath string
+
 	assets fs.FS // optional assets, to override the webui.FS default
 }
 
+func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	assets := h.assets
+	if assets == nil {
+		assets = webui.FS
+	}
+
+	// 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 {
+			log.Error().Err(err).Msg("Unable to parse index template")
+			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+			return
+		}
+
+		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")
+			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+			return
+		}
+
+		return
+	}
+
+	http.FileServerFS(assets).ServeHTTP(w, r)
+}
+
 // Append adds dashboard routes on the given router, optionally using the given
 // assets (or webui.FS otherwise).
-func Append(router *mux.Router, customAssets fs.FS) {
+func Append(router *mux.Router, basePath string, customAssets fs.FS) error {
 	assets := customAssets
 	if assets == nil {
 		assets = webui.FS
 	}
+
+	indexTemplate, err := template.ParseFS(assets, "index.html")
+	if err != nil {
+		return fmt.Errorf("parsing index template: %w", err)
+	}
+
+	dashboardPath := strings.TrimSuffix(basePath, "/") + "/dashboard/"
+
 	// Expose dashboard
 	router.Methods(http.MethodGet).
-		Path("/").
+		Path(basePath).
 		HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
 			prefix := strings.TrimSuffix(req.Header.Get("X-Forwarded-Prefix"), "/")
-			http.Redirect(resp, req, prefix+"/dashboard/", http.StatusFound)
+			http.Redirect(resp, req, prefix+dashboardPath, http.StatusFound)
 		})
 
 	router.Methods(http.MethodGet).
-		PathPrefix("/dashboard/").
+		Path(dashboardPath).
 		HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			// allow iframes from our domains only
 			// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src
@@ -40,22 +92,26 @@ func Append(router *mux.Router, customAssets fs.FS) {
 			// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
 			w.Header().Del("Content-Type")
 
-			http.StripPrefix("/dashboard/", http.FileServerFS(assets)).ServeHTTP(w, r)
+			apiPath := strings.TrimSuffix(basePath, "/") + "/api/"
+			if err = indexTemplate.Execute(w, indexTemplateData{APIUrl: apiPath}); err != nil {
+				log.Error().Err(err).Msg("Unable to render index template")
+				http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+				return
+			}
 		})
-}
 
-func (g Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	assets := g.assets
-	if assets == nil {
-		assets = webui.FS
-	}
-	// 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;")
+	router.Methods(http.MethodGet).
+		PathPrefix(dashboardPath).
+		HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			// 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")
+			// 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)
+			http.StripPrefix(dashboardPath, http.FileServerFS(assets)).ServeHTTP(w, r)
+		})
+	return nil
 }
diff --git a/pkg/api/handler.go b/pkg/api/handler.go
index b137e5401b..d0f90150e8 100644
--- a/pkg/api/handler.go
+++ b/pkg/api/handler.go
@@ -78,38 +78,40 @@ func New(staticConfig static.Configuration, runtimeConfig *runtime.Configuration
 func (h Handler) createRouter() *mux.Router {
 	router := mux.NewRouter().UseEncodedPath()
 
+	apiRouter := router.PathPrefix(h.staticConfig.API.BasePath).Subrouter().UseEncodedPath()
+
 	if h.staticConfig.API.Debug {
-		DebugHandler{}.Append(router)
+		DebugHandler{}.Append(apiRouter)
 	}
 
-	router.Methods(http.MethodGet).Path("/api/rawdata").HandlerFunc(h.getRuntimeConfiguration)
+	apiRouter.Methods(http.MethodGet).Path("/api/rawdata").HandlerFunc(h.getRuntimeConfiguration)
 
 	// Experimental endpoint
-	router.Methods(http.MethodGet).Path("/api/overview").HandlerFunc(h.getOverview)
-
-	router.Methods(http.MethodGet).Path("/api/entrypoints").HandlerFunc(h.getEntryPoints)
-	router.Methods(http.MethodGet).Path("/api/entrypoints/{entryPointID}").HandlerFunc(h.getEntryPoint)
-
-	router.Methods(http.MethodGet).Path("/api/http/routers").HandlerFunc(h.getRouters)
-	router.Methods(http.MethodGet).Path("/api/http/routers/{routerID}").HandlerFunc(h.getRouter)
-	router.Methods(http.MethodGet).Path("/api/http/services").HandlerFunc(h.getServices)
-	router.Methods(http.MethodGet).Path("/api/http/services/{serviceID}").HandlerFunc(h.getService)
-	router.Methods(http.MethodGet).Path("/api/http/middlewares").HandlerFunc(h.getMiddlewares)
-	router.Methods(http.MethodGet).Path("/api/http/middlewares/{middlewareID}").HandlerFunc(h.getMiddleware)
-
-	router.Methods(http.MethodGet).Path("/api/tcp/routers").HandlerFunc(h.getTCPRouters)
-	router.Methods(http.MethodGet).Path("/api/tcp/routers/{routerID}").HandlerFunc(h.getTCPRouter)
-	router.Methods(http.MethodGet).Path("/api/tcp/services").HandlerFunc(h.getTCPServices)
-	router.Methods(http.MethodGet).Path("/api/tcp/services/{serviceID}").HandlerFunc(h.getTCPService)
-	router.Methods(http.MethodGet).Path("/api/tcp/middlewares").HandlerFunc(h.getTCPMiddlewares)
-	router.Methods(http.MethodGet).Path("/api/tcp/middlewares/{middlewareID}").HandlerFunc(h.getTCPMiddleware)
-
-	router.Methods(http.MethodGet).Path("/api/udp/routers").HandlerFunc(h.getUDPRouters)
-	router.Methods(http.MethodGet).Path("/api/udp/routers/{routerID}").HandlerFunc(h.getUDPRouter)
-	router.Methods(http.MethodGet).Path("/api/udp/services").HandlerFunc(h.getUDPServices)
-	router.Methods(http.MethodGet).Path("/api/udp/services/{serviceID}").HandlerFunc(h.getUDPService)
-
-	version.Handler{}.Append(router)
+	apiRouter.Methods(http.MethodGet).Path("/api/overview").HandlerFunc(h.getOverview)
+
+	apiRouter.Methods(http.MethodGet).Path("/api/entrypoints").HandlerFunc(h.getEntryPoints)
+	apiRouter.Methods(http.MethodGet).Path("/api/entrypoints/{entryPointID}").HandlerFunc(h.getEntryPoint)
+
+	apiRouter.Methods(http.MethodGet).Path("/api/http/routers").HandlerFunc(h.getRouters)
+	apiRouter.Methods(http.MethodGet).Path("/api/http/routers/{routerID}").HandlerFunc(h.getRouter)
+	apiRouter.Methods(http.MethodGet).Path("/api/http/services").HandlerFunc(h.getServices)
+	apiRouter.Methods(http.MethodGet).Path("/api/http/services/{serviceID}").HandlerFunc(h.getService)
+	apiRouter.Methods(http.MethodGet).Path("/api/http/middlewares").HandlerFunc(h.getMiddlewares)
+	apiRouter.Methods(http.MethodGet).Path("/api/http/middlewares/{middlewareID}").HandlerFunc(h.getMiddleware)
+
+	apiRouter.Methods(http.MethodGet).Path("/api/tcp/routers").HandlerFunc(h.getTCPRouters)
+	apiRouter.Methods(http.MethodGet).Path("/api/tcp/routers/{routerID}").HandlerFunc(h.getTCPRouter)
+	apiRouter.Methods(http.MethodGet).Path("/api/tcp/services").HandlerFunc(h.getTCPServices)
+	apiRouter.Methods(http.MethodGet).Path("/api/tcp/services/{serviceID}").HandlerFunc(h.getTCPService)
+	apiRouter.Methods(http.MethodGet).Path("/api/tcp/middlewares").HandlerFunc(h.getTCPMiddlewares)
+	apiRouter.Methods(http.MethodGet).Path("/api/tcp/middlewares/{middlewareID}").HandlerFunc(h.getTCPMiddleware)
+
+	apiRouter.Methods(http.MethodGet).Path("/api/udp/routers").HandlerFunc(h.getUDPRouters)
+	apiRouter.Methods(http.MethodGet).Path("/api/udp/routers/{routerID}").HandlerFunc(h.getUDPRouter)
+	apiRouter.Methods(http.MethodGet).Path("/api/udp/services").HandlerFunc(h.getUDPServices)
+	apiRouter.Methods(http.MethodGet).Path("/api/udp/services/{serviceID}").HandlerFunc(h.getUDPService)
+
+	version.Handler{}.Append(apiRouter)
 
 	return router
 }
diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go
index 7f26e053a4..6451bc77fc 100644
--- a/pkg/config/static/static_config.go
+++ b/pkg/config/static/static_config.go
@@ -3,6 +3,7 @@ package static
 import (
 	"errors"
 	"fmt"
+	"path"
 	"strings"
 	"time"
 
@@ -145,16 +146,18 @@ type TLSClientConfig struct {
 
 // API holds the API configuration.
 type API struct {
-	Insecure           bool `description:"Activate API directly on the entryPoint named traefik." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"`
-	Dashboard          bool `description:"Activate dashboard." json:"dashboard,omitempty" toml:"dashboard,omitempty" yaml:"dashboard,omitempty" export:"true"`
-	Debug              bool `description:"Enable additional endpoints for debugging and profiling." json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty" export:"true"`
-	DisableDashboardAd bool `description:"Disable ad in the dashboard." json:"disableDashboardAd,omitempty" toml:"disableDashboardAd,omitempty" yaml:"disableDashboardAd,omitempty" export:"true"`
+	BasePath           string `description:"Defines the base path where the API and Dashboard will be exposed." json:"basePath,omitempty" toml:"basePath,omitempty" yaml:"basePath,omitempty" export:"true"`
+	Insecure           bool   `description:"Activate API directly on the entryPoint named traefik." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"`
+	Dashboard          bool   `description:"Activate dashboard." json:"dashboard,omitempty" toml:"dashboard,omitempty" yaml:"dashboard,omitempty" export:"true"`
+	Debug              bool   `description:"Enable additional endpoints for debugging and profiling." json:"debug,omitempty" toml:"debug,omitempty" yaml:"debug,omitempty" export:"true"`
+	DisableDashboardAd bool   `description:"Disable ad in the dashboard." json:"disableDashboardAd,omitempty" toml:"disableDashboardAd,omitempty" yaml:"disableDashboardAd,omitempty" export:"true"`
 	// TODO: Re-enable statistics
 	// Statistics      *types.Statistics `description:"Enable more detailed statistics." json:"statistics,omitempty" toml:"statistics,omitempty" yaml:"statistics,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
 }
 
 // SetDefaults sets the default values.
 func (a *API) SetDefaults() {
+	a.BasePath = "/"
 	a.Dashboard = true
 }
 
@@ -360,6 +363,10 @@ func (c *Configuration) ValidateConfiguration() error {
 		}
 	}
 
+	if c.API != nil && !path.IsAbs(c.API.BasePath) {
+		return errors.New("API basePath must be a valid absolute path")
+	}
+
 	return nil
 }
 
diff --git a/pkg/server/service/managerfactory.go b/pkg/server/service/managerfactory.go
index ce1fa8a271..19da05d7b2 100644
--- a/pkg/server/service/managerfactory.go
+++ b/pkg/server/service/managerfactory.go
@@ -4,6 +4,7 @@ import (
 	"net/http"
 
 	"github.com/gorilla/mux"
+	"github.com/rs/zerolog/log"
 	"github.com/traefik/traefik/v3/pkg/api"
 	"github.com/traefik/traefik/v3/pkg/api/dashboard"
 	"github.com/traefik/traefik/v3/pkg/config/runtime"
@@ -44,10 +45,13 @@ func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *s
 		apiRouterBuilder := api.NewBuilder(staticConfiguration)
 
 		if staticConfiguration.API.Dashboard {
-			factory.dashboardHandler = dashboard.Handler{}
+			factory.dashboardHandler = dashboard.Handler{BasePath: staticConfiguration.API.BasePath}
 			factory.api = func(configuration *runtime.Configuration) http.Handler {
 				router := apiRouterBuilder(configuration).(*mux.Router)
-				dashboard.Append(router, nil)
+				if err := dashboard.Append(router, staticConfiguration.API.BasePath, nil); err != nil {
+					log.Error().Err(err).Msg("Error appending dashboard to API router")
+				}
+
 				return router
 			}
 		} else {
diff --git a/webui/index.html b/webui/index.html
index 6eb6d6091a..654437a273 100644
--- a/webui/index.html
+++ b/webui/index.html
@@ -1,6 +1,13 @@
 <!DOCTYPE html>
 <html>
   <head>
+
+    {{if .APIUrl}}
+    <script>
+      window.APIURL = "{{.APIUrl}}"
+    </script>
+    {{end}}
+
     <title><%= productName %></title>
 
     <meta charset="utf-8">
diff --git a/webui/src/boot/api.js b/webui/src/boot/api.js
index 8d4f5b3149..1d8f6deaf1 100644
--- a/webui/src/boot/api.js
+++ b/webui/src/boot/api.js
@@ -4,7 +4,7 @@ import { APP } from '../_helpers/APP'
 
 // Set config defaults when creating the instance
 const api = axios.create({
-  baseURL: APP.config.apiUrl
+  baseURL: window.APIURL || APP.config.apiUrl
 })
 
 export default boot(({ app }) => {


Test patch: