diff --git a/doc/man/knot.conf.5in b/doc/man/knot.conf.5in
index 8dfdd95d38b48cbed60f4b6f7be8c670f8482bee..37f3a2f86891036c5915ff12ce65828fd50c70bb 100644
--- a/doc/man/knot.conf.5in
+++ b/doc/man/knot.conf.5in
@@ -66,13 +66,15 @@ the following symbols:
 | – Choice
 .UNINDENT
 .sp
-There are 12 main sections (\fBmodule\fP, \fBserver\fP, \fBcontrol\fP, \fBlog\fP,
-\fBstatistics\fP, \fBkeystore\fP, \fBpolicy\fP, \fBkey\fP, \fBacl\fP, \fBremote\fP,
-\fBtemplate\fP, and \fBzone\fP) and module sections with the \fBmod\-\fP prefix.
-Most of the sections (excluding \fBserver\fP, \fBcontrol\fP, and \fBstatistics\fP)
-are sequences of settings blocks. Each settings block begins with a unique identifier,
-which can be used as a reference from other sections (such identifier
-must be defined in advance).
+The configuration consists of several fixed sections and optional module
+sections. There are 14 fixed sections (\fBmodule\fP, \fBserver\fP, \fBkey\fP, \fBacl\fP,
+\fBcontrol\fP, \fBstatistics\fP, \fBdatabase\fP, \fBkeystore\fP, \fBsubmission\fP,
+\fBpolicy\fP, \fBremote\fP, \fBtemplate\fP, \fBzone\fP, \fBlog\fP).
+Module sections are prefixed with the \fBmod\-\fP prefix (e.g. \fBmod\-stats\fP).
+.sp
+Most of the sections (e.g. \fBzone\fP) are sequences of settings blocks. Each
+settings block begins with a unique identifier, which can be used as a reference
+from other sections (such an identifier must be defined in advance).
 .sp
 A multi\-valued item can be specified either as a YAML sequence:
 .INDENT 0.0
@@ -572,6 +574,115 @@ If enabled, the output will be appended to the \fI\%file\fP
 instead of file replacement.
 .sp
 \fIDefault:\fP off
+.SH DATABASE SECTION
+.sp
+Configuration of databases for zone contents, DNSSEC metadata, or event timers.
+.INDENT 0.0
+.INDENT 3.5
+.sp
+.nf
+.ft C
+database:
+    storage: STR
+    journal\-db: STR
+    journal\-db\-mode: robust | asynchronous
+    journal\-db\-max\-size: SIZE
+    kasp\-db: STR
+    kasp\-db\-max\-size: SIZE
+    timer\-db: STR
+    timer\-db\-max\-size: SIZE
+.ft P
+.fi
+.UNINDENT
+.UNINDENT
+.SS storage
+.sp
+A data directory for storing journal, KASP, and timer databases.
+.sp
+\fIDefault:\fP \fB${localstatedir}/lib/knot\fP (configured with \fB\-\-with\-storage=path\fP)
+.SS journal\-db
+.sp
+An explicit specification of the persistent journal database directory.
+Non\-absolute path (i.e. not starting with \fB/\fP) is relative to
+\fI\%storage\fP\&.
+.sp
+\fIDefault:\fP \fI\%storage\fP/journal
+.SS journal\-db\-mode
+.sp
+Specifies journal LMDB backend configuration, which influences performance
+and durability.
+.sp
+Possible values:
+.INDENT 0.0
+.IP \(bu 2
+\fBrobust\fP – The journal database disk sychronization ensures database
+durability but is generally slower.
+.IP \(bu 2
+\fBasynchronous\fP – The journal database disk synchronization is optimized for
+better performance at the expense of lower database durability in the case of
+a crash. This mode is recommended on slave nodes with many zones.
+.UNINDENT
+.sp
+\fIDefault:\fP robust
+.SS journal\-db\-max\-size
+.sp
+The hard limit for the journal database maximum size. There is no cleanup logic
+in journal to recover from reaching this limit. Journal simply starts refusing
+changes across all zones. Decreasing this value has no effect if it is lower
+than the actual database file size.
+.sp
+It is recommended to limit \fI\%max\-journal\-usage\fP
+per\-zone instead of \fI\%journal\-db\-max\-size\fP
+in most cases. Please keep this value larger than the sum of all zones\(aq
+journal usage limits. See more details regarding
+journal behaviour\&.
+.sp
+\fBNOTE:\fP
+.INDENT 0.0
+.INDENT 3.5
+This value also influences server\(aqs usage of virtual memory.
+.UNINDENT
+.UNINDENT
+.sp
+\fIDefault:\fP 20 GiB (1 GiB for 32\-bit)
+.SS kasp\-db
+.sp
+An explicit specification of the KASP database directory.
+Non\-absolute path (i.e. not starting with \fB/\fP) is relative to
+\fI\%storage\fP\&.
+.sp
+\fIDefault:\fP \fI\%storage\fP/keys
+.SS kasp\-db\-max\-size
+.sp
+The hard limit for the KASP database maximum size.
+.sp
+\fBNOTE:\fP
+.INDENT 0.0
+.INDENT 3.5
+This value also influences server\(aqs usage of virtual memory.
+.UNINDENT
+.UNINDENT
+.sp
+\fIDefault:\fP 500 MiB
+.SS timer\-db
+.sp
+An explicit specification of the persistent timer database directory.
+Non\-absolute path (i.e. not starting with \fB/\fP) is relative to
+\fI\%storage\fP\&.
+.sp
+\fIDefault:\fP \fI\%storage\fP/timers
+.SS timer\-db\-max\-size
+.sp
+The hard limit for the timer database maximum size.
+.sp
+\fBNOTE:\fP
+.INDENT 0.0
+.INDENT 3.5
+This value also influences server\(aqs usage of virtual memory.
+.UNINDENT
+.UNINDENT
+.sp
+\fIDefault:\fP 100 MiB
 .SH KEYSTORE SECTION
 .sp
 DNSSEC keystore configuration.
@@ -1051,9 +1162,9 @@ the communication with the remote server.
 \fIDefault:\fP not set
 .SH TEMPLATE SECTION
 .sp
-A template is a shareable zone setting which can be used for configuration of
-many zones in one place. A special default template (with the \fIdefault\fP identifier)
-can be used for global querying configuration or as an implicit configuration
+A template is shareable zone settings, which can simplify configuration by
+reducing duplicates. A special default template (with the \fIdefault\fP identifier)
+can be used for global zone configuration or as an implicit configuration
 if a zone doesn\(aqt have another template specified.
 .INDENT 0.0
 .INDENT 3.5
@@ -1062,13 +1173,6 @@ if a zone doesn\(aqt have another template specified.
 .ft C
 template:
   \- id: STR
-    timer\-db: STR
-    max\-timer\-db\-size: SIZE
-    journal\-db: STR
-    journal\-db\-mode: robust | asynchronous
-    max\-journal\-db\-size: SIZE
-    kasp\-db: STR
-    max\-kasp\-db\-size: SIZE
     global\-module: STR/STR ...
     # All zone options (excluding \(aqtemplate\(aq item)
 .ft P
@@ -1078,116 +1182,6 @@ template:
 .SS id
 .sp
 A template identifier.
-.SS timer\-db
-.sp
-Specifies a path of the persistent timer database. The path can be specified
-as a relative path to the \fIdefault\fP template \fI\%storage\fP\&.
-.sp
-\fBNOTE:\fP
-.INDENT 0.0
-.INDENT 3.5
-This option is only available in the \fIdefault\fP template.
-.UNINDENT
-.UNINDENT
-.sp
-\fIDefault:\fP \fI\%storage\fP/timers
-.SS max\-timer\-db\-size
-.sp
-Hard limit for the timer database maximum size.
-.sp
-\fBNOTE:\fP
-.INDENT 0.0
-.INDENT 3.5
-This option is only available in the \fIdefault\fP template.
-.UNINDENT
-.UNINDENT
-.sp
-\fIDefault:\fP 100 MiB
-.SS journal\-db
-.sp
-Specifies a path of the persistent journal database. The path can be specified
-as a relative path to the \fIdefault\fP template \fI\%storage\fP\&.
-.sp
-\fBNOTE:\fP
-.INDENT 0.0
-.INDENT 3.5
-This option is only available in the \fIdefault\fP template.
-.UNINDENT
-.UNINDENT
-.sp
-\fIDefault:\fP \fI\%storage\fP/journal
-.SS journal\-db\-mode
-.sp
-Specifies journal LMDB backend configuration, which influences performance
-and durability.
-.sp
-Possible values:
-.INDENT 0.0
-.IP \(bu 2
-\fBrobust\fP – The journal DB disk sychronization ensures DB durability but is
-generally slower.
-.IP \(bu 2
-\fBasynchronous\fP – The journal DB disk synchronization is optimized for
-better performance at the expense of lower DB durability; this mode is
-recommended only on slave nodes with many zones.
-.UNINDENT
-.sp
-\fBNOTE:\fP
-.INDENT 0.0
-.INDENT 3.5
-This option is only available in the \fIdefault\fP template.
-.UNINDENT
-.UNINDENT
-.sp
-\fIDefault:\fP robust
-.SS max\-journal\-db\-size
-.sp
-Hard limit for the common journal DB. There is no cleanup logic in journal
-to recover from reaching this limit: journal simply starts refusing changes
-across all zones. Decreasing this value has no effect if lower than actual
-DB file size.
-.sp
-It is recommended to limit \fI\%max\-journal\-usage\fP
-per\-zone instead of \fI\%max\-journal\-db\-size\fP
-in most cases. Please keep this value larger than the sum of all zones\(aq
-journal usage limits. See more details regarding
-journal behaviour\&.
-.sp
-This value also influences server\(aqs usage of virtual memory.
-.sp
-\fBNOTE:\fP
-.INDENT 0.0
-.INDENT 3.5
-This option is only available in the \fIdefault\fP template.
-.UNINDENT
-.UNINDENT
-.sp
-\fIDefault:\fP 20 GiB (1 GiB for 32\-bit)
-.SS kasp\-db
-.sp
-A KASP database path. Non\-absolute path is relative to
-\fI\%storage\fP\&.
-.sp
-\fBNOTE:\fP
-.INDENT 0.0
-.INDENT 3.5
-This option is only available in the \fIdefault\fP template.
-.UNINDENT
-.UNINDENT
-.sp
-\fIDefault:\fP \fI\%storage\fP/keys
-.SS max\-kasp\-db\-size
-.sp
-Hard limit for the KASP database maximum size.
-.sp
-\fBNOTE:\fP
-.INDENT 0.0
-.INDENT 3.5
-This option is only available in the \fIdefault\fP template.
-.UNINDENT
-.UNINDENT
-.sp
-\fIDefault:\fP 500 MiB
 .SS global\-module
 .sp
 An ordered list of references to query modules in the form of \fImodule_name\fP or
@@ -1246,13 +1240,14 @@ A \fI\%reference\fP to a configuration template.
 \fIDefault:\fP not set or \fIdefault\fP (if the template exists)
 .SS storage
 .sp
-A data directory for storing zone files, journal database, and timers database.
+A data directory for storing zone files.
 .sp
 \fIDefault:\fP \fB${localstatedir}/lib/knot\fP (configured with \fB\-\-with\-storage=path\fP)
 .SS file
 .sp
-A path to the zone file. Non\-absolute path is relative to
-\fI\%storage\fP\&. It is also possible to use the following formatters:
+A path to the zone file. Non\-absolute path (i.e. not starting with \fB/\fP) is
+relative to \fI\%storage\fP\&.
+It is also possible to use the following formatters:
 .INDENT 0.0
 .IP \(bu 2
 \fB%c[\fP\fIN\fP\fB]\fP or \fB%c[\fP\fIN\fP\fB\-\fP\fIM\fP\fB]\fP – Means the \fIN\fPth
diff --git a/doc/migration.rst b/doc/migration.rst
index fd896f8fd3c915bb3f9be685677c1192e8dbe438..948d0118c8f18db288e2fcaedfd06658344679a3 100644
--- a/doc/migration.rst
+++ b/doc/migration.rst
@@ -30,7 +30,7 @@ Since Knot DNS version 2.5.0 each query module can be configured to be:
   ``dnsproxy`` and ``onlinesign``)
 
 The ``--with-timer-mapsize`` configure option was replaced with the runtime
-:ref:`template_max-timer-db-size` configuration option.
+``template.max-timer-db-size`` configuration option.
 
 .. _KASP DB migration:
 
diff --git a/doc/operation.rst b/doc/operation.rst
index 8cf189d4357e36194e7b08c0a3d4bcb7ae155749..71aff2682d00956f45fe0280fe15c7d46b8c8fe0 100644
--- a/doc/operation.rst
+++ b/doc/operation.rst
@@ -360,7 +360,7 @@ If the journal is used to store both zone history and contents, a special change
 is present with zone contents. When the journal gets full, the changes are merged into this
 special changeset.
 
-There is also a :ref:`safety hard limit <template_max-journal-db-size>` for overall
+There is also a :ref:`safety hard limit <database_journal-db-max-size>` for overall
 journal database size, but it's strongly recommended to set the per-zone limits in
 a way to prevent hitting this one. For LMDB, it's hard to recover from the
 database-full state. For wiping one zone's journal, see *knotc zone-purge +journal*
@@ -810,14 +810,14 @@ package on Ubuntu and Debian or by the `lmdb <https://rpms.remirepo.net/rpmphp/z
 package on Fedora, CentOS and RHEL.
 These tools allow you to convert the contents of any LMDB database to a portable plain text format
 which can be imported to any other LMDB database. Note that the `keys` subdirectory of the
-:ref:`template_kasp-db` directory containing the \*.pem files has to be copied separately.
+:ref:`database_kasp-db` directory containing the \*.pem files has to be copied separately.
 
 .. NOTE::
    Make sure to freeze DNSSEC events on a running server prior to applying the following
    commands to its  KASP DB. Use the ``knotc zone-freeze`` and ``knotc zone-thaw`` commands
    as described in :ref:`Editing zone file`.
 
-Use the ``mdb_dump -a`` command with the configured :ref:`template_kasp-db` directory
+Use the ``mdb_dump -a`` command with the configured :ref:`database_kasp-db` directory
 as an argument to convert the contents of the LMDB database to a portable text format:
 
 .. code-block:: console
diff --git a/doc/reference.rst b/doc/reference.rst
index c928069bf37a8251cb3ec0c41e0bab5227ba300d..27d52a100647da361efd0c490cc343917b24529e 100644
--- a/doc/reference.rst
+++ b/doc/reference.rst
@@ -31,13 +31,15 @@ the following symbols:
 - [ ] – Optional value
 - \| – Choice
 
-There are 12 main sections (``module``, ``server``, ``control``, ``log``,
-``statistics``, ``keystore``, ``policy``, ``key``, ``acl``, ``remote``,
-``template``, and ``zone``) and module sections with the ``mod-`` prefix.
-Most of the sections (excluding ``server``, ``control``, and ``statistics``)
-are sequences of settings blocks. Each settings block begins with a unique identifier,
-which can be used as a reference from other sections (such identifier
-must be defined in advance).
+The configuration consists of several fixed sections and optional module
+sections. There are 14 fixed sections (``module``, ``server``, ``key``, ``acl``,
+``control``, ``statistics``, ``database``, ``keystore``, ``submission``,
+``policy``, ``remote``, ``template``, ``zone``, ``log``).
+Module sections are prefixed with the ``mod-`` prefix (e.g. ``mod-stats``).
+
+Most of the sections (e.g. ``zone``) are sequences of settings blocks. Each
+settings block begins with a unique identifier, which can be used as a reference
+from other sections (such an identifier must be defined in advance).
 
 A multi-valued item can be specified either as a YAML sequence::
 
@@ -629,6 +631,130 @@ instead of file replacement.
 
 *Default:* off
 
+.. _Database section:
+
+Database section
+================
+
+Configuration of databases for zone contents, DNSSEC metadata, or event timers.
+
+::
+
+ database:
+     storage: STR
+     journal-db: STR
+     journal-db-mode: robust | asynchronous
+     journal-db-max-size: SIZE
+     kasp-db: STR
+     kasp-db-max-size: SIZE
+     timer-db: STR
+     timer-db-max-size: SIZE
+
+.. _database_storage:
+
+storage
+-------
+
+A data directory for storing journal, KASP, and timer databases.
+
+*Default:* ``${localstatedir}/lib/knot`` (configured with ``--with-storage=path``)
+
+.. _database_journal-db:
+
+journal-db
+----------
+
+An explicit specification of the persistent journal database directory.
+Non-absolute path (i.e. not starting with ``/``) is relative to
+:ref:`storage<database_storage>`.
+
+*Default:* :ref:`storage<database_storage>`/journal
+
+.. _database_journal-db-mode:
+
+journal-db-mode
+---------------
+
+Specifies journal LMDB backend configuration, which influences performance
+and durability.
+
+Possible values:
+
+- ``robust`` – The journal database disk sychronization ensures database
+  durability but is generally slower.
+- ``asynchronous`` – The journal database disk synchronization is optimized for
+  better performance at the expense of lower database durability in the case of
+  a crash. This mode is recommended on slave nodes with many zones.
+
+*Default:* robust
+
+.. _database_journal-db-max-size:
+
+journal-db-max-size
+-------------------
+
+The hard limit for the journal database maximum size. There is no cleanup logic
+in journal to recover from reaching this limit. Journal simply starts refusing
+changes across all zones. Decreasing this value has no effect if it is lower
+than the actual database file size.
+
+It is recommended to limit :ref:`max-journal-usage<zone_max-journal-usage>`
+per-zone instead of :ref:`journal-db-max-size<database_journal-db-max-size>`
+in most cases. Please keep this value larger than the sum of all zones'
+journal usage limits. See more details regarding
+:ref:`journal behaviour<Journal behaviour>`.
+
+.. NOTE::
+   This value also influences server's usage of virtual memory.
+
+*Default:* 20 GiB (1 GiB for 32-bit)
+
+.. _database_kasp-db:
+
+kasp-db
+-------
+
+An explicit specification of the KASP database directory.
+Non-absolute path (i.e. not starting with ``/``) is relative to
+:ref:`storage<database_storage>`.
+
+*Default:* :ref:`storage<database_storage>`/keys
+
+.. _database_kasp-db-max-size:
+
+kasp-db-max-size
+----------------
+
+The hard limit for the KASP database maximum size.
+
+.. NOTE::
+   This value also influences server's usage of virtual memory.
+
+*Default:* 500 MiB
+
+.. _database_timer-db:
+
+timer-db
+--------
+
+An explicit specification of the persistent timer database directory.
+Non-absolute path (i.e. not starting with ``/``) is relative to
+:ref:`storage<database_storage>`.
+
+*Default:* :ref:`storage<database_storage>`/timers
+
+.. _database_timer-db-max-size:
+
+timer-db-max-size
+-----------------
+
+The hard limit for the timer database maximum size.
+
+.. NOTE::
+   This value also influences server's usage of virtual memory.
+
+*Default:* 100 MiB
+
 .. _Keystore section:
 
 Keystore section
@@ -671,7 +797,7 @@ config
 ------
 
 A backend specific configuration. A directory with PEM files (the path can
-be specified as a relative path to :ref:`kasp-db<template_kasp-db>`) or
+be specified as a relative path to :ref:`kasp-db<database_kasp-db>`) or
 a configuration string for PKCS #11 storage (`<pkcs11-url> <module-path>`).
 
 .. NOTE::
@@ -679,7 +805,7 @@ a configuration string for PKCS #11 storage (`<pkcs11-url> <module-path>`).
 
      "pkcs11:token=knot;pin-value=1234 /usr/lib64/pkcs11/libsofthsm2.so"
 
-*Default:* :ref:`kasp-db<template_kasp-db>`/keys
+*Default:* :ref:`kasp-db<database_kasp-db>`/keys
 
 .. _Submission section:
 
@@ -1163,22 +1289,15 @@ the communication with the remote server.
 Template section
 ================
 
-A template is a shareable zone setting which can be used for configuration of
-many zones in one place. A special default template (with the *default* identifier)
-can be used for global querying configuration or as an implicit configuration
+A template is shareable zone settings, which can simplify configuration by
+reducing duplicates. A special default template (with the *default* identifier)
+can be used for global zone configuration or as an implicit configuration
 if a zone doesn't have another template specified.
 
 ::
 
  template:
    - id: STR
-     timer-db: STR
-     max-timer-db-size: SIZE
-     journal-db: STR
-     journal-db-mode: robust | asynchronous
-     max-journal-db-size: SIZE
-     kasp-db: STR
-     max-kasp-db-size: SIZE
      global-module: STR/STR ...
      # All zone options (excluding 'template' item)
 
@@ -1189,113 +1308,6 @@ id
 
 A template identifier.
 
-.. _template_timer-db:
-
-timer-db
---------
-
-Specifies a path of the persistent timer database. The path can be specified
-as a relative path to the *default* template :ref:`storage<zone_storage>`.
-
-.. NOTE::
-   This option is only available in the *default* template.
-
-*Default:* :ref:`storage<zone_storage>`/timers
-
-.. _template_max-timer-db-size:
-
-max-timer-db-size
------------------
-
-Hard limit for the timer database maximum size.
-
-.. NOTE::
-   This option is only available in the *default* template.
-
-*Default:* 100 MiB
-
-.. _template_journal-db:
-
-journal-db
-----------
-
-Specifies a path of the persistent journal database. The path can be specified
-as a relative path to the *default* template :ref:`storage<zone_storage>`.
-
-.. NOTE::
-   This option is only available in the *default* template.
-
-*Default:* :ref:`storage<zone_storage>`/journal
-
-.. _template_journal-db-mode:
-
-journal-db-mode
----------------
-
-Specifies journal LMDB backend configuration, which influences performance
-and durability.
-
-Possible values:
-
-- ``robust`` – The journal DB disk sychronization ensures DB durability but is
-  generally slower.
-- ``asynchronous`` – The journal DB disk synchronization is optimized for
-  better performance at the expense of lower DB durability; this mode is
-  recommended only on slave nodes with many zones.
-
-.. NOTE::
-   This option is only available in the *default* template.
-
-*Default:* robust
-
-.. _template_max-journal-db-size:
-
-max-journal-db-size
--------------------
-
-Hard limit for the common journal DB. There is no cleanup logic in journal
-to recover from reaching this limit: journal simply starts refusing changes
-across all zones. Decreasing this value has no effect if lower than actual
-DB file size.
-
-It is recommended to limit :ref:`max-journal-usage<zone_max-journal-usage>`
-per-zone instead of :ref:`max-journal-db-size<template_max-journal-db-size>`
-in most cases. Please keep this value larger than the sum of all zones'
-journal usage limits. See more details regarding
-:ref:`journal behaviour<Journal behaviour>`.
-
-This value also influences server's usage of virtual memory.
-
-.. NOTE::
-   This option is only available in the *default* template.
-
-*Default:* 20 GiB (1 GiB for 32-bit)
-
-.. _template_kasp-db:
-
-kasp-db
--------
-
-A KASP database path. Non-absolute path is relative to
-:ref:`storage<zone_storage>`.
-
-.. NOTE::
-   This option is only available in the *default* template.
-
-*Default:* :ref:`storage<zone_storage>`/keys
-
-.. _template_max-kasp-db-size:
-
-max-kasp-db-size
-----------------
-
-Hard limit for the KASP database maximum size.
-
-.. NOTE::
-   This option is only available in the *default* template.
-
-*Default:* 500 MiB
-
 .. _template_global-module:
 
 global-module
@@ -1363,7 +1375,7 @@ A :ref:`reference<template_id>` to a configuration template.
 storage
 -------
 
-A data directory for storing zone files, journal database, and timers database.
+A data directory for storing zone files.
 
 *Default:* ``${localstatedir}/lib/knot`` (configured with ``--with-storage=path``)
 
@@ -1372,8 +1384,9 @@ A data directory for storing zone files, journal database, and timers database.
 file
 ----
 
-A path to the zone file. Non-absolute path is relative to
-:ref:`storage<zone_storage>`. It is also possible to use the following formatters:
+A path to the zone file. Non-absolute path (i.e. not starting with ``/``) is
+relative to :ref:`storage<zone_storage>`.
+It is also possible to use the following formatters:
 
 - ``%c[``\ *N*\ ``]`` or ``%c[``\ *N*\ ``-``\ *M*\ ``]`` – Means the *N*\ th
   character or a sequence of characters beginning from the *N*\ th and ending
diff --git a/samples/knot.sample.conf.in b/samples/knot.sample.conf.in
index 10302f958c8765ae7ec410fa169cf84023435153..6df291b7dd5f6c4a339f30abb17d831b40181b37 100644
--- a/samples/knot.sample.conf.in
+++ b/samples/knot.sample.conf.in
@@ -10,6 +10,9 @@ log:
   - target: syslog
     any: info
 
+database:
+    storage: "@storage_dir@"
+
 remote:
 #  - id: slave
 #    address: 192.168.1.1@53
diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c
index 569d483fe562f05fe0c235567713f67504416b73..7a3c8d5b956b37aa42b261b35b8266c03b7eaf6f 100644
--- a/src/knot/conf/conf.c
+++ b/src/knot/conf/conf.c
@@ -1066,15 +1066,37 @@ char* conf_db_txn(
 	knot_db_txn_t *txn,
 	const yp_name_t *db_type)
 {
-	conf_val_t val = conf_default_get_txn(conf, txn, C_STORAGE);
-	char *storage = conf_abs_path(&val, NULL);
-	val = conf_default_get_txn(conf, txn, db_type);
-	char *dbdir = conf_abs_path(&val, storage);
+	conf_val_t storage_val = conf_get_txn(conf, txn, C_DB, C_STORAGE);
+	if (storage_val.code != KNOT_EOK) {
+		storage_val = conf_default_get_txn(conf, txn, C_STORAGE);
+	}
+
+	conf_val_t db_val = conf_get_txn(conf, txn, C_DB, db_type);
+	if (db_val.code != KNOT_EOK) {
+		db_val = conf_default_get_txn(conf, txn, db_type);
+	}
+
+	char *storage = conf_abs_path(&storage_val, NULL);
+	char *dbdir = conf_abs_path(&db_val, storage);
 	free(storage);
 
 	return dbdir;
 }
 
+conf_val_t conf_db_param_txn(
+	conf_t *conf,
+	knot_db_txn_t *txn,
+	const yp_name_t *param,
+	const yp_name_t *legacy_param)
+{
+	conf_val_t val = conf_get_txn(conf, txn, C_DB, param);
+	if (val.code != KNOT_EOK) {
+		val = conf_default_get_txn(conf, txn, legacy_param);
+	}
+
+	return val;
+}
+
 size_t conf_udp_threads_txn(
 	conf_t *conf,
 	knot_db_txn_t *txn)
diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h
index abb5e579aa34c8c722fd681637ce24ef1d006fc7..64ad89192613bcd488183c44c34bf52405f878df 100644
--- a/src/knot/conf/conf.h
+++ b/src/knot/conf/conf.h
@@ -597,14 +597,17 @@ static inline char* conf_zonefile(
  *
  * \note The result must be explicitly deallocated.
  *
- * \param[in] conf  Configuration.
- * \param[in] txn   Configuration DB transaction.
+ * \param[in] conf     Configuration.
+ * \param[in] txn      Configuration DB transaction.
+ * \param[in] db_type  Database name.
  *
  * \return Absolute database path string pointer.
  */
-char* conf_db_txn(conf_t *conf,
+char* conf_db_txn(
+	conf_t *conf,
 	knot_db_txn_t *txn,
-	const yp_name_t *db_type);
+	const yp_name_t *db_type
+);
 static inline char* conf_db(
 	conf_t *conf,
 	const yp_name_t *db_type)
@@ -612,6 +615,30 @@ static inline char* conf_db(
 	return conf_db_txn(conf, &conf->read_txn, db_type);
 }
 
+/*!
+ * Gets database-specific parameter.
+ *
+ * \param[in] conf          Configuration.
+ * \param[in] txn           Configuration DB transaction.
+ * \param[in] param         Parameter name.
+ * \param[in] legacy_param  Legacy parameter name.
+ *
+ * \return Item value.
+ */
+conf_val_t conf_db_param_txn(
+	conf_t *conf,
+	knot_db_txn_t *txn,
+	const yp_name_t *param,
+	const yp_name_t *legacy_param
+);
+static inline conf_val_t conf_db_param(
+	conf_t *conf,
+	const yp_name_t *param,
+	const yp_name_t *legacy_param)
+{
+	return conf_db_param_txn(conf, &conf->read_txn, param, legacy_param);
+}
+
 /*!
  * Gets the configured number of UDP threads.
  *
diff --git a/src/knot/conf/schema.c b/src/knot/conf/schema.c
index e2dea846a9bc4e2d4dc48199e3747870720a0bcb..e04b598bf3b1727a86fbb74da5b8e62a3c125980 100644
--- a/src/knot/conf/schema.c
+++ b/src/knot/conf/schema.c
@@ -202,6 +202,21 @@ static const yp_item_t desc_stats[] = {
 	{ NULL }
 };
 
+static const yp_item_t desc_database[] = {
+	{ C_STORAGE,             YP_TSTR,  YP_VSTR = { STORAGE_DIR } },
+	{ C_JOURNAL_DB,          YP_TSTR,  YP_VSTR = { "journal" } },
+	{ C_JOURNAL_DB_MODE,     YP_TOPT,  YP_VOPT = { journal_modes, JOURNAL_MODE_ROBUST } },
+	{ C_JOURNAL_DB_MAX_SIZE, YP_TINT,  YP_VINT = { MEGA(1), VIRT_MEM_LIMIT(TERA(100)),
+	                                               VIRT_MEM_LIMIT(GIGA(20)), YP_SSIZE } },
+	{ C_KASP_DB,             YP_TSTR,  YP_VSTR = { "keys" } },
+	{ C_KASP_DB_MAX_SIZE,    YP_TINT,  YP_VINT = { MEGA(5), VIRT_MEM_LIMIT(GIGA(100)),
+	                                               MEGA(500), YP_SSIZE } },
+	{ C_TIMER_DB,            YP_TSTR,  YP_VSTR = { "timers" } },
+	{ C_TIMER_DB_MAX_SIZE,   YP_TINT,  YP_VINT = { MEGA(1), VIRT_MEM_LIMIT(GIGA(100)),
+	                                               MEGA(100), YP_SSIZE } },
+	{ NULL }
+};
+
 static const yp_item_t desc_keystore[] = {
 	{ C_ID,      YP_TSTR, YP_VNONE },
 	{ C_BACKEND, YP_TOPT, YP_VOPT = { keystore_backends, KEYSTORE_BACKEND_PEM },
@@ -328,9 +343,10 @@ static const yp_item_t desc_policy[] = {
 
 static const yp_item_t desc_template[] = {
 	{ C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF },
-	ZONE_ITEMS(CONF_IO_FRLD_ZONES)
 	{ C_GLOBAL_MODULE,       YP_TDATA, YP_VDATA = { 0, NULL, mod_id_to_bin, mod_id_to_txt },
 	                                   YP_FMULTI | CONF_IO_FRLD_MOD, { check_modref } },
+	ZONE_ITEMS(CONF_IO_FRLD_ZONES)
+	// Legacy items.
 	{ C_TIMER_DB,            YP_TSTR,  YP_VSTR = { "timers" }, CONF_IO_FRLD_SRV },
 	{ C_MAX_TIMER_DB_SIZE,   YP_TINT,  YP_VINT = { MEGA(1), VIRT_MEM_LIMIT(GIGA(100)),
 	                                               MEGA(100), YP_SSIZE }, CONF_IO_FRLD_SRV },
@@ -360,6 +376,7 @@ const yp_item_t conf_schema[] = {
 	{ C_CTL,      YP_TGRP, YP_VGRP = { desc_control } },
 	{ C_LOG,      YP_TGRP, YP_VGRP = { desc_log }, YP_FMULTI | CONF_IO_FRLD_LOG },
 	{ C_STATS,    YP_TGRP, YP_VGRP = { desc_stats }, CONF_IO_FRLD_SRV },
+	{ C_DB,       YP_TGRP, YP_VGRP = { desc_database }, CONF_IO_FRLD_SRV },
 	{ C_KEYSTORE, YP_TGRP, YP_VGRP = { desc_keystore }, YP_FMULTI, { check_keystore } },
 	{ C_KEY,      YP_TGRP, YP_VGRP = { desc_key }, YP_FMULTI, { check_key } },
 	{ C_ACL,      YP_TGRP, YP_VGRP = { desc_acl }, YP_FMULTI, { check_acl } },
diff --git a/src/knot/conf/schema.h b/src/knot/conf/schema.h
index e35ffa016589ff1fdc88c003df152c1cf043eca4..bd942a7de27db02d50727e65fab2acdcaf1ec21e 100644
--- a/src/knot/conf/schema.h
+++ b/src/knot/conf/schema.h
@@ -34,6 +34,7 @@
 #define C_COMMENT		"\x07""comment"
 #define C_CONFIG		"\x06""config"
 #define C_CTL			"\x07""control"
+#define C_DB			"\x08""database"
 #define C_DDNS_MASTER		"\x0B""ddns-master"
 #define C_DENY			"\x04""deny"
 #define C_DISABLE_ANY		"\x0B""disable-any"
@@ -50,8 +51,10 @@
 #define C_INCL			"\x07""include"
 #define C_JOURNAL_CONTENT	"\x0F""journal-content"
 #define C_JOURNAL_DB		"\x0A""journal-db"
+#define C_JOURNAL_DB_MAX_SIZE	"\x13""journal-db-max-size"
 #define C_JOURNAL_DB_MODE	"\x0F""journal-db-mode"
 #define C_KASP_DB		"\x07""kasp-db"
+#define C_KASP_DB_MAX_SIZE	"\x10""kasp-db-max-size"
 #define C_KEY			"\x03""key"
 #define C_KEYSTORE		"\x08""keystore"
 #define C_KSK_LIFETIME		"\x0C""ksk-lifetime"
@@ -64,13 +67,10 @@
 #define C_MASTER		"\x06""master"
 #define C_MAX_IPV4_UDP_PAYLOAD	"\x14""max-ipv4-udp-payload"
 #define C_MAX_IPV6_UDP_PAYLOAD	"\x14""max-ipv6-udp-payload"
-#define C_MAX_JOURNAL_DB_SIZE	"\x13""max-journal-db-size"
 #define C_MAX_JOURNAL_DEPTH	"\x11""max-journal-depth"
 #define C_MAX_JOURNAL_USAGE	"\x11""max-journal-usage"
-#define C_MAX_KASP_DB_SIZE	"\x10""max-kasp-db-size"
 #define C_MAX_REFRESH_INTERVAL	"\x14""max-refresh-interval"
 #define C_MAX_TCP_CLIENTS	"\x0F""max-tcp-clients"
-#define C_MAX_TIMER_DB_SIZE	"\x11""max-timer-db-size"
 #define C_MAX_UDP_PAYLOAD	"\x0F""max-udp-payload"
 #define C_MAX_ZONE_SIZE		"\x0D""max-zone-size"
 #define C_MIN_REFRESH_INTERVAL	"\x14""min-refresh-interval"
@@ -89,8 +89,8 @@
 #define C_PROPAG_DELAY		"\x11""propagation-delay"
 #define C_RMT			"\x06""remote"
 #define C_RRSIG_LIFETIME	"\x0E""rrsig-lifetime"
-#define C_RRSIG_REFRESH		"\x0D""rrsig-refresh"
 #define C_RRSIG_PREREFRESH	"\x11""rrsig-pre-refresh"
+#define C_RRSIG_REFRESH		"\x0D""rrsig-refresh"
 #define C_RUNDIR		"\x06""rundir"
 #define C_SBM			"\x0A""submission"
 #define C_SECRET		"\x06""secret"
@@ -111,6 +111,7 @@
 #define C_TIMEOUT		"\x07""timeout"
 #define C_TIMER			"\x05""timer"
 #define C_TIMER_DB		"\x08""timer-db"
+#define C_TIMER_DB_MAX_SIZE	"\x11""timer-db-max-size"
 #define C_TPL			"\x08""template"
 #define C_UDP_WORKERS		"\x0B""udp-workers"
 #define C_UPDATE_OWNER		"\x0C""update-owner"
@@ -127,6 +128,11 @@
 #define C_ZSK_LIFETIME		"\x0C""zsk-lifetime"
 #define C_ZSK_SIZE		"\x08""zsk-size"
 
+// Legacy items.
+#define C_MAX_TIMER_DB_SIZE	"\x11""max-timer-db-size"
+#define C_MAX_JOURNAL_DB_SIZE	"\x13""max-journal-db-size"
+#define C_MAX_KASP_DB_SIZE	"\x10""max-kasp-db-size"
+
 enum {
 	KEYSTORE_BACKEND_PEM    = 1,
 	KEYSTORE_BACKEND_PKCS11 = 2,
diff --git a/src/knot/conf/tools.c b/src/knot/conf/tools.c
index 1c3373f0f9fc9c26bd4e08e08aaf086f7cfcb9af..1eed87025894c0f5f760cb0c83b6b49fc502d86e 100644
--- a/src/knot/conf/tools.c
+++ b/src/knot/conf/tools.c
@@ -412,12 +412,31 @@ int check_remote(
 int check_template(
 	knotd_conf_check_args_t *args)
 {
+	conf_val_t val;
+
+	#define CHECK_LEGACY(old_item, new_item) \
+		val = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_TPL, \
+		                         old_item, args->id, args->id_len); \
+		if (val.code == KNOT_EOK) { \
+			CONF_LOG(LOG_NOTICE, "option 'template.%s' is obsolete, " \
+			                     "use option 'database.%s' instead", \
+			                     old_item + 1, new_item + 1); \
+		}
+
+
+	CHECK_LEGACY(C_TIMER_DB, C_TIMER_DB);
+	CHECK_LEGACY(C_MAX_TIMER_DB_SIZE, C_TIMER_DB_MAX_SIZE);
+	CHECK_LEGACY(C_JOURNAL_DB, C_JOURNAL_DB);
+	CHECK_LEGACY(C_JOURNAL_DB_MODE, C_JOURNAL_DB_MODE);
+	CHECK_LEGACY(C_MAX_JOURNAL_DB_SIZE, C_JOURNAL_DB_MAX_SIZE);
+	CHECK_LEGACY(C_KASP_DB, C_KASP_DB);
+	CHECK_LEGACY(C_MAX_KASP_DB_SIZE, C_KASP_DB_MAX_SIZE);
+
 	// Stop if the default template.
 	if (is_default_id(args->id, args->id_len)) {
 		return KNOT_EOK;
 	}
 
-	conf_val_t val;
 	#define CHECK_DFLT(item, name) \
 		val = conf_rawid_get_txn(args->extra->conf, args->extra->txn, C_TPL, \
 		                         item, args->id, args->id_len); \
@@ -427,13 +446,6 @@ int check_template(
 		}
 
 	CHECK_DFLT(C_GLOBAL_MODULE, "global module");
-	CHECK_DFLT(C_TIMER_DB, "timer database path");
-	CHECK_DFLT(C_MAX_TIMER_DB_SIZE, "timer database maximum size");
-	CHECK_DFLT(C_JOURNAL_DB, "journal database path");
-	CHECK_DFLT(C_JOURNAL_DB_MODE, "journal database mode");
-	CHECK_DFLT(C_MAX_JOURNAL_DB_SIZE, "journal database maximum size");
-	CHECK_DFLT(C_KASP_DB, "KASP database path");
-	CHECK_DFLT(C_MAX_KASP_DB_SIZE, "KASP database maximum size");
 
 	return KNOT_EOK;
 }
diff --git a/src/knot/nameserver/query_module.c b/src/knot/nameserver/query_module.c
index 88308fa35863e3493933e1ac9c77975d6f897303..4ea94a66b56f11665e7fc747331361a40b5ccad9 100644
--- a/src/knot/nameserver/query_module.c
+++ b/src/knot/nameserver/query_module.c
@@ -581,7 +581,7 @@ int knotd_mod_dnssec_init(knotd_mod_t *mod)
 	kaspdb = (knot_lmdb_db_t *)(mod->dnssec + 1);
 
 	char *kasp_dir = conf_db(mod->config, C_KASP_DB);
-	conf_val_t kasp_size = conf_default_get(mod->config, C_MAX_KASP_DB_SIZE);
+	conf_val_t kasp_size = conf_db_param(mod->config, C_KASP_DB_MAX_SIZE, C_MAX_KASP_DB_SIZE);
 	knot_lmdb_init(kaspdb, kasp_dir, conf_int(&kasp_size), 0, "keys_db");
 	free(kasp_dir);
 
diff --git a/src/knot/server/server.c b/src/knot/server/server.c
index f4700f921aab682377ad7d5d295b110cb2defe3f..b136509cbbe981fb478d861cde06a266ad4f0867 100644
--- a/src/knot/server/server.c
+++ b/src/knot/server/server.c
@@ -382,18 +382,18 @@ int server_init(server_t *server, int bg_workers)
 	}
 
 	char *journal_dir = conf_db(conf(), C_JOURNAL_DB);
-	conf_val_t journal_size = conf_default_get(conf(), C_MAX_JOURNAL_DB_SIZE);
-	conf_val_t journal_mode = conf_default_get(conf(), C_JOURNAL_DB_MODE);
+	conf_val_t journal_size = conf_db_param(conf(), C_JOURNAL_DB_MAX_SIZE, C_MAX_JOURNAL_DB_SIZE);
+	conf_val_t journal_mode = conf_db_param(conf(), C_JOURNAL_DB_MODE, C_JOURNAL_DB_MODE);
 	knot_lmdb_init(&server->journaldb, journal_dir, conf_int(&journal_size), journal_env_flags(conf_opt(&journal_mode)), NULL);
 	free(journal_dir);
 
 	char *kasp_dir = conf_db(conf(), C_KASP_DB);
-	conf_val_t kasp_size = conf_default_get(conf(), C_MAX_KASP_DB_SIZE);
+	conf_val_t kasp_size = conf_db_param(conf(), C_KASP_DB_MAX_SIZE, C_MAX_KASP_DB_SIZE);
 	knot_lmdb_init(&server->kaspdb, kasp_dir, conf_int(&kasp_size), 0, "keys_db");
 	free(kasp_dir);
 
 	char *timer_dir = conf_db(conf(), C_TIMER_DB);
-	conf_val_t timer_size = conf_default_get(conf(), C_MAX_TIMER_DB_SIZE);
+	conf_val_t timer_size = conf_db_param(conf(), C_TIMER_DB_MAX_SIZE, C_MAX_TIMER_DB_SIZE);
 	knot_lmdb_init(&server->timerdb, timer_dir, conf_int(&timer_size), 0, NULL);
 	free(timer_dir);
 
@@ -781,8 +781,8 @@ static int configure_threads(conf_t *conf, server_t *server)
 static int reconfigure_journal_db(conf_t *conf, server_t *server)
 {
 	char *journal_dir = conf_db(conf, C_JOURNAL_DB);
-	conf_val_t journal_size = conf_default_get(conf, C_MAX_JOURNAL_DB_SIZE);
-	conf_val_t journal_mode = conf_default_get(conf, C_JOURNAL_DB_MODE);
+	conf_val_t journal_size = conf_db_param(conf, C_JOURNAL_DB_MAX_SIZE, C_MAX_JOURNAL_DB_SIZE);
+	conf_val_t journal_mode = conf_db_param(conf, C_JOURNAL_DB_MODE, C_JOURNAL_DB_MODE);
 	int ret = knot_lmdb_reinit(&server->journaldb, journal_dir, conf_int(&journal_size),
 	                           journal_env_flags(conf_opt(&journal_mode)));
 	if (ret != KNOT_EOK) {
@@ -796,7 +796,7 @@ static int reconfigure_journal_db(conf_t *conf, server_t *server)
 static int reconfigure_kasp_db(conf_t *conf, server_t *server)
 {
 	char *kasp_dir = conf_db(conf, C_KASP_DB);
-	conf_val_t kasp_size = conf_default_get(conf, C_MAX_KASP_DB_SIZE);
+	conf_val_t kasp_size = conf_db_param(conf, C_KASP_DB_MAX_SIZE, C_MAX_KASP_DB_SIZE);
 	int ret = knot_lmdb_reinit(&server->kaspdb, kasp_dir, conf_int(&kasp_size), 0);
 	if (ret != KNOT_EOK) {
 		log_warning("ignored reconfiguration of KASP DB (%s)", knot_strerror(ret));
@@ -809,7 +809,7 @@ static int reconfigure_kasp_db(conf_t *conf, server_t *server)
 static int reconfigure_timer_db(conf_t *conf, server_t *server)
 {
 	char *timer_dir = conf_db(conf, C_TIMER_DB);
-	conf_val_t timer_size = conf_default_get(conf, C_MAX_TIMER_DB_SIZE);
+	conf_val_t timer_size = conf_db_param(conf, C_TIMER_DB_MAX_SIZE, C_MAX_TIMER_DB_SIZE);
 	int ret = knot_lmdb_reconfigure(&server->timerdb, timer_dir, conf_int(&timer_size), 0);
 	free(timer_dir);
 	return ret;
diff --git a/src/utils/keymgr/main.c b/src/utils/keymgr/main.c
index 88c94d1b7aedb9c3029c4effa517eb6518076be9..b3ecef48ec0b688d37cdb894044289c23c93c3f0 100644
--- a/src/utils/keymgr/main.c
+++ b/src/utils/keymgr/main.c
@@ -122,7 +122,7 @@ static int key_command(int argc, char *argv[], int opt_ind)
 	knot_lmdb_db_t kaspdb = { 0 };
 	kdnssec_ctx_t kctx = { 0 };
 
-	conf_val_t mapsize = conf_default_get(conf(), C_MAX_KASP_DB_SIZE);
+	conf_val_t mapsize = conf_db_param(conf(), C_KASP_DB_MAX_SIZE, C_MAX_KASP_DB_SIZE);
 	char *kasp_dir = conf_db(conf(), C_KASP_DB);
 	knot_lmdb_init(&kaspdb, kasp_dir, conf_int(&mapsize), 0, "keys_db");
 	free(kasp_dir);
diff --git a/tests-extra/tools/dnstest/server.py b/tests-extra/tools/dnstest/server.py
index e39ff3f898b4b48c7bc6c377b2e67a31b56f4fdc..ade73dacf4ccda68c7c0ab58547d14b4be452829 100644
--- a/tests-extra/tools/dnstest/server.py
+++ b/tests-extra/tools/dnstest/server.py
@@ -1231,15 +1231,19 @@ class Knot(Server):
         if have_policy:
             s.end()
 
+        s.begin("database")
+        s.item_str("storage", self.dir)
+        s.item_str("kasp-db", self.keydir)
+        s.item_str("kasp-db-max-size", self.kasp_db_size)
+        s.item_str("journal-db-max-size", self.journal_db_size)
+        s.item_str("timer-db-max-size", self.timer_db_size)
+        s.end()
+
         s.begin("template")
         s.id_item("id", "default")
         s.item_str("storage", self.dir)
         s.item_str("zonefile-sync", self.zonefile_sync)
-        s.item_str("kasp-db", self.keydir)
-        s.item_str("max-kasp-db-size", self.kasp_db_size)
-        s.item_str("max-journal-db-size", self.journal_db_size)
         s.item_str("max-journal-usage", self.max_journal_usage)
-        s.item_str("max-timer-db-size", self.timer_db_size)
         s.item_str("semantic-checks", "on" if self.semantic_check else "off")
         if self.disable_any:
             s.item_str("disable-any", "on")