diff --git a/setup.go b/setup.go
index 6e2034716a..cb891d67be 100644
--- a/setup.go
+++ b/setup.go
@@ -51,6 +51,8 @@ func (e *Executor) Setup() error {
 	}
 	e.setupDefaults()
 	e.setupConcurrencyState()
+	e.readTaskignore()
+
 	return nil
 }
 
@@ -63,6 +65,28 @@ func (e *Executor) getRootNode() (taskfile.Node, error) {
 	return node, err
 }
 
+func (e *Executor) readTaskignore() {
+	// get only the tasks that have the sources defined
+	tasksWithSources := taskfile.GetTasksWithSources(e.Taskfile)
+
+	if tasksWithSources == nil {
+		return
+	}
+
+	ignoreGlobs := taskfile.ReadTaskignore(e.Logger, e.Dir, e.Timeout)
+
+	if ignoreGlobs == nil {
+		return
+	}
+
+	// apply .taskignore globs to each task
+	for _, t := range tasksWithSources {
+		for _, g := range ignoreGlobs {
+			t.Sources = append(t.Sources, &ast.Glob{Glob: g, Negate: true})
+		}
+	}
+}
+
 func (e *Executor) readTaskfile(node taskfile.Node) error {
 	reader := taskfile.NewReader(
 		node,
diff --git a/task_test.go b/task_test.go
index afb40fa19d..1c2a84b133 100644
--- a/task_test.go
+++ b/task_test.go
@@ -2839,6 +2839,23 @@ func TestReference(t *testing.T) {
 	}
 }
 
+func TestTaskignore(t *testing.T) {
+	var buff bytes.Buffer
+	e := task.Executor{
+		Dir:    "testdata/taskignore",
+		Stdout: &buff,
+		Stderr: &buff,
+		Force:  true,
+		Silent: true,
+	}
+
+	require.NoError(t, e.Setup())
+
+	err := e.Run(context.Background(), &ast.Call{Task: "txt"})
+	assert.Equal(t, "dont_ignore.txt\n", buff.String())
+	require.NoError(t, err)
+}
+
 // enableExperimentForTest enables the experiment behind pointer e for the duration of test t and sub-tests,
 // with the experiment being restored to its previous state when tests complete.
 //
diff --git a/taskfile/taskignore.go b/taskfile/taskignore.go
new file mode 100644
index 0000000000..29d90cc1b5
--- /dev/null
+++ b/taskfile/taskignore.go
@@ -0,0 +1,68 @@
+package taskfile
+
+import (
+	"context"
+	"path"
+	"strings"
+	"time"
+
+	"github.com/go-task/task/v3/internal/logger"
+	"github.com/go-task/task/v3/taskfile/ast"
+)
+
+const taskignore = ".taskignore"
+
+func GetTasksWithSources(t *ast.Taskfile) []*ast.Task {
+	var tasksWithSources []*ast.Task
+
+	for _, task := range t.Tasks.Values() {
+		if len(task.Sources) > 0 {
+			tasksWithSources = append(tasksWithSources, task)
+		}
+	}
+
+	return tasksWithSources
+}
+
+func ReadTaskignore(l *logger.Logger, dir string, timeout time.Duration) []string {
+	bytes := read(l, dir, timeout)
+	globs := filterGlobs(bytes)
+
+	return globs
+}
+
+func read(l *logger.Logger, dir string, timeout time.Duration) []byte {
+	fileNode, err := NewFileNode(l, "", path.Join(dir, taskignore))
+
+	if err != nil {
+		return nil
+	}
+
+	ctx, cf := context.WithTimeout(context.Background(), timeout)
+	defer cf()
+
+	bytes, err := fileNode.Read(ctx)
+	if err != nil {
+		return nil
+	}
+
+	return bytes
+}
+
+func filterGlobs(bytes []byte) []string {
+	if len(bytes) == 0 {
+		return nil
+	}
+
+	lines := strings.Split(string(bytes), "\n")
+	var validGlobs []string
+
+	for _, line := range lines {
+		trimmed := strings.TrimSpace(line)
+		if trimmed != "" && !strings.HasPrefix(trimmed, "#") {
+			validGlobs = append(validGlobs, trimmed)
+		}
+	}
+
+	return validGlobs
+}
diff --git a/testdata/taskignore/.taskignore b/testdata/taskignore/.taskignore
new file mode 100644
index 0000000000..ca04abfd07
--- /dev/null
+++ b/testdata/taskignore/.taskignore
@@ -0,0 +1 @@
+./**/*.json
diff --git a/testdata/taskignore/Taskfile.yml b/testdata/taskignore/Taskfile.yml
new file mode 100644
index 0000000000..a4a609a90c
--- /dev/null
+++ b/testdata/taskignore/Taskfile.yml
@@ -0,0 +1,12 @@
+version: '3'
+
+tasks:
+   txt:
+    desc: Echo file names
+    sources:
+      - ./nested/ignore_me_too.json
+      - ./ignore_me.json
+      - ./dont_ignore.txt
+    cmds:
+      - for: sources
+        cmd: echo {{.ITEM}}
diff --git a/testdata/taskignore/dont_ignore.txt b/testdata/taskignore/dont_ignore.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/testdata/taskignore/ignore_me.json b/testdata/taskignore/ignore_me.json
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/testdata/taskignore/nested/ignore_me_too.json b/testdata/taskignore/nested/ignore_me_too.json
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/website/docs/usage.mdx b/website/docs/usage.mdx
index 2f5baa4773..f58b802b9f 100644
--- a/website/docs/usage.mdx
+++ b/website/docs/usage.mdx
@@ -789,6 +789,37 @@ tasks:
       - public/bundle.css
 ```
 
+It is also possible to exclude files from fingerprinting globally by creating `.taskignore`
+file in the same directory where the `Taskfile` is located.
+
+<Tabs defaultValue="2"
+      values={[
+        { label: 'Taskfile', value: '1' },
+        { label: '.taskignore', value: '2' }
+      ]}>
+
+<TabItem value="1">
+
+```yaml
+version: '3'
+
+tasks:
+  css:
+    sources:
+      - mysources/**/*.css
+    generates:
+      - public/bundle.css
+```
+
+</TabItem>
+<TabItem value="2">
+
+```yaml
+mysources/ignoreme.css
+```
+
+</TabItem></Tabs>
+
 If you prefer these check to be made by the modification timestamp of the files,
 instead of its checksum (content), just set the `method` property to
 `timestamp`.