diff --git a/lib/cache/cdb_lmdb.c b/lib/cache/cdb_lmdb.c
index 64298416d28ad8c7034680008ad277371e64a166..e458e496865c277d2c9f5fa509145d7b59468d8d 100644
--- a/lib/cache/cdb_lmdb.c
+++ b/lib/cache/cdb_lmdb.c
@@ -395,17 +395,6 @@ static int cdb_init(knot_db_t **db, struct kr_cdb_stats *stats,
 		return kr_error(EINVAL);
 	}
 
-	/* Clear stale lockfiles. */
-	auto_free char *lockfile = kr_strcatdup(2, opts->path, "/.cachelock");
-	if (lockfile) {
-		if (unlink(lockfile) == 0) {
-			kr_log_info("[cache] cleared stale lockfile '%s'\n", lockfile);
-		} else if (errno != ENOENT) {
-			kr_log_info("[cache] failed to clear stale lockfile '%s': %s\n", lockfile,
-				    strerror(errno));
-		}
-	}
-
 	/* Open the database. */
 	struct lmdb_env *env = calloc(1, sizeof(*env));
 	if (!env) {
@@ -489,6 +478,46 @@ static int cdb_check_health(knot_db_t *db, struct kr_cdb_stats *stats)
 	return refresh_mapsize(env);
 }
 
+/** Obtain exclusive (advisory) lock by creating a file, returning FD or negative kr_error().
+ * The lock is auto-released by OS in case the process finishes in any way (file remains). */
+static int lockfile_get(const char *path)
+{
+	assert(path);
+	const int fd = open(path, O_CREAT|O_RDWR, S_IRUSR);
+	if (fd < 0)
+		return kr_error(errno);
+
+	struct flock lock_info;
+	memset(&lock_info, 0, sizeof(lock_info));
+	lock_info.l_type = F_WRLCK;
+	lock_info.l_whence = SEEK_SET;
+	lock_info.l_start = 0;
+	lock_info.l_len = 1; // it's OK for locks to extend beyond the end of the file
+	int err;
+	do {
+		err = fcntl(fd, F_SETLK, &lock_info);
+	} while (err == -1 && errno == EINTR);
+	if (err) {
+		close(fd);
+		return kr_error(errno);
+	}
+	return fd;
+}
+
+/** Release and remove lockfile created by lockfile_get().  Return kr_error(). */
+static int lockfile_release(const char *path, int fd)
+{
+	assert(path && fd > 0); // fd == 0 is surely a mistake, in our case at least
+	int err = 0;
+	// To avoid a race, we unlink it first.
+	if (unlink(path))
+		err = kr_error(errno);
+	// And we try to close it even on error.
+	if (close(fd) && !err)
+		err = kr_error(errno);
+	return err;
+}
+
 static int cdb_clear(knot_db_t *db, struct kr_cdb_stats *stats)
 {
 	struct lmdb_env *env = db;
@@ -528,36 +557,37 @@ static int cdb_clear(knot_db_t *db, struct kr_cdb_stats *stats)
 	}
 
 	/* Find if we get a lock on lockfile. */
-	ret = open(lockfile, O_CREAT|O_EXCL|O_RDONLY, S_IRUSR);
-	if (ret == -1) {
+	const int lockfile_fd = lockfile_get(lockfile);
+	if (lockfile_fd < 0) {
 		kr_log_error("[cache] clearing failed to get ./.cachelock (%s); retry later\n",
-				strerror(errno));
+				kr_strerror(lockfile_fd));
 		/* As we're out of space (almost certainly - mdb_drop didn't work),
 		 * we will retry on the next failing write operation. */
 		return kr_error(EAGAIN);
 	}
-	close(ret);
 
 	/* We acquired lockfile.  Now find whether *.mdb are what we have open now.
 	 * If they are not we don't want to remove them; most likely they have been
 	 * cleaned by another instance. */
 	ret = cdb_check_health(db, stats);
 	if (ret != 0) {
-		unlink(lockfile);
-		if (ret == 1) { // file changed and reopened successfuly
-			return kr_ok();
-		} else { // some other error
-			kr_error(ret);
-		}
+		if (ret == 1) // file changed and reopened successfuly
+			ret = kr_ok();
+		// else pass some other error
+	} else {
+		kr_log_verbose("[cache] clear: identical files, unlinking\n");
+		// coverity[toctou]
+		unlink(env->mdb_data_path);
+		unlink(mdb_lockfile);
+		ret = reopen_env(env, stats, env->mapsize);
 	}
 
-	kr_log_verbose("[cache] clear: identical files, unlinking\n");
-	// coverity[toctou]
-	unlink(env->mdb_data_path);
-	unlink(mdb_lockfile);
-	ret = reopen_env(env, stats, env->mapsize);
 	/* Environment updated, release lockfile. */
-	unlink(lockfile);
+	int lrerr = lockfile_release(lockfile, lockfile_fd);
+	if (lrerr) {
+		kr_log_error("[cache] failed to release ./.cachelock: %s\n",
+				kr_strerror(lrerr));
+	}
 	return ret;
 }