diff --git a/AGHTechDoc.md b/AGHTechDoc.md
index a5ad69ad..41413046 100644
--- a/AGHTechDoc.md
+++ b/AGHTechDoc.md
@@ -1439,7 +1439,7 @@ Request:
 
 	{
 		"name": "..."
-		"url": "..."
+		"url": "..." // URL or an absolute file path
 		"whitelist": true
 	}
 
diff --git a/home/control_filtering.go b/home/control_filtering.go
index 837d2a45..4c820ec1 100644
--- a/home/control_filtering.go
+++ b/home/control_filtering.go
@@ -8,15 +8,22 @@ import (
 	"net/http"
 	"net/url"
 	"os"
+	"path/filepath"
 	"strings"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/util"
 	"github.com/AdguardTeam/golibs/log"
 	"github.com/miekg/dns"
 )
 
-// IsValidURL - return TRUE if URL is valid
+// IsValidURL - return TRUE if URL or file path is valid
 func IsValidURL(rawurl string) bool {
+	if filepath.IsAbs(rawurl) {
+		// this is a file path
+		return util.FileExists(rawurl)
+	}
+
 	url, err := url.ParseRequestURI(rawurl)
 	if err != nil {
 		return false //Couldn't even parse the rawurl
@@ -42,7 +49,7 @@ func (f *Filtering) handleFilteringAddURL(w http.ResponseWriter, r *http.Request
 	}
 
 	if !IsValidURL(fj.URL) {
-		http.Error(w, "Invalid URL", http.StatusBadRequest)
+		http.Error(w, "Invalid URL or file path", http.StatusBadRequest)
 		return
 	}
 
@@ -100,11 +107,6 @@ func (f *Filtering) handleFilteringRemoveURL(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
-	if !IsValidURL(req.URL) {
-		http.Error(w, "URL parameter is not valid request URL", http.StatusBadRequest)
-		return
-	}
-
 	// go through each element and delete if url matches
 	config.Lock()
 	newFilters := []filter{}
@@ -154,7 +156,7 @@ func (f *Filtering) handleFilteringSetURL(w http.ResponseWriter, r *http.Request
 	}
 
 	if !IsValidURL(fj.URL) {
-		http.Error(w, "invalid URL", http.StatusBadRequest)
+		http.Error(w, "invalid URL or file path", http.StatusBadRequest)
 		return
 	}
 
diff --git a/home/filter.go b/home/filter.go
index 494d98e5..7b632747 100644
--- a/home/filter.go
+++ b/home/filter.go
@@ -69,7 +69,7 @@ func defaultFilters() []filter {
 // field ordering is important -- yaml fields will mirror ordering from here
 type filter struct {
 	Enabled     bool
-	URL         string
+	URL         string    // URL or a file path
 	Name        string    `yaml:"name"`
 	RulesCount  int       `yaml:"-"`
 	LastUpdated time.Time `yaml:"-"`
@@ -500,6 +500,7 @@ func (f *Filtering) update(filter *filter) (bool, error) {
 	return b, err
 }
 
+// nolint(gocyclo)
 func (f *Filtering) updateIntl(filter *filter) (bool, error) {
 	log.Tracef("Downloading update for filter %d from %s", filter.ID, filter.URL)
 
@@ -514,18 +515,29 @@ func (f *Filtering) updateIntl(filter *filter) (bool, error) {
 		}
 	}()
 
-	resp, err := Context.client.Get(filter.URL)
-	if resp != nil && resp.Body != nil {
-		defer resp.Body.Close()
-	}
-	if err != nil {
-		log.Printf("Couldn't request filter from URL %s, skipping: %s", filter.URL, err)
-		return false, err
-	}
+	var reader io.Reader
+	if filepath.IsAbs(filter.URL) {
+		f, err := os.Open(filter.URL)
+		if err != nil {
+			return false, fmt.Errorf("open file: %s", err)
+		}
+		defer f.Close()
+		reader = f
+	} else {
+		resp, err := Context.client.Get(filter.URL)
+		if resp != nil && resp.Body != nil {
+			defer resp.Body.Close()
+		}
+		if err != nil {
+			log.Printf("Couldn't request filter from URL %s, skipping: %s", filter.URL, err)
+			return false, err
+		}
 
-	if resp.StatusCode != 200 {
-		log.Printf("Got status code %d from URL %s, skipping", resp.StatusCode, filter.URL)
-		return false, fmt.Errorf("got status code != 200: %d", resp.StatusCode)
+		if resp.StatusCode != 200 {
+			log.Printf("Got status code %d from URL %s, skipping", resp.StatusCode, filter.URL)
+			return false, fmt.Errorf("got status code != 200: %d", resp.StatusCode)
+		}
+		reader = resp.Body
 	}
 
 	htmlTest := true
@@ -534,7 +546,7 @@ func (f *Filtering) updateIntl(filter *filter) (bool, error) {
 	buf := make([]byte, 64*1024)
 	total := 0
 	for {
-		n, err := resp.Body.Read(buf)
+		n, err := reader.Read(buf)
 		total += n
 
 		if htmlTest {
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 64fb5e5d..9653af5a 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -507,7 +507,7 @@ paths:
             tags:
                 - filtering
             operationId: filteringAddURL
-            summary: 'Add filter URL'
+            summary: 'Add filter URL or an absolute file path'
             consumes:
             - application/json
             parameters:
@@ -1495,7 +1495,7 @@ definitions:
             name:
                 type: "string"
             url:
-                description: "URL containing filtering rules"
+                description: "URL or an absolute path to the file containing filtering rules"
                 type: "string"
                 example: "https://filters.adtidy.org/windows/filters/15.txt"
     RemoveUrlRequest: