diff --git a/modules/policy/README.rst b/modules/policy/README.rst
index 6d22b9bc008c6ee31f4b223936ccb3b90a8991cd..1d87b48878f4e83f19976bf9e36f4aeccc419b47 100644
--- a/modules/policy/README.rst
+++ b/modules/policy/README.rst
@@ -40,8 +40,12 @@ A *filter* selects which queries will be affected by specified Actions_. There a
 
    .. code-block:: lua
 
-      policy.suffix(policy.DENY, policy.todnames({'example.com', 'example.net'}))
+      policy.add(policy.suffix(policy.DENY, policy.todnames({'example.com', 'example.net'})))
 
+.. function:: domains(action, domain_table)
+
+   Like :func:`policy.suffix` match, but the queried name must match exactly, not just its suffix.
+   
 .. function:: suffix_common(action, suffix_table[, common_suffix])
 
   :param action: action if the pattern matches query name
@@ -168,13 +172,13 @@ Following actions stop the policy matching on the query, i.e. other rules are no
 
       -- policy to change IPv4 address and TTL for example.com
       policy.add(
-          policy.suffix(
+          policy.domains(
               policy.ANSWER(
                   { [kres.type.A] = { rdata=kres.str2ip('192.0.2.7'), ttl=300 } }
               ), { todname('example.com') }))
       -- policy to generate two TXT records (specified in binary format) for example.net
       policy.add(
-          policy.suffix(
+          policy.domains(
               policy.ANSWER(
                   { [kres.type.TXT] = { rdata={'\005first', '\006second'}, ttl=5 } }
               ), { todname('example.net') }))
diff --git a/modules/policy/policy.lua b/modules/policy/policy.lua
index 07ba88b600ba4432d72a30f4a4ea5ebd11cf1311..71d277b145db4f0d07558f3a7e343a7b7c5330ef 100644
--- a/modules/policy/policy.lua
+++ b/modules/policy/policy.lua
@@ -344,7 +344,20 @@ function policy.all(action)
 	return function(_, _) return action end
 end
 
--- Requests which QNAME matches given zone list (i.e. suffix match)
+-- Requests whose QNAME is exactly the provided domain
+function policy.domains(action, dname_list)
+	return function(_, query)
+		local qname = query:name()
+		for _, dname in ipairs(dname_list) do
+			if ffi.C.knot_dname_is_equal(qname, dname) then
+				return action
+			end
+		end
+		return nil
+	end
+end
+
+-- Requests whose QNAME matches given zone list (i.e. suffix match)
 function policy.suffix(action, zone_list)
 	local AC = require('ahocorasick')
 	local tree = AC.create(zone_list)
diff --git a/modules/policy/test.integr/kresd_config.j2 b/modules/policy/test.integr/kresd_config.j2
index 4ba83dd6a574a3d32e87ee0ceb3855de62a7884e..668c792356b824c4a9a5d69f83e6f9ac9962d388 100644
--- a/modules/policy/test.integr/kresd_config.j2
+++ b/modules/policy/test.integr/kresd_config.j2
@@ -1,5 +1,6 @@
 -- SPDX-License-Identifier: GPL-3.0-or-later
 {% raw %}
+policy.add(policy.domains(policy.DENY, {todname('example.com')}))
 policy.add(policy.suffix(policy.REFUSE, {todname('refuse.example.com')}))
 
 -- make sure DNSSEC is turned off for tests
diff --git a/modules/policy/test.integr/refuse.rpl b/modules/policy/test.integr/refuse.rpl
index ee8daa4f9a42b6c3a69a332a180700f4d0b890fc..08f994201d6145f94f6c124cfcefd305a1b93803 100644
--- a/modules/policy/test.integr/refuse.rpl
+++ b/modules/policy/test.integr/refuse.rpl
@@ -22,4 +22,23 @@ www.refuse.example.com. IN A
 SECTION ANSWER
 ENTRY_END
 
+STEP 30 QUERY
+ENTRY_BEGIN
+REPLY RD AD
+SECTION QUESTION
+example.com. IN A
+ENTRY_END
+
+STEP 40 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all answer
+REPLY QR RD AA RA NXDOMAIN
+SECTION QUESTION
+example.com. IN A
+SECTION ANSWER
+SECTION AUTHORITY
+example.com. 10800 IN SOA example.com. nobody.invalid. 1 3600 1200 604800 10800
+ENTRY_END
+
+
 SCENARIO_END