diff --git a/doc/reference.rst b/doc/reference.rst
index 12440c5c6cc1856b6e8ec62f5ede152b49583b6c..7d7d601212336600d5cc2d6cf7921fc1cdf65ca4 100644
--- a/doc/reference.rst
+++ b/doc/reference.rst
@@ -29,6 +29,7 @@ else.
       [ rundir "string"; ]
       [ pidfile "string"; ]
       [ workers integer; ]
+      [ asynchronous-start ( on | off ); ]
       [ user string[.string]; ]
       [ max-conn-idle ( integer | integer(s | m | h | d); ) ]
       [ max-conn-handshake ( integer | integer(s | m | h | d); ) ]
@@ -149,6 +150,32 @@ online CPUs)
       workers 16;
     }
 
+.. _asynchronous-start:
+
+asynchronous-start
+^^^^^^^^^^^^^^^^^^
+
+When asynchronous startup is enabled, server doesn't wait for the zones to be loaded, and
+starts responding immediately lame answers until the zone loads. This may be useful in
+some scenarios, but it is disabled by default.
+
+Default: disabled (wait for zones to be loaded before answering)
+
+::
+
+    system {
+      asynchronous-start off;
+    }
+
+.. _user:
+
+user
+^^^^
+
+System ``user`` or ``user``.``group`` under which the Knot DNS is run
+after starting and binding to interfaces.  Linux capabilities
+(:ref:`Required libraries`) are employed if supported and this
+
 .. _user:
 
 user
diff --git a/man/knot.conf.5.in b/man/knot.conf.5.in
index c1bb55bf2d33364e0007831d04b6399a68a3e763..19aa7f246aafac8bcf6ff4fe8ec1e67d7087092b 100644
--- a/man/knot.conf.5.in
+++ b/man/knot.conf.5.in
@@ -54,6 +54,13 @@ serves as an example of the configuration for knotc(8) and knotd(8).
   # Default: unset (auto-estimates optimal value from the number of online CPUs)
   # background-workers 4;
 
+  # Start server asynchronously
+  # When asynchronous startup is enabled, server doesn't wait for the zones to be loaded, and
+  # starts responding immediately lame answers until the zone loads. This may be useful in
+  # some scenarios, but it is disabled by default.
+  # Default: disabled (wait for zones to be loaded before answering)
+  asynchronous-start off;
+
   # User for running server
   # May also specify user.group (e.g. knot.users)
   # user knot.users;
diff --git a/samples/knot.full.conf b/samples/knot.full.conf
index 91b521eafae1bd155f00d7023bbcb35ca5b04192..e2366412e6bb0c88ba69890447d0e3145dcff15d 100644
--- a/samples/knot.full.conf
+++ b/samples/knot.full.conf
@@ -47,6 +47,14 @@ system {
   # operations (e.g., zone loading, zone signing, XFR zone updates, ...)
   # Default: unset (auto-estimates optimal value from the number of online CPUs)
   # background-workers 4;
+
+  # Start server asynchronously
+  # When asynchronous startup is enabled, server doesn't wait for the zones to be loaded, and
+  # starts responding immediately lame answers until the zone loads. This may be useful in
+  # some scenarios, but it is disabled by default.
+  # Default: disabled (wait for zones to be loaded before answering)
+  asynchronous-start off;
+
   # User for running server
   # May also specify user.group (e.g. knot.users)
   # user knot.users;
diff --git a/src/knot/conf/cf-lex.l b/src/knot/conf/cf-lex.l
index 555c9aa1f4a5ac1305b554fffa26d0946a6623af..d813c818d119914d9c5354e29678f6b456ff1f40 100644
--- a/src/knot/conf/cf-lex.l
+++ b/src/knot/conf/cf-lex.l
@@ -118,6 +118,7 @@ notify-in       { lval.t = yytext; return NOTIFY_IN; }
 notify-out      { lval.t = yytext; return NOTIFY_OUT; }
 workers         { lval.t = yytext; return WORKERS; }
 background-workers { lval.t = yytext; return BACKGROUND_WORKERS; }
+asynchronous-start { lval.t = yytext; return ASYNC_START; }
 user            { lval.t = yytext; return USER; }
 pidfile         { lval.t = yytext; return PIDFILE; }
 rundir          { lval.t = yytext; return RUNDIR; }
diff --git a/src/knot/conf/cf-parse.y b/src/knot/conf/cf-parse.y
index 33797a391cdf4e37cae7c1a7e2d8710c9e7465d4..fd70df4cfaff9fb889fc9f2ba3067573a12eafeb 100644
--- a/src/knot/conf/cf-parse.y
+++ b/src/knot/conf/cf-parse.y
@@ -468,6 +468,7 @@ static void ident_auto(int tok, conf_t *conf, bool val)
 %token <tok> TSIG_ALGO_NAME
 %token <tok> WORKERS
 %token <tok> BACKGROUND_WORKERS
+%token <tok> ASYNC_START
 %token <tok> USER
 %token <tok> RUNDIR
 %token <tok> PIDFILE
@@ -600,6 +601,9 @@ system:
  | system BACKGROUND_WORKERS NUM ';' {
      SET_NUM(new_config->bg_workers, $3.i, 1, 255, "background-workers");
  }
+ | system ASYNC_START BOOL ';' {
+     new_config->async_start = $3.i;
+ }
  | system USER TEXT ';' {
      new_config->uid = new_config->gid = -1; // Invalidate
      char* dpos = strchr($3.t, '.'); // Find uid.gid format
diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h
index 54140321d76c9f714f5fbcd0c095a0dcf4fab24a..e3358d08134260342079a811e52f19bb8f4f9630 100644
--- a/src/knot/conf/conf.h
+++ b/src/knot/conf/conf.h
@@ -214,6 +214,7 @@ typedef struct conf_t {
 	size_t max_udp_payload; /*!< Maximal UDP payload size. */
 	int   workers;  /*!< Number of workers per interface. */
 	int   bg_workers; /*!< Number of background workers. */
+	bool  async_start; /*!< Asynchronous startup. */
 	int   uid;      /*!< Specified user id. */
 	int   gid;      /*!< Specified group id. */
 	int   max_conn_idle; /*!< TCP idle timeout. */
diff --git a/src/knot/main.c b/src/knot/main.c
index 56351f7d843bb5055d019d6d6caeafa0b93be583..9f3f4b279cdc82972a9fdb605a509510fda2e07d 100644
--- a/src/knot/main.c
+++ b/src/knot/main.c
@@ -349,7 +349,7 @@ int main(int argc, char **argv)
 
 	/* Start it up. */
 	log_server_info("Starting server...\n");
-	res = server_start(&server);
+	res = server_start(&server, config->async_start);
 	if (res != KNOT_EOK) {
 		log_server_fatal("Failed to start server: %s.\n",
 		                 knot_strerror(res));
diff --git a/src/knot/server/server.c b/src/knot/server/server.c
index e1620e5b1b2011a0c9cd755c98946bb43932717f..005914b368c4bea8083c76555a866f05d98ac87f 100644
--- a/src/knot/server/server.c
+++ b/src/knot/server/server.c
@@ -381,21 +381,24 @@ static void server_free_handler(iohandler_t *h)
 	memset(h, 0, sizeof(iohandler_t));
 }
 
-int server_start(server_t *s)
+int server_start(server_t *s, bool async)
 {
-	// Check server
+	dbg_server("%s(%p, %d)\n", __func__, s, async);
 	if (s == 0) {
 		return KNOT_EINVAL;
 	}
 
-	dbg_server("server: starting server instance\n");
+	/* Start workers. */
+	worker_pool_start(s->workers);
+
+	/* Wait for enqueued events if not asynchronous. */
+	if (!async) {
+		worker_pool_wait(s->workers);
+	}
 
 	/* Start evsched handler. */
 	dt_start(s->iosched);
 
-	/* Start workers. */
-	worker_pool_start(s->workers);
-
 	/* Start I/O handlers. */
 	int ret = KNOT_EOK;
 	s->state |= ServerRunning;
@@ -405,8 +408,6 @@ int server_start(server_t *s)
 		}
 	}
 
-	dbg_server("server: server started\n");
-
 	return ret;
 }
 
diff --git a/src/knot/server/server.h b/src/knot/server/server.h
index f5a920d9e879d4a999332956f2e62bd2395cce6b..66bb7bcd83a176a1e9c64add92cb213181c629d8 100644
--- a/src/knot/server/server.h
+++ b/src/knot/server/server.h
@@ -144,12 +144,13 @@ void server_deinit(server_t *server);
  * \brief Starts the server.
  *
  * \param server Server structure to be used for operation.
+ * \param async  Don't wait for zones to load if true.
  *
  * \retval KNOT_EOK on success.
  * \retval KNOT_EINVAL on invalid parameters.
  *
  */
-int server_start(server_t *server);
+int server_start(server_t *server, bool async);
 
 /*!
  * \brief Waits for the server to finish.
diff --git a/src/knot/zone/events.c b/src/knot/zone/events.c
index c0b04a974d3bbc3d57feb0c0595e7ab2d52df95c..7315e1c5b95633cc7c1840cf1848ca186f14bbb7 100644
--- a/src/knot/zone/events.c
+++ b/src/knot/zone/events.c
@@ -38,6 +38,10 @@
 #include "knot/nameserver/tsig_ctx.h"
 #include "knot/nameserver/process_answer.h"
 
+/* ------------------------- internal timers -------------------------------- */
+
+#define ZONE_EVENT_IMMEDIATE 1 /* Fast-track to worker queue. */
+
 /* ------------------------- bootstrap timer logic -------------------------- */
 
 #define BOOTSTRAP_RETRY (30) /*!< Interval between AXFR bootstrap retries. */
@@ -795,6 +799,31 @@ void zone_events_schedule_at(zone_t *zone, zone_event_type_t type, time_t time)
 	pthread_mutex_unlock(&events->mx);
 }
 
+void zone_events_enqueue(zone_t *zone, zone_event_type_t type)
+{
+	if (!zone || !valid_event(type)) {
+		return;
+	}
+
+	zone_events_t *events = &zone->events;
+
+	pthread_mutex_lock(&events->mx);
+
+	/* Possible only if no event is running at the moment. */
+	if (!events->running && !events->frozen) {
+		events->running = true;
+		event_set_time(events, type, ZONE_EVENT_IMMEDIATE);
+		worker_pool_assign(events->pool, &events->task);
+		pthread_mutex_unlock(&events->mx);
+		return;
+	}
+
+	pthread_mutex_unlock(&events->mx);
+
+	/* Execute as soon as possible. */
+	zone_events_schedule(zone, type, ZONE_EVENT_NOW);
+}
+
 void zone_events_schedule(zone_t *zone, zone_event_type_t type, unsigned dt)
 {
 	time_t abstime = time(NULL) + dt;
diff --git a/src/knot/zone/events.h b/src/knot/zone/events.h
index 965c929d4bc05eddb1076f13f5967c2aa4f20e65..88c22acf92500cd23cd782f0c0dd0e7430e43a51 100644
--- a/src/knot/zone/events.h
+++ b/src/knot/zone/events.h
@@ -87,6 +87,17 @@ int zone_events_setup(struct zone_t *zone, worker_pool_t *workers,
  */
 void zone_events_deinit(struct zone_t *zone);
 
+/*!
+ * \brief Enqueue event type for asynchronous execution.
+ *
+ * \note This is similar to the scheduling an event for NOW, but it can
+ *       bypass the event scheduler if no event is running at the moment.
+ *
+ * \param zone  Zone to schedule new event for.
+ * \param type  Type of event.
+ */
+void zone_events_enqueue(struct zone_t *zone, zone_event_type_t type);
+
 /*!
  * \brief Schedule new zone event to absolute time.
  *
diff --git a/src/knot/zone/zonedb-load.c b/src/knot/zone/zonedb-load.c
index bd481d290c536dfcea8ed331de4e78e57bf100b4..21ba839150bfe6f59ba435ad313d5c38b67bb055 100644
--- a/src/knot/zone/zonedb-load.c
+++ b/src/knot/zone/zonedb-load.c
@@ -147,10 +147,18 @@ static zone_t *create_zone(conf_zone_t *conf, server_t *server, zone_t *old_zone
 
 	zone_status_t zstatus = zone_file_status(old_zone, conf);
 
+	int result = zone_events_setup(zone, server->workers, &server->sched);
+	if (result != KNOT_EOK) {
+		zone->conf = NULL;
+		zone_free(&zone);
+		return NULL;
+	}
+
 	switch (zstatus) {
 	case ZONE_STATUS_FOUND_NEW:
 	case ZONE_STATUS_FOUND_UPDATED:
-		zone_events_schedule(zone, ZONE_EVENT_RELOAD, ZONE_EVENT_NOW);
+		/* Enqueueing makes the first zone load waitable. */
+		zone_events_enqueue(zone, ZONE_EVENT_RELOAD);
 		break;
 	case ZONE_STATUS_BOOSTRAP:
 		zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
@@ -167,13 +175,6 @@ static zone_t *create_zone(conf_zone_t *conf, server_t *server, zone_t *old_zone
 		assert(0);
 	}
 
-	int result = zone_events_setup(zone, server->workers, &server->sched);
-	if (result != KNOT_EOK) {
-		zone->conf = NULL;
-		zone_free(&zone);
-		return NULL;
-	}
-
 	log_zone_load_info(zone, conf->name, zstatus);
 
 	return zone;