diff --git a/doc/configuration.rst b/doc/configuration.rst
index 60ebbbf4ece8e931b58262327265d4a5eb3c4c13..ebd085e1f362b954735267f2775c894eefb6fe6e 100644
--- a/doc/configuration.rst
+++ b/doc/configuration.rst
@@ -271,8 +271,6 @@ The signing keys can be generated using ISC ``dnssec-keygen`` tool
 only and there are some limitations:
 
 * Keys for all zones must be placed in one directory.
-* Algorithms based on RSA, DSA, and ECDSA are supported, support for
-  GOST algorithm is not finished yet.
 * Only key publication, activation, inactivation, and removal time
   stamps are utilized. Other time stamps are ignored.
 * It is required, that both ``.private`` and ``.key`` files for each
@@ -280,6 +278,7 @@ only and there are some limitations:
   (even for verification only).
 * There cannot be more than eight keys per zone. Keys which are not
   published are not included in this number.
+* Single-Type Signing Scheme is not supported.
 
 Example how to generate NSEC3 capable zone signing key (ZSK) and key
 signing key (KSK) for zone ``example.com``::
diff --git a/src/knot/dnssec/zone-keys.c b/src/knot/dnssec/zone-keys.c
index 94284334edd9457b39e519ab72abe689c95e1808..6988c495c3d2d14b934446e04d2f1c438ab3e1ac 100644
--- a/src/knot/dnssec/zone-keys.c
+++ b/src/knot/dnssec/zone-keys.c
@@ -106,6 +106,65 @@ static void set_zone_key_flags(const knot_key_params_t *params,
 	                 (params->time_delete == 0 || now < params->time_delete);
 }
 
+/*!
+ * \brief Check if there is a functional KSK and ZSK for each used algorithm.
+ */
+static int check_keys_validity(const knot_zone_keys_t *keys)
+{
+	assert(keys);
+
+	const int MAX_ALGORITHMS = KNOT_DNSSEC_ALG_ECDSAP384SHA384 + 1;
+	struct {
+		bool published;
+		bool ksk_enabled;
+		bool zsk_enabled;
+	} algorithms[MAX_ALGORITHMS];
+	memset(algorithms, 0, sizeof(algorithms));
+
+	/* Make a list of used algorithms */
+
+	const knot_zone_key_t *key = NULL;
+	WALK_LIST(key, keys->list) {
+		knot_dnssec_algorithm_t a = key->dnssec_key.algorithm;
+		assert(a < MAX_ALGORITHMS);
+
+		if (key->is_public) {
+			// public key creates a requirement for an algorithm
+			algorithms[a].published = true;
+
+			// need fully enabled ZSK and KSK for each algorithm
+			if (key->is_active) {
+				if (key->is_ksk) {
+					algorithms[a].ksk_enabled = true;
+				} else {
+					algorithms[a].zsk_enabled = true;
+				}
+			}
+		}
+	}
+
+	/* Validate enabled algorithms */
+
+	int enabled_count = 0;
+	for (int a = 0; a < MAX_ALGORITHMS; a++) {
+		if (!algorithms[a].published) {
+			continue;
+		}
+
+		if (!algorithms[a].ksk_enabled || !algorithms[a].zsk_enabled) {
+			return KNOT_DNSSEC_EMISSINGKEYTYPE;
+		}
+
+		enabled_count += 1;
+	}
+
+	if (enabled_count == 0) {
+		return KNOT_DNSSEC_ENOKEY;
+	}
+
+	return KNOT_EOK;
+}
+
 /*!
  * \brief Load zone keys from a key directory.
  *
@@ -230,8 +289,8 @@ int knot_load_zone_keys(const char *keydir_name, const knot_dname_t *zone_name,
 
 	closedir(keydir);
 
-	if (result == KNOT_EOK && EMPTY_LIST(keys->list)) {
-		result = KNOT_DNSSEC_ENOKEY;
+	if (result == KNOT_EOK) {
+		result = check_keys_validity(keys);
 	}
 
 	if (result == KNOT_EOK) {
diff --git a/src/libknot/errcode.c b/src/libknot/errcode.c
index 5d738b472f8ddeda7dda20f1a883766a5b7601f0..cf2d809d3f9b8f4074a69404000172568abcab49 100644
--- a/src/libknot/errcode.c
+++ b/src/libknot/errcode.c
@@ -112,6 +112,7 @@ const error_table_t error_messages[] = {
 	{ KNOT_DNSSEC_ESIGN,                      "Cannot create the signature" },
 	{ KNOT_DNSSEC_ENOKEY,                     "No keys for signing" },
 	{ KNOT_DNSSEC_ENOKEYDIR,                  "Keydir does not exist" },
+	{ KNOT_DNSSEC_EMISSINGKEYTYPE,            "Missing KSK or ZSK for used algorithm" },
 
 	/* NSEC3 errors. */
 	{ KNOT_NSEC3_ECOMPUTE_HASH, "Cannot compute NSEC3 hash" },
diff --git a/src/libknot/errcode.h b/src/libknot/errcode.h
index 838303940eee458452093a9922feaf1f55d2e046..9b64ea18e684274ebe30dc9cf3f8a77ee92f9232 100644
--- a/src/libknot/errcode.h
+++ b/src/libknot/errcode.h
@@ -128,6 +128,7 @@ enum knot_error {
 	KNOT_DNSSEC_ESIGN,
 	KNOT_DNSSEC_ENOKEY,
 	KNOT_DNSSEC_ENOKEYDIR,
+	KNOT_DNSSEC_EMISSINGKEYTYPE,
 
 	/* NSEC3 errors. */
 	KNOT_NSEC3_ECOMPUTE_HASH
diff --git a/tests-extra/tests/dnssec/dnskey_algorithms/data/generate_keys.sh b/tests-extra/tests/dnssec/dnskey_algorithms/data/generate_keys.sh
new file mode 100755
index 0000000000000000000000000000000000000000..cfd52b0ec02b745eeec14c7939865b0b4bd47844
--- /dev/null
+++ b/tests-extra/tests/dnssec/dnskey_algorithms/data/generate_keys.sh
@@ -0,0 +1,68 @@
+#!/bin/sh
+#
+# Run this script every 50 years to refresh the keys. :-)
+#
+
+set -xe
+
+TIME_PAST="-50y"
+TIME_FUTURE="+50y"
+
+keygen()
+{
+	dnssec-keygen -r/dev/urandom $@
+}
+
+dir=$(pwd)
+keydir=$(mktemp -d)
+
+pushd "$keydir"
+
+#
+# valid scenarios
+#
+
+keygen -a RSASHA256 -b 2048 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_ok
+keygen -a RSASHA256 -b 1024 -P $TIME_PAST -A $TIME_PAST rsa_ok
+
+keygen -a RSASHA256 -b 2048 -P $TIME_PAST -A $TIME_PAST rsa_ecdsa_ok
+keygen -a RSASHA256 -b 1024 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_ecdsa_ok
+keygen -a ECDSAP256SHA256 -P $TIME_PAST -A $TIME_PAST rsa_ecdsa_ok
+keygen -a ECDSAP256SHA256 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_ecdsa_ok
+
+keygen -a RSASHA256 -b 2048 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_ecdsa_roll_ok
+keygen -a RSASHA256 -b 1024 -P $TIME_PAST -A $TIME_PAST rsa_ecdsa_roll_ok
+keygen -a ECDSAP256SHA256 -P $TIME_FUTURE -A $TIME_PAST rsa_ecdsa_roll_ok
+
+#
+# invalid scenarios
+#
+
+keygen -a RSASHA256 -b 2048 -P $TIME_FUTURE -A $TIME_FUTURE -f KSK rsa_future_all
+keygen -a RSASHA256 -b 1024 -P $TIME_FUTURE -A $TIME_FUTURE rsa_future_all
+
+keygen -a RSASHA512 -b 2048 -P $TIME_FUTURE -A $TIME_PAST -f KSK rsa_future_publish
+keygen -a RSASHA256 -b 1024 -P $TIME_FUTURE -A $TIME_PAST rsa_future_publish
+
+keygen -a RSASHA512 -b 2048 -P $TIME_PAST -A $TIME_FUTURE -f KSK rsa_future_active
+keygen -a RSASHA256 -b 1024 -P $TIME_PAST -A $TIME_FUTURE rsa_future_active
+
+keygen -a RSASHA256 -b 2048 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_inactive_zsk
+keygen -a RSASHA256 -b 1024 -P $TIME_PAST -A $TIME_FUTURE rsa_inactive_zsk
+
+keygen -a RSASHA256 -b 2048 -P $TIME_FUTURE -A $TIME_FUTURE -f KSK rsa_no_zsk
+keygen -a RSASHA256 -b 1024 -P $TIME_PAST -A $TIME_PAST rsa_no_zsk
+
+keygen -a RSASHA256 -b 2048 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_twice_ksk
+keygen -a RSASHA256 -b 1024 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_twice_ksk
+
+keygen -a RSASHA256 -b 2048 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_ecdsa_ksk_only
+keygen -a RSASHA256 -b 1024 -P $TIME_PAST -A $TIME_PAST rsa_ecdsa_ksk_only
+keygen -a ECDSAP256SHA256 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_ecdsa_ksk_only
+
+keygen -a RSASHA256 -b 2048 -P $TIME_PAST -A $TIME_PAST -f KSK rsa256_rsa512
+keygen -a RSASHA512 -b 2048 -P $TIME_PAST -A $TIME_PAST rsa256_rsa512
+
+tar czf "$dir/keys.tgz" K*.{key,private}
+popd
+rm -rf "$keydir"
diff --git a/tests-extra/tests/dnssec/dnskey_algorithms/data/keys.tgz b/tests-extra/tests/dnssec/dnskey_algorithms/data/keys.tgz
new file mode 100644
index 0000000000000000000000000000000000000000..25aef643afd6f39ff8f00ea290eb7016b4300bac
Binary files /dev/null and b/tests-extra/tests/dnssec/dnskey_algorithms/data/keys.tgz differ
diff --git a/tests-extra/tests/dnssec/dnskey_algorithms/test.py b/tests-extra/tests/dnssec/dnskey_algorithms/test.py
new file mode 100644
index 0000000000000000000000000000000000000000..20b81d4c4c10535f0cfb2c377971067a3fa8c736
--- /dev/null
+++ b/tests-extra/tests/dnssec/dnskey_algorithms/test.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+"""
+Validate ZSK and KSK constrains checks.
+"""
+
+import tarfile
+import os.path
+
+import dnstest.zonefile
+from dnstest.test import Test
+
+TEST_CASES = {
+    # valid cases
+    "rsa_ok":             True,
+    "rsa_ecdsa_ok":       True,
+    "rsa_ecdsa_roll_ok":  True,
+    # invalid cases
+    "rsa_future_all":     False,
+    "rsa_future_publish": False,
+    "rsa_future_active":  False,
+    "rsa_inactive_zsk":   False,
+    "rsa_no_zsk":         False,
+    "rsa_twice_ksk":      False,
+    "rsa_ecdsa_ksk_only": False,
+    "rsa256_rsa512":      False,
+}
+
+t = Test()
+
+knot = t.server("knot")
+knot.dnssec_enable = True
+
+# setup keys
+
+keys_archive = os.path.join(t.data_dir, "keys.tgz")
+with tarfile.open(keys_archive, "r:*") as tar:
+    tar.extractall(knot.keydir)
+
+# setup zones
+
+zones = []
+for zone_name in TEST_CASES:
+    zone = dnstest.zonefile.ZoneFile(t.zones_dir)
+    zone.set_name(zone_name)
+    zone.gen_file(dnssec=False, nsec3=False, records=5)
+    zones.append(zone)
+
+t.link(zones, knot)
+
+t.start()
+
+for zone, valid in TEST_CASES.items():
+    expected_rcode = "NOERROR" if valid else "SERVFAIL"
+    knot.dig(zone, "SOA").check(rcode=expected_rcode)
+
+t.end()