diff --git a/doc/operation.rst b/doc/operation.rst
index 2202b42ce70bb65182e807b476b5c9818aa99748..57478612c482bcc57e39aaa3a3bc8621b34712cd 100644
--- a/doc/operation.rst
+++ b/doc/operation.rst
@@ -757,9 +757,10 @@ For the ZSK side (i.e. the operator of the DNS server), the pre-requisites are:
 
 For the KSK side (i.e. the operator of the KSK signer), the pre-requisites are:
 
-- Knot configuration equal to the ZSK side (at least relevant parts of corresponding
-  :ref:`policy <Policy section>`, :ref:`zone <Zone section>`, and :ref:`template <Template section>`
-  sections must be identical)
+- a properly configured :ref:`DNSSEC policy <Policy section>`
+- :ref:`manual <policy_manual>` set to `on`
+- :ref:`offline-ksk <policy_offline-ksk>` set to `on`
+- :ref:`dnskey-ttl <policy_dnskey-ttl>` equal to ZSK side
 - a KASP DB with the KSK(s)
 
 Generating and signing future ZSKs
diff --git a/src/knot/dnssec/rrset-sign.c b/src/knot/dnssec/rrset-sign.c
index ccaa23c37a6b265fda36411a416f40ef4c3cefa1..7476deb089a82a180915cb402aa033c031d8e408 100644
--- a/src/knot/dnssec/rrset-sign.c
+++ b/src/knot/dnssec/rrset-sign.c
@@ -364,6 +364,7 @@ int knot_check_signature(const knot_rrset_t *covered,
                     const dnssec_key_t *key,
                     dnssec_sign_ctx_t *sign_ctx,
                     const kdnssec_ctx_t *dnssec_ctx,
+                    knot_timediff_t refresh,
                     bool skip_crypto)
 {
 	if (knot_rrset_empty(covered) || knot_rrset_empty(rrsigs) || !key ||
@@ -374,9 +375,6 @@ int knot_check_signature(const knot_rrset_t *covered,
 	knot_rdata_t *rrsig = knot_rdataset_at(&rrsigs->rrs, pos);
 	assert(rrsig);
 
-	// consider signature invalid even if validity ends in refresh - in order to refresh it soon enough
-	knot_timediff_t refresh = dnssec_ctx->policy->rrsig_refresh_before +
-	                          dnssec_ctx->policy->rrsig_prerefresh;
 	if (!(dnssec_ctx->policy->unsafe & UNSAFE_EXPIRED) &&
 	    is_expired_signature(rrsig, dnssec_ctx->now, refresh)) {
 		return DNSSEC_INVALID_SIGNATURE;
diff --git a/src/knot/dnssec/rrset-sign.h b/src/knot/dnssec/rrset-sign.h
index 7114cb184cd021e98ae8975ccd1a270caaeb1009..b5ef5afd518f24a0228346794c2e19677266c574 100644
--- a/src/knot/dnssec/rrset-sign.h
+++ b/src/knot/dnssec/rrset-sign.h
@@ -103,6 +103,7 @@ int knot_synth_rrsig(uint16_t type, const knot_rdataset_t *rrsig_rrs,
  * \param key         Signing key.
  * \param sign_ctx    Signing context.
  * \param dnssec_ctx  DNSSEC context.
+ * \param refresh     Consider RRSIG expired when gonna expire this soon.
  * \param skip_crypto All RRSIGs in this node have been verified, just check validity.
  *
  * \return Error code, KNOT_EOK if successful and the signature is valid.
@@ -113,4 +114,5 @@ int knot_check_signature(const knot_rrset_t *covered,
                          const dnssec_key_t *key,
                          dnssec_sign_ctx_t *sign_ctx,
                          const kdnssec_ctx_t *dnssec_ctx,
+                         knot_timediff_t refresh,
                          bool skip_crypto);
diff --git a/src/knot/dnssec/zone-sign.c b/src/knot/dnssec/zone-sign.c
index a945abad4e05197bc4c770a14605805c8147f42d..781814c8d8dd39109e11e3a67bb245f34b3a0841 100644
--- a/src/knot/dnssec/zone-sign.c
+++ b/src/knot/dnssec/zone-sign.c
@@ -98,6 +98,7 @@ static bool apex_dnssec_changed(zone_update_t *update)
  * \param ctx             Signing context.
  * \param policy          DNSSEC policy.
  * \param skip_crypto     All RRSIGs in this node have been verified, just check validity.
+ * \param refresh         Consider RRSIG expired when gonna expire this soon.
  * \param found_invalid   Out: some matching but expired%invalid RRSIG found.
  * \param at              Out: RRSIG position.
  *
@@ -108,6 +109,7 @@ static bool valid_signature_exists(const knot_rrset_t *covered,
 				   const dnssec_key_t *key,
 				   dnssec_sign_ctx_t *ctx,
 				   const kdnssec_ctx_t *dnssec_ctx,
+				   knot_timediff_t refresh,
 				   bool skip_crypto,
 				   int *found_invalid,
 				   uint16_t *at)
@@ -134,7 +136,7 @@ static bool valid_signature_exists(const knot_rrset_t *covered,
 		}
 
 		int ret = knot_check_signature(covered, rrsigs, i, key, ctx,
-					       dnssec_ctx, skip_crypto);
+					       dnssec_ctx, refresh, skip_crypto);
 		if (ret == KNOT_EOK) {
 			if (at != NULL) {
 				*at = i;
@@ -173,9 +175,12 @@ static bool all_signatures_exist(const knot_rrset_t *covered,
 			continue;
 		}
 
+		knot_timediff_t refresh = sign_ctx->dnssec_ctx->policy->rrsig_refresh_before +
+		                          sign_ctx->dnssec_ctx->policy->rrsig_prerefresh;
 		if (!valid_signature_exists(covered, rrsigs, key->key,
 		                            sign_ctx->sign_ctxs[i],
-		                            sign_ctx->dnssec_ctx, false, NULL, NULL)) {
+		                            sign_ctx->dnssec_ctx, refresh,
+		                            false, NULL, NULL)) {
 			return false;
 		}
 	}
@@ -271,8 +276,10 @@ static int add_missing_rrsigs(const knot_rrset_t *covered,
 		}
 
 		uint16_t valid_at;
+		knot_timediff_t refresh = sign_ctx->dnssec_ctx->policy->rrsig_refresh_before +
+		                          sign_ctx->dnssec_ctx->policy->rrsig_prerefresh;
 		if (valid_signature_exists(covered, rrsigs, key->key, sign_ctx->sign_ctxs[i],
-		                           sign_ctx->dnssec_ctx, skip_crypto, NULL, &valid_at)) {
+		                           sign_ctx->dnssec_ctx, refresh, skip_crypto, NULL, &valid_at)) {
 			knot_rdata_t *valid_rr = knot_rdataset_at(&rrsigs->rrs, valid_at);
 			result = knot_rdataset_remove(&to_remove.rrs, valid_rr, NULL);
 			note_earliest_expiration(valid_rr, expires_at);
@@ -341,7 +348,7 @@ int knot_validate_rrsigs(const knot_rrset_t *covered,
 
 		uint16_t valid_at;
 		if (valid_signature_exists(covered, rrsigs, key->key, sign_ctx->sign_ctxs[i],
-		                           sign_ctx->dnssec_ctx, skip_crypto, &ret, &valid_at)) {
+		                           sign_ctx->dnssec_ctx, 0, skip_crypto, &ret, &valid_at)) {
 			valid_exists = true;
 		}
 	}
diff --git a/tests-extra/tests/dnssec/offline_ksk/test.py b/tests-extra/tests/dnssec/offline_ksk/test.py
index 5335334b5a39117875739ea94188f324d0da07ca..07813738a9c3273dde028ff6db70fe127ef463fa 100644
--- a/tests-extra/tests/dnssec/offline_ksk/test.py
+++ b/tests-extra/tests/dnssec/offline_ksk/test.py
@@ -109,6 +109,7 @@ ZONE = "example.com."
 FUTURE = 55
 TICK = 5
 STARTUP = 10
+NONSENSE = 4396
 
 zone = t.zone(ZONE)
 t.link(zone, knot)
@@ -117,13 +118,13 @@ knot.zonefile_sync = 24 * 60 * 60
 
 knot.dnssec(zone).enable = True
 knot.dnssec(zone).manual = True
+knot.dnssec(zone).offline_ksk = True
 knot.dnssec(zone).alg = "ECDSAP384SHA384"
 knot.dnssec(zone).dnskey_ttl = 2
 knot.dnssec(zone).zone_max_ttl = 3
 knot.dnssec(zone).zsk_lifetime = STARTUP + 6 * TICK # see ksk1 lifetime
-knot.dnssec(zone).ksk_lifetime = 300 # this can be possibly left also infinity
+knot.dnssec(zone).ksk_lifetime = NONSENSE
 knot.dnssec(zone).propagation_delay = TICK - 2
-knot.dnssec(zone).offline_ksk = "on"
 knot.dnssec(zone).cds_publish = "rollover"
 knot.dnssec(zone).rrsig_lifetime = 15
 knot.dnssec(zone).rrsig_refresh = 5
@@ -134,7 +135,22 @@ knot.gen_confile()
 
 signer = t.server("knot")
 t.link(zone, signer)
-signer.zones[ZONE].dnssec = knot.zones[ZONE].dnssec
+
+# mandatory options
+signer.dnssec(zone).enable = True
+signer.dnssec(zone).manual = True
+signer.dnssec(zone).offline_ksk = True
+# needed options
+signer.dnssec(zone).alg = "ECDSAP384SHA384"
+signer.dnssec(zone).dnskey_ttl = 2
+# options without any effect
+signer.dnssec(zone).zone_max_ttl = NONSENSE
+signer.dnssec(zone).ksk_lifetime = NONSENSE * 2
+signer.dnssec(zone).propagation_delay = int(NONSENSE / 10)
+signer.dnssec(zone).cds_publish = random.choice(["none", "rollover"])
+signer.dnssec(zone).rrsig_lifetime = 6
+signer.dnssec(zone).rrsig_refresh = 2
+signer.dnssec(zone).rrsig_prerefresh = 1
 
 # needed for keymgr
 signer.gen_confile()