Repository: traefik/traefik (language: go)

Issue:
Dynamic configuration not reloaded
### 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, I tried to run a dynamic configuration with the content below:
```yaml
# Static traefik configuration
providers:
  file:
    filename: /etc/traefik/dynamic/traefik-dynamic.yml
    watch: true

api:
  dashboard: true
  debug: true
  insecure: true

log:
  level: DEBUG

accessLog: {}
```

```yaml
# /etc/traefik/dynamic/traefik-dynamic.yml
http:
  routers:
    whoami-one:
      middlewares:
        - souin
      entrypoints:
        - http
      service: whoami1
      rule: Host(`whoami1.domain.com`)

  services:
    whoami1:
      loadBalancer:
        servers:
          - url: http://whoami1
        passHostHeader: false
```

I can see the service `whoami-one` is running, but if I update the `/etc/traefik/dynamic/traefik-dynamic.yml` file with the following configuration
```yaml
# /etc/traefik/dynamic/traefik-dynamic.yml
http:
  routers:
    whoami-one:
      middlewares:
        - souin
      entrypoints:
        - http
      service: whoami1
      rule: Host(`whoami1.domain.com`)
    whoami-two:
      middlewares:
        - souin
      entrypoints:
        - http
      service: whoami2
      rule: Host(`whoami2.domain.com`)

  services:
    whoami1:
      loadBalancer:
        servers:
          - url: http://whoami1
        passHostHeader: false
    whoami2:
      loadBalancer:
        servers:
          - url: http://whoami2
        passHostHeader: false
```
traefik won't update the handled routes and only `whoami1` persists. If I restart Træfik it loads the both endpoints.

### What did you see instead?

The configuration is unchanged.

### What version of Traefik are you using?

v3.0

### What is your environment & configuration?

```yaml
# Static traefik configuration
providers:
  file:
    filename: /etc/traefik/dynamic/traefik-dynamic.yml
    watch: true

api:
  dashboard: true
  debug: true
  insecure: true

log:
  level: DEBUG

accessLog: {}
```

```yaml
# /etc/traefik/dynamic/traefik-dynamic.yml
http:
  routers:
    whoami-one:
      middlewares:
        - souin
      entrypoints:
        - http
      service: whoami1
      rule: Host(`whoami1.domain.com`)

  services:
    whoami1:
      loadBalancer:
        servers:
          - url: http://whoami1
        passHostHeader: false
```

```yaml
# /etc/traefik/dynamic/traefik-dynamic.yml
http:
  routers:
    whoami-one:
      middlewares:
        - souin
      entrypoints:
        - http
      service: whoami1
      rule: Host(`whoami1.domain.com`)
    whoami-two:
      middlewares:
        - souin
      entrypoints:
        - http
      service: whoami2
      rule: Host(`whoami2.domain.com`)

  services:
    whoami1:
      loadBalancer:
        servers:
          - url: http://whoami1
        passHostHeader: false
    whoami2:
      loadBalancer:
        servers:
          - url: http://whoami2
        passHostHeader: false
```

I'm running traffic in a container and the both `whoami1` and `whoami2` are running in the same docker network so they are accessible.


### If applicable, please paste the log output in DEBUG level

_No response_


Discussion / hints:
Hello,

The documentation explains this inside the Limitations section: https://doc.traefik.io/traefik/providers/file/#provider-configuration

It's recommended to use `directory` instead of `filename` because `filename` can have issues with some filesystems.


@kevinpollet @ldez  it still doesn't work using
```yaml
providers:
  file:
    directory: /etc/traefik/dynamic
    watch: true
```
Hello @darkweak,

This is unexpected.
By looking at the config you have shared, I can see that the routers are using a middleware named `souin` but you didn’t share the definition of it nor the static part if it is indeed the souin plugin.
Can you please provide the full configuration needed to be able to try to reproduce the problem?
@rtribotte here is the minimal reproducible example

```yaml
# ./dynamic-configuration.yml
http:
  routers:
    whoami-one:
      entrypoints:
        - http
      service: whoami1
      rule: Host(`whoami1.domain.com`)
    whoami-two:
      entrypoints:
        - http
      service: whoami2
      rule: Host(`whoami2.domain.com`)

  services:
    whoami1:
      loadBalancer:
        servers:
          - url: http://whoami1
        passHostHeader: false
    whoami2:
      loadBalancer:
        servers:
          - url: http://whoami2
        passHostHeader: false
```
```yaml
# ./traefik.yml
providers:
  file:
    directory: /etc/traefik/dynamic
    watch: true

api:
  dashboard: true
  debug: true
  insecure: true

log:
  level: DEBUG

accessLog: {}
```
```yaml
# ./compose.yml
version: '3'

services:
  traefik:
    image: traefik:v3.0
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik.yml:/traefik.yml
      - ./dynamic-configuration.yml:/etc/traefik/dynamic/traefik-dynamic.yml
    ports:
      - 80:80
      - 8080:8080

  whoami1:
    image: traefik/whoami

  whoami2:
    image: traefik/whoami
```

If I update the `./dynamic-configuration.yml` file (remove the `whom-two` router declaration and the `whoami2` service declaration) and save the file changes nothing get reloaded in Træfik but I set the `watch: true` on the directory I put my dynamic configuration. Even without middlewares it doesn't get reloaded. 
If I restart træfik, the new configuration get loaded well with only the `whoami-one` router and the `whoami1` service.

Fix patch:
diff --git a/pkg/provider/file/file.go b/pkg/provider/file/file.go
index 21599431cb..8b9fbb40a5 100644
--- a/pkg/provider/file/file.go
+++ b/pkg/provider/file/file.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"path"
 	"path/filepath"
 	"strings"
 	"text/template"
@@ -47,18 +48,31 @@ func (p *Provider) Init() error {
 // using the given configuration channel.
 func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
 	if p.Watch {
-		var watchItem string
+		var watchItems []string
 
 		switch {
 		case len(p.Directory) > 0:
-			watchItem = p.Directory
+			watchItems = append(watchItems, p.Directory)
+
+			fileList, err := os.ReadDir(p.Directory)
+			if err != nil {
+				return fmt.Errorf("unable to read directory %s: %w", p.Directory, err)
+			}
+
+			for _, entry := range fileList {
+				if entry.IsDir() {
+					// ignore sub-dir
+					continue
+				}
+				watchItems = append(watchItems, path.Join(p.Directory, entry.Name()))
+			}
 		case len(p.Filename) > 0:
-			watchItem = filepath.Dir(p.Filename)
+			watchItems = append(watchItems, filepath.Dir(p.Filename), p.Filename)
 		default:
 			return errors.New("error using file configuration provider, neither filename or directory defined")
 		}
 
-		if err := p.addWatcher(pool, watchItem, configurationChan, p.watcherCallback); err != nil {
+		if err := p.addWatcher(pool, watchItems, configurationChan, p.watcherCallback); err != nil {
 			return err
 		}
 	}
@@ -92,15 +106,18 @@ func (p *Provider) BuildConfiguration() (*dynamic.Configuration, error) {
 	return nil, errors.New("error using file configuration provider, neither filename or directory defined")
 }
 
-func (p *Provider) addWatcher(pool *safe.Pool, directory string, configurationChan chan<- dynamic.Message, callback func(chan<- dynamic.Message, fsnotify.Event)) error {
+func (p *Provider) addWatcher(pool *safe.Pool, items []string, configurationChan chan<- dynamic.Message, callback func(chan<- dynamic.Message, fsnotify.Event)) error {
 	watcher, err := fsnotify.NewWatcher()
 	if err != nil {
 		return fmt.Errorf("error creating file watcher: %w", err)
 	}
 
-	err = watcher.Add(directory)
-	if err != nil {
-		return fmt.Errorf("error adding file watcher: %w", err)
+	for _, item := range items {
+		log.WithoutContext().Debugf("add watcher on: %s", item)
+		err = watcher.Add(item)
+		if err != nil {
+			return fmt.Errorf("error adding file watcher: %w", err)
+		}
 	}
 
 	// Process events


Test patch: