diff --git a/src/knot/ctl/commands.c b/src/knot/ctl/commands.c
index 1f53014f7f65dbd52dbbcb6edfba8527e25656c0..c95b6e8358309fca8201dfbc9a87f255b4befe4f 100644
--- a/src/knot/ctl/commands.c
+++ b/src/knot/ctl/commands.c
@@ -16,6 +16,7 @@
 
 #include <string.h>
 #include <unistd.h>
+#include <sys/stat.h>
 #include <sys/time.h>
 #include <time.h>
 #include <urcu.h>
@@ -513,6 +514,19 @@ static int zone_retransfer(zone_t *zone, _unused_ ctl_args_t *args)
 	return schedule_trigger(zone, args, ZONE_EVENT_REFRESH, true);
 }
 
+static void common_failure(_unused_ ctl_args_t *args, int err, const char *msg)
+{
+	if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
+		log_ctl_error("%s", msg);
+	} else {
+		log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE], "%s", msg);
+	}
+
+	/* Warning: zone name in the control command params discarded here. */
+	args->data[KNOT_CTL_IDX_ZONE] = NULL;
+	ctl_send_error(args, knot_strerror(err));
+}
+
 static int zone_notify(zone_t *zone, _unused_ ctl_args_t *args)
 {
 	zone_notifailed_clear(zone);
@@ -521,17 +535,6 @@ static int zone_notify(zone_t *zone, _unused_ ctl_args_t *args)
 
 static int zone_flush(zone_t *zone, ctl_args_t *args)
 {
-	if (MATCH_AND_FILTER(args, CTL_FILTER_FLUSH_OUTDIR)) {
-		rcu_read_lock();
-		int ret = zone_dump_to_dir(conf(), zone, args->data[KNOT_CTL_IDX_DATA]);
-		rcu_read_unlock();
-		if (ret != KNOT_EOK) {
-			log_zone_warning(zone->name, "failed to update zone file (%s)",
-			                 knot_strerror(ret));
-		}
-		return ret;
-	}
-
 	zone_set_flag(zone, ZONE_USER_FLUSH);
 	if (ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE)) {
 		zone_set_flag(zone, ZONE_FORCE_FLUSH);
@@ -540,6 +543,44 @@ static int zone_flush(zone_t *zone, ctl_args_t *args)
 	return schedule_trigger(zone, args, ZONE_EVENT_FLUSH, true);
 }
 
+static int zone_flush_outdir(zone_t *zone, ctl_args_t *args)
+{
+	rcu_read_lock();
+	int ret = zone_dump_to_dir(conf(), zone, args->data[KNOT_CTL_IDX_DATA]);
+	rcu_read_unlock();
+
+	if (ret != KNOT_EOK) {
+		log_zone_warning(zone->name, "failed to update zone file (%s)",
+		                 knot_strerror(ret));
+	}
+	return ret;
+}
+
+static int zones_apply_flush(ctl_args_t *args)
+{
+	if (MATCH_AND_FILTER(args, CTL_FILTER_FLUSH_OUTDIR)) {
+		const char *dir = args->data[KNOT_CTL_IDX_DATA];
+		if (dir == NULL) {
+			char *msg = "flush, output directory not specified";
+			common_failure(args, KNOT_ENOPARAM, msg);
+			return KNOT_CTL_EZONE;
+		}
+		int ret = make_path(dir, S_IRUSR | S_IWUSR | S_IXUSR |
+		                         S_IRGRP | S_IWGRP | S_IXGRP);
+		if (ret != KNOT_EOK) {
+			char *msg = sprintf_alloc("flush, failed to create output directory '%s' (%s)",
+			                          dir, knot_strerror(ret));
+			common_failure(args, ret, msg);
+			free(msg);
+			return KNOT_CTL_EZONE;
+		}
+
+		return zones_apply(args, zone_flush_outdir);
+	}
+
+	return zones_apply(args, zone_flush);
+}
+
 static void report_insufficient_backup(ctl_args_t *args, zone_backup_ctx_t *ctx)
 {
 	const char *msg = "missing in backup:%s";
@@ -578,6 +619,9 @@ static int init_backup(ctl_args_t *args, bool restore_mode)
 	const char *db_storage = conf_str(&db_storage_val);
 
 	const char *backup_dir = args->data[KNOT_CTL_IDX_DATA];
+	if (backup_dir == NULL) {
+		return KNOT_ENOPARAM;
+	}
 
 	if (same_path(backup_dir, db_storage)) {
 		char *msg = sprintf_alloc("%s the database storage directory not allowed",
@@ -615,8 +659,7 @@ static int init_backup(ctl_args_t *args, bool restore_mode)
 	// The present timer db size is not up-to-date, use the maximum one.
 	conf_val_t timer_db_size = conf_db_param(conf(), C_TIMER_DB_MAX_SIZE);
 
-	int ret = zone_backup_init(restore_mode, filters, forced,
-	                           args->data[KNOT_CTL_IDX_DATA],
+	int ret = zone_backup_init(restore_mode, filters, forced, backup_dir,
 	                           knot_lmdb_copy_size(&args->server->kaspdb),
 	                           conf_int(&timer_db_size),
 	                           knot_lmdb_copy_size(&args->server->journaldb),
@@ -719,18 +762,8 @@ static int zones_apply_backup(ctl_args_t *args, bool restore_mode)
 		char *msg = sprintf_alloc("%s init failed (%s)",
 		                          restore_mode ? "restore" : "backup",
 		                          knot_strerror(ret));
-
-		if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
-			log_ctl_error("%s", msg);
-		} else {
-			log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE],
-			                       "%s", msg);
-		}
+		common_failure(args, ret, msg);
 		free (msg);
-
-		/* Warning: zone name in the control command params discarded here. */
-		args->data[KNOT_CTL_IDX_ZONE] = NULL;
-		ctl_send_error(args, knot_strerror(ret));
 		return KNOT_CTL_EZONE;
 	}
 
@@ -1869,7 +1902,7 @@ static int ctl_zone(ctl_args_t *args, ctl_cmd_t cmd)
 	case CTL_ZONE_NOTIFY:
 		return zones_apply(args, zone_notify);
 	case CTL_ZONE_FLUSH:
-		return zones_apply(args, zone_flush);
+		return zones_apply_flush(args);
 	case CTL_ZONE_BACKUP:
 		return zones_apply_backup(args, false);
 	case CTL_ZONE_RESTORE: