diff --git a/NEWS b/NEWS
index 53d39a194bd0415b0fc1af99b0c5e5757f88c6c1..7f4929f4eac389cc283a02edfa40859e540de59c 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,10 @@
 Knot Resolver 6.0.9 (2024-mm-dd)
 ================================
 
+Incompatible changes
+--------------------
+- -f/--forks is removed (#631, !1602)
+
 Improvements
 ------------
 
diff --git a/daemon/engine.h b/daemon/engine.h
index e25590a833f2a894d948a5724823f3874b08260e..4699b77dc77be24388eaf90eb8e323e27696112c 100644
--- a/daemon/engine.h
+++ b/daemon/engine.h
@@ -93,7 +93,6 @@ struct args {
 	addr_array_t addrs, addrs_tls;
 	flagged_fd_array_t fds;
 	int control_fd;
-	int forks;
 	config_array_t config;
 	const char *rundir;
 	bool interactive;
diff --git a/daemon/lua/kres-gen-33.lua b/daemon/lua/kres-gen-33.lua
index 40a03453bd79bca67ff7786dc9efdcccdf3e2df4..1a0857418885da16e186ee62038ccbd4ec86e078 100644
--- a/daemon/lua/kres-gen-33.lua
+++ b/daemon/lua/kres-gen-33.lua
@@ -543,7 +543,6 @@ struct args {
 	addr_array_t addrs_tls;
 	flagged_fd_array_t fds;
 	int control_fd;
-	int forks;
 	config_array_t config;
 	const char *rundir;
 	_Bool interactive;
diff --git a/daemon/main.c b/daemon/main.c
index 44b8ae4c1b745c19ae91f6c9119b895d7738947f..59e4866edb5f0806a2467405c615dd0a460d10b3 100644
--- a/daemon/main.c
+++ b/daemon/main.c
@@ -136,24 +136,6 @@ static void sigbus_handler(int sig, siginfo_t *siginfo, void *ptr)
  * Server operation.
  */
 
-static int fork_workers(int forks)
-{
-	/* Fork subprocesses if requested */
-	while (--forks > 0) {
-		int pid = fork();
-		if (pid < 0) {
-			perror("[system] fork");
-			return kr_error(errno);
-		}
-
-		/* Forked process */
-		if (pid == 0) {
-			return forks;
-		}
-	}
-	return 0;
-}
-
 static void help(int argc, char *argv[])
 {
 	printf("Usage: %s [parameters] [rundir]\n", argv[0]);
@@ -172,7 +154,7 @@ static void help(int argc, char *argv[])
 }
 
 /** \return exit code for main()  */
-static int run_worker(uv_loop_t *loop, bool leader, struct args *args)
+static int run_worker(uv_loop_t *loop, struct args *args)
 {
 	/* Only some kinds of stdin work with uv_pipe_t.
 	 * Otherwise we would abort() from libuv e.g. with </dev/null */
@@ -225,7 +207,6 @@ static void args_init(struct args *args)
 {
 	memset(args, 0, sizeof(struct args));
 	/* Zeroed arrays are OK. */
-	args->forks = 1;
 	args->control_fd = -1;
 	args->interactive = true;
 	args->quiet = false;
@@ -253,7 +234,6 @@ static int parse_args(int argc, char **argv, struct args *args)
 		{"addr",       required_argument, 0, 'a'},
 		{"tls",        required_argument, 0, 't'},
 		{"config",     required_argument, 0, 'c'},
-		{"forks",      required_argument, 0, 'f'},
 		{"noninteractive",   no_argument, 0, 'n'},
 		{"verbose",          no_argument, 0, 'v'},
 		{"quiet",            no_argument, 0, 'q'},
@@ -277,20 +257,6 @@ static int parse_args(int argc, char **argv, struct args *args)
 			kr_require(optarg);
 			array_push(args->config, optarg);
 			break;
-		case 'f':
-			kr_require(optarg);
-			args->forks = strtol(optarg, NULL, 10);
-			if (args->forks == 1) {
-				kr_log_deprecate(SYSTEM, "use --noninteractive instead of --forks=1\n");
-			} else {
-				kr_log_deprecate(SYSTEM, "support for running multiple --forks will be removed\n");
-			}
-			if (args->forks <= 0) {
-				kr_log_error(SYSTEM, "error '-f' requires a positive"
-						" number, not '%s'\n", optarg);
-				return EXIT_FAILURE;
-			}
-			/* fall through */
 		case 'n':
 			args->interactive = false;
 			break;
@@ -514,12 +480,6 @@ int main(int argc, char **argv)
 				(long)rlim.rlim_cur);
 	}
 
-	/* Fork subprocesses if requested */
-	int fork_id = fork_workers(the_args->forks);
-	if (fork_id < 0) {
-		return EXIT_FAILURE;
-	}
-
 	kr_crypto_init();
 
 	network_init(uv_default_loop(), TCP_BACKLOG_DEFAULT);
@@ -633,7 +593,7 @@ int main(int argc, char **argv)
 	kr_rules_commit(true);
 
 	/* Run the event loop */
-	ret = run_worker(loop, fork_id == 0, the_args);
+	ret = run_worker(loop, the_args);
 
 cleanup:/* Cleanup. */
 	network_unregister();
diff --git a/daemon/worker.c b/daemon/worker.c
index caf11e55f4ee4483db47ea90716e42f4675241a2..83915b011572b5514a5e6691fcc1b35a66555555 100644
--- a/daemon/worker.c
+++ b/daemon/worker.c
@@ -2291,9 +2291,6 @@ int worker_init(void)
 	uv_loop_t *loop = uv_default_loop();
 	the_worker->loop = loop;
 
-	static const int worker_count = 1;
-	the_worker->count = worker_count;
-
 	/* Register table for worker per-request variables */
 	struct lua_State *L = the_engine->L;
 	lua_newtable(L);
@@ -2329,8 +2326,6 @@ int worker_init(void)
 
 	lua_pushnumber(L, pid);
 	lua_setfield(L, -2, "pid");
-	lua_pushnumber(L, worker_count);
-	lua_setfield(L, -2, "count");
 
 	char cwd[PATH_MAX];
 	get_workdir(cwd, sizeof(cwd));
diff --git a/doc/kresd.8.in b/doc/kresd.8.in
index 29d4ed9b4f821623ac3043965d335e79de5ad065..33e4035e4bb4f92c864c03fb2a9e13b9db96e8d1 100644
--- a/doc/kresd.8.in
+++ b/doc/kresd.8.in
@@ -90,17 +90,6 @@ Option may be passed multiple times to listen on more file descriptors.
 Set the config file with settings for kresd to read instead of reading the
 file at the default location (\fIconfig\fR).
 .TP
-.B \-f\fI N\fR, \fB\-\-forks=\fI<N>
-This option is deprecated since 5.0.0!
-
-With this option, the daemon is started in non-interactive mode and instead creates a
-UNIX socket in \fIrundir\fR that the operator can connect to for interactive session.
-A number greater than 1 forks the daemon N times, all forks will bind to same addresses
-and the kernel will load-balance between them on Linux with \fISO_REUSEPORT\fR support.
-
-If you want multiple concurrent processes supervised in this way,
-they should be supervised independently (see \fBkresd.systemd(7)\fR).
-.TP
 .B \-n\fR, \fB\-\-noninteractive
 Daemon will refrain from entering into read-eval-print loop for stdin+stdout.
 .TP