diff --git a/manager/knot_resolver_manager/datamodel/config_schema.py b/manager/knot_resolver_manager/datamodel/config_schema.py
index f61806c201ee811db49d9b854d17faa849566ad1..478b71cbf507a0defa9489b726de6243f4cdc08c 100644
--- a/manager/knot_resolver_manager/datamodel/config_schema.py
+++ b/manager/knot_resolver_manager/datamodel/config_schema.py
@@ -23,7 +23,8 @@ from knot_resolver_manager.datamodel.rpz_schema import RPZSchema
 from knot_resolver_manager.datamodel.slice_schema import SliceSchema
 from knot_resolver_manager.datamodel.static_hints_schema import StaticHintsSchema
 from knot_resolver_manager.datamodel.stub_zone_schema import StubZoneSchema
-from knot_resolver_manager.datamodel.types import AbsoluteDir, IntPositive
+from knot_resolver_manager.datamodel.types import IntPositive
+from knot_resolver_manager.datamodel.types.files import UncheckedPath
 from knot_resolver_manager.datamodel.view_schema import ViewSchema
 from knot_resolver_manager.datamodel.webmgmt_schema import WebmgmtSchema
 from knot_resolver_manager.utils.modeling import ConfigSchema
@@ -112,7 +113,7 @@ class KresConfig(ConfigSchema):
         version: int = 1
         nsid: Optional[str] = None
         hostname: Optional[str] = None
-        rundir: AbsoluteDir = AbsoluteDir("/var/run/knot-resolver")
+        rundir: UncheckedPath = UncheckedPath("/var/run/knot-resolver")
         workers: Union[Literal["auto"], IntPositive] = IntPositive(1)
         max_workers: IntPositive = IntPositive(_default_max_worker_count())
         management: ManagementSchema = ManagementSchema({"unix-socket": "./manager.sock"})
@@ -137,7 +138,7 @@ class KresConfig(ConfigSchema):
 
     nsid: Optional[str]
     hostname: str
-    rundir: AbsoluteDir
+    rundir: UncheckedPath
     workers: IntPositive
     max_workers: IntPositive
     management: ManagementSchema
@@ -201,3 +202,20 @@ class KresConfig(ConfigSchema):
         # it should be removed and relative path used instead as soon as issue
         # https://gitlab.nic.cz/knot/knot-resolver/-/issues/720 is fixed
         return _MAIN_TEMPLATE.render(cfg=self, cwd=os.getcwd())  # pyright: reportUnknownMemberType=false
+
+
+def get_rundir_without_validation(data: Dict[str, Any]) -> UncheckedPath:
+    """
+    Without fully parsing, try to get a rundir from a raw config data. When it fails,
+    attempts a full validation to produce a good error message.
+
+    Used for initial manager startup.
+    """
+
+    if "rundir" in data:
+        rundir = data["rundir"]
+    else:
+        _ = KresConfig(data)  # this should throw a descriptive error
+        assert False
+
+    return UncheckedPath(rundir)
diff --git a/manager/knot_resolver_manager/datamodel/types/files.py b/manager/knot_resolver_manager/datamodel/types/files.py
index 73db8b70dc0b3b57059a5a6a187be775c81cafc0..1672132ee254bcab9194807e62edc0ac3ba0c745 100644
--- a/manager/knot_resolver_manager/datamodel/types/files.py
+++ b/manager/knot_resolver_manager/datamodel/types/files.py
@@ -77,7 +77,7 @@ class Dir(UncheckedPath):
     ) -> None:
         super().__init__(source_value, parents=parents, object_path=object_path)
         if not self._value.is_dir():
-            raise ValueError("path does not point to an existing directory")
+            raise ValueError(f"path '{self._value}' does not point to an existing directory")
 
 
 class AbsoluteDir(Dir):
@@ -124,7 +124,7 @@ class FilePath(UncheckedPath):
         super().__init__(source_value, parents=parents, object_path=object_path)
         p = self._value.parent
         if not p.exists() or not p.is_dir():
-            raise ValueError("path does not point inside an existing directory")
+            raise ValueError(f"path '{self._value}' does not point inside an existing directory")
         if self._value.is_dir():
             raise ValueError("path points to a directory when we expected a file")
 
diff --git a/manager/knot_resolver_manager/server.py b/manager/knot_resolver_manager/server.py
index 1126798ecbea77df58498912e31199a354f8d652..a837df550fa3be05b2f20861b2cd56f1ae16017d 100644
--- a/manager/knot_resolver_manager/server.py
+++ b/manager/knot_resolver_manager/server.py
@@ -23,7 +23,7 @@ from knot_resolver_manager import log, statistics
 from knot_resolver_manager.compat import asyncio as asyncio_compat
 from knot_resolver_manager.config_store import ConfigStore
 from knot_resolver_manager.constants import DEFAULT_MANAGER_CONFIG_FILE, PID_FILE_NAME, init_user_constants
-from knot_resolver_manager.datamodel.config_schema import KresConfig
+from knot_resolver_manager.datamodel.config_schema import KresConfig, get_rundir_without_validation
 from knot_resolver_manager.datamodel.management_schema import ManagementSchema
 from knot_resolver_manager.exceptions import CancelStartupExecInsteadException, KresManagerException
 from knot_resolver_manager.kresd_controller import get_best_controller_implementation
@@ -389,12 +389,13 @@ async def _deny_working_directory_changes(config_old: KresConfig, config_new: Kr
 
 
 def _set_working_directory(config_raw: Dict[str, Any]) -> None:
-    config = KresConfig(config_raw)
+    rundir = get_rundir_without_validation(config_raw)
 
-    if not config.rundir.to_path().exists():
-        raise KresManagerException(f"`rundir` directory ({config.rundir}) does not exist!")
+    if not rundir.to_path().exists():
+        raise KresManagerException(f"`rundir` directory ({rundir}) does not exist!")
 
-    os.chdir(config.rundir.to_path())
+    logger.info("changing working directory to rundir at '%s'", rundir.to_path().absolute())
+    os.chdir(rundir.to_path())
 
 
 def _lock_working_directory(attempt: int = 0) -> None:
@@ -469,11 +470,9 @@ async def start_server(config: Union[Path, Dict[str, Any]] = DEFAULT_MANAGER_CON
         config_raw = await _load_raw_config(config)
 
         # We want to change cwd as soon as possible. Especially before any real config validation, because cwd
-        # is used for resolving relative paths. Thats also a reason, why in practice, we validate the config twice.
-        # Once when setting up the cwd just to read the `rundir` property. When cwd is set, we do it again to resolve
-        # all paths correctly.
-        # Note: the first config validation is done here - therefore all initial config validation errors will
-        # originate from here.
+        # is used for resolving relative paths. If we fail to read rundir from unparsed config, a full validation
+        # error will come from here. If we are successfull, full validation will be done further on when initializing
+        # the config store.
         _set_working_directory(config_raw)
 
         # We don't want more than one manager in a single working directory. So we lock it with a PID file.