diff --git a/Knot.files b/Knot.files
index 8faa4dc8589f2fb38540d001adfbe54bdbf350ea..742fcd0d154c4f256b3caf13a779f4c33f985b26 100644
--- a/Knot.files
+++ b/Knot.files
@@ -258,10 +258,12 @@ src/knot/events/handlers/refresh.c
 src/knot/events/handlers/update.c
 src/knot/events/replan.c
 src/knot/events/replan.h
-src/knot/journal/serialization.c
-src/knot/journal/serialization.h
 src/knot/journal/journal.c
 src/knot/journal/journal.h
+src/knot/journal/old_journal.c
+src/knot/journal/old_journal.h
+src/knot/journal/serialization.c
+src/knot/journal/serialization.h
 src/knot/modules/dnsproxy/dnsproxy.c
 src/knot/modules/dnsproxy/dnsproxy.h
 src/knot/modules/dnstap/dnstap.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 8a8adc303a8afef57a7c24e6088093a16cf9f8a2..00b1f98951d36ba171858587970481df7b14ad1e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -337,6 +337,8 @@ libknotd_la_SOURCES =				\
 	knot/server/dthreads.h			\
 	knot/journal/journal.c			\
 	knot/journal/journal.h			\
+	knot/journal/old_journal.c		\
+	knot/journal/old_journal.h		\
 	knot/journal/serialization.c		\
 	knot/journal/serialization.h		\
 	knot/server/server.c			\
diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c
index c0e42472f3192c1712608bb0102efeeb892ce072..b5c6d57e9cd3a9b81e452c614890e66156b08527 100644
--- a/src/knot/conf/conf.c
+++ b/src/knot/conf/conf.c
@@ -1046,17 +1046,41 @@ char* conf_zonefile_txn(
 	return get_filename(conf, txn, zone, file);
 }
 
-char* conf_journalfile_txn(
+char* conf_old_journalfile(
 	conf_t *conf,
-	knot_db_txn_t *txn)
+	const knot_dname_t *zone)
 {
-	conf_val_t val;
+	if (zone == NULL) {
+		return NULL;
+	}
 
-	val = conf_default_get_txn(conf, txn, C_STORAGE);
-	char *storage = conf_abs_path(&val, NULL);
+	conf_val_t val = conf_zone_get(conf, C_JOURNAL, zone);
+	const char *journal = conf_str(&val);
 
-	val = conf_default_get_txn(conf, txn, C_JOURNAL);
+	// Use default journalfile name pattern if not specified.
+	if (journal == NULL) {
+		journal = "%s.db";
+	} else {
+		CONF_LOG_ZONE(LOG_NOTICE, zone, "obsolete configuration 'journal', "
+		              "use 'template.journal-db'' instead");
+	}
 
+	val = conf_zone_get(conf, C_MAX_JOURNAL_SIZE, zone);
+	if (val.code == KNOT_EOK) {
+		CONF_LOG_ZONE(LOG_NOTICE, zone, "obsolete configuration 'max-journal-size', "
+		              "use 'max-journal-usage' and 'template.journal-db' instead");
+	}
+
+	return get_filename(conf, &conf->read_txn, zone, journal);
+}
+
+char* conf_journalfile_txn(
+	conf_t *conf,
+	knot_db_txn_t *txn)
+{
+	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, C_JOURNAL_DB);
 	char *journaldir = conf_abs_path(&val, storage);
 	free(storage);
 
diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h
index cfdacad2a890dac453132d545b571932c066a5af..6cfa6cfa2f464a5c345c90ccaa028cb084881165 100644
--- a/src/knot/conf/conf.h
+++ b/src/knot/conf/conf.h
@@ -580,11 +580,11 @@ static inline char* conf_zonefile(
  *
  * \param[in] conf  Configuration.
  * \param[in] txn   Configuration DB transaction.
- * \param[in] zone  Zone name.
  *
  * \return Absolute journal file path string pointer.
  */
-char* conf_journalfile_txn(conf_t *conf,
+char* conf_journalfile_txn(
+	conf_t *conf,
 	knot_db_txn_t *txn);
 static inline char* conf_journalfile(
 	conf_t *conf)
@@ -592,6 +592,11 @@ static inline char* conf_journalfile(
 	return conf_journalfile_txn(conf, &conf->read_txn);
 }
 
+char* conf_old_journalfile(
+	conf_t *conf,
+	const knot_dname_t *zone
+);
+
 /*!
  * Gets the configured number of UDP threads.
  *
diff --git a/src/knot/conf/scheme.c b/src/knot/conf/scheme.c
index a6cc3fd65680d6418484b61acdc4ccb842553d06..7ae68fcd8d3dd12cd23f5f839dca85b04dacef52 100644
--- a/src/knot/conf/scheme.c
+++ b/src/knot/conf/scheme.c
@@ -45,11 +45,14 @@
 #define HOURS(x)	((x) * 3600)
 #define DAYS(x)		((x) * HOURS(24))
 
-#define GIGA		(1024LLU * 1024 * 1024)
-#define TERA		(1024 * GIGA)
+#define KILO(x)		(1024LLU * (x))
+#define MEGA(x)		(KILO(1024) * (x))
+#define GIGA(x)		(MEGA(1024) * (x))
+#define TERA(x)		(GIGA(1024) * (x))
 
-#define VIRT_MEM_TOP_32BIT (2 * GIGA)
-#define VIRT_MEM_LIMIT(x) (((sizeof(void *) < 8) && ((x) > VIRT_MEM_TOP_32BIT)) ? VIRT_MEM_TOP_32BIT : (x))
+#define VIRT_MEM_TOP_32BIT	GIGA(2)
+#define VIRT_MEM_LIMIT(x)	(((sizeof(void *) < 8) && ((x) > VIRT_MEM_TOP_32BIT)) \
+				 ? VIRT_MEM_TOP_32BIT : (x))
 
 #define FMOD		(YP_FMULTI | CONF_IO_FRLD_MOD | CONF_IO_FRLD_ZONES)
 
@@ -242,11 +245,9 @@ static const yp_item_t desc_remote[] = {
 	{ C_DISABLE_ANY,         YP_TBOOL, YP_VNONE }, \
 	{ C_ZONEFILE_SYNC,       YP_TINT,  YP_VINT = { -1, INT32_MAX, 0, YP_STIME } }, \
 	{ C_IXFR_DIFF,           YP_TBOOL, YP_VNONE }, \
-	{ C_MAX_ZONE_SIZE,       YP_TINT,  YP_VINT = { 0, INT64_MAX, INT64_MAX, YP_SSIZE }, \
-	                                   FLAGS }, \
-	{ C_MAX_JOURNAL_USAGE,   YP_TINT,  YP_VINT = { 40 * 1024, INT64_MAX, 100 * 1024 * 1024, \
-	                                               YP_SSIZE } }, \
-	{ C_MAX_JOURNAL_DEPTH,   YP_TINT,  YP_VINT = { 2, INT64_MAX, INT64_MAX, YP_SSIZE } }, \
+	{ C_MAX_ZONE_SIZE,       YP_TINT,  YP_VINT = { 0, INT64_MAX, INT64_MAX, YP_SSIZE }, FLAGS }, \
+	{ C_MAX_JOURNAL_USAGE,   YP_TINT,  YP_VINT = { KILO(40), INT64_MAX, MEGA(100), YP_SSIZE } }, \
+	{ C_MAX_JOURNAL_DEPTH,   YP_TINT,  YP_VINT = { 2, INT64_MAX, INT64_MAX } }, \
 	{ C_KASP_DB,             YP_TSTR,  YP_VSTR = { "keys" }, FLAGS }, \
 	{ C_DNSSEC_SIGNING,      YP_TBOOL, YP_VNONE, FLAGS }, \
 	{ C_DNSSEC_POLICY,       YP_TREF,  YP_VREF = { C_POLICY }, FLAGS, { check_ref_dflt } }, \
@@ -254,18 +255,21 @@ static const yp_item_t desc_remote[] = {
 	{ C_REQUEST_EDNS_OPTION, YP_TDATA, YP_VDATA = { 0, NULL, edns_opt_to_bin, edns_opt_to_txt } }, \
 	{ C_MODULE,              YP_TDATA, YP_VDATA = { 0, NULL, mod_id_to_bin, mod_id_to_txt }, \
 	                                   YP_FMULTI | FLAGS, { check_modref } }, \
-	{ C_COMMENT,             YP_TSTR,  YP_VNONE },
+	{ C_COMMENT,             YP_TSTR,  YP_VNONE }, \
+	/* Obsolete, old journal items. */ \
+	{ C_JOURNAL,             YP_TSTR,  YP_VNONE, FLAGS }, \
+	{ C_MAX_JOURNAL_SIZE,    YP_TINT,  YP_VINT = { 0, INT64_MAX, INT64_MAX, YP_SSIZE }, FLAGS }, \
 
 static const yp_item_t desc_template[] = {
 	{ C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF },
 	ZONE_ITEMS(CONF_IO_FRLD_ZONES)
-	{ C_TIMER_DB,            YP_TSTR,  YP_VSTR = { "timers" }, 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 } }, \
-	{ C_JOURNAL,             YP_TSTR,  YP_VSTR = { "journal.db" }, CONF_IO_FRLD_SRV }, \
-	{ C_MAX_JOURNAL_SIZE,    YP_TINT,  YP_VINT = { 1024 * 1024, VIRT_MEM_LIMIT(100 * TERA), \
-	                                               VIRT_MEM_LIMIT(20 * GIGA), YP_SSIZE }, \
-	                                               CONF_IO_FRLD_SRV }, \
+	{ C_TIMER_DB,            YP_TSTR,  YP_VSTR = { "timers" }, 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 } },
+	{ C_JOURNAL_DB,          YP_TSTR,  YP_VSTR = { "journal" }, CONF_IO_FRLD_SRV },
+	{ C_MAX_JOURNAL_DB_SIZE, YP_TINT,  YP_VINT = { JOURNAL_MIN_FSLIMIT, VIRT_MEM_LIMIT(TERA(100)),
+	                                               VIRT_MEM_LIMIT(GIGA(20)), YP_SSIZE },
+	                                               CONF_IO_FRLD_SRV },
 	{ NULL }
 };
 
diff --git a/src/knot/conf/scheme.h b/src/knot/conf/scheme.h
index 7deb2084d80cd41a2c74d6b09446f1dfea486beb..c81074efded3f4dcdd7ad0e44e4ca75ffeb8bf8c 100644
--- a/src/knot/conf/scheme.h
+++ b/src/knot/conf/scheme.h
@@ -53,7 +53,8 @@
 #define C_IDENT			"\x08""identity"
 #define C_INCL			"\x07""include"
 #define C_IXFR_DIFF		"\x15""ixfr-from-differences"
-#define C_JOURNAL		"\x07""journal"
+#define C_JOURNAL		"\x07""journal" /* obsolete, old journal compat */
+#define C_JOURNAL_DB		"\x0A""journal-db"
 #define C_KASP_DB		"\x07""kasp-db"
 #define C_KEY			"\x03""key"
 #define C_KEYSTORE		"\x08""keystore"
@@ -62,7 +63,8 @@
 #define C_LOG			"\x03""log"
 #define C_MANUAL		"\x06""manual"
 #define C_MASTER		"\x06""master"
-#define C_MAX_JOURNAL_SIZE	"\x10""max-journal-size"
+#define C_MAX_JOURNAL_SIZE	"\x10""max-journal-size" /* obsolete, old journal compat */
+#define C_MAX_JOURNAL_DB_SIZE	"\x13""max-journal-db-size"
 #define C_MAX_JOURNAL_USAGE	"\x11""max-journal-usage"
 #define C_MAX_JOURNAL_DEPTH	"\x11""max-journal-depth"
 #define C_MAX_TCP_CLIENTS	"\x0F""max-tcp-clients"
diff --git a/src/knot/conf/tools.c b/src/knot/conf/tools.c
index fef40d85cfe7c2dfc0a216badd1f7be82b427c36..a3c88f4a660ee814ef605876b3b7f04cd99f7504 100644
--- a/src/knot/conf/tools.c
+++ b/src/knot/conf/tools.c
@@ -495,42 +495,19 @@ int check_template(
 		return KNOT_EOK;
 	}
 
-	// Check global-module.
-	conf_val_t g_module = conf_rawid_get_txn(args->conf, args->txn, C_TPL,
-	                                         C_GLOBAL_MODULE, args->id,
-	                                         args->id_len);
-
-	if (g_module.code == KNOT_EOK) {
-		args->err_str = "global module in non-default template";
-		return KNOT_EINVAL;
-	}
-
-	// Check timer-db.
-	conf_val_t timer_db = conf_rawid_get_txn(args->conf, args->txn, C_TPL,
-	                                         C_TIMER_DB, args->id, args->id_len);
-
-	if (timer_db.code == KNOT_EOK) {
-		args->err_str = "timer database location in non-default template";
-		return KNOT_EINVAL;
-	}
-
-	// Check journal.
-	conf_val_t journal = conf_rawid_get_txn(args->conf, args->txn, C_TPL,
-	                                        C_JOURNAL, args->id, args->id_len);
-
-	if (journal.code == KNOT_EOK) {
-		args->err_str = "journal location in non-default template";
-		return KNOT_EINVAL;
-	}
-
-	// Check max-journal-size.
-	conf_val_t max_journal_size = conf_rawid_get_txn(args->conf, args->txn, C_TPL,
-	                                                 C_MAX_JOURNAL_SIZE, args->id, args->id_len);
+	conf_val_t val;
+	#define CHECK_DFLT(item, name) \
+		val = conf_rawid_get_txn(args->conf, args->txn, C_TPL, item, \
+		                         args->id, args->id_len); \
+		if (val.code == KNOT_EOK) { \
+			args->err_str = name " in non-default template"; \
+			return KNOT_EINVAL; \
+		}
 
-	if (max_journal_size.code == KNOT_EOK) {
-		args->err_str = "journal size in non-default template";
-		return KNOT_EINVAL;
-	}
+	CHECK_DFLT(C_TIMER_DB, "timer database");
+	CHECK_DFLT(C_GLOBAL_MODULE, "global module");
+	CHECK_DFLT(C_JOURNAL_DB, "journal database path");
+	CHECK_DFLT(C_MAX_JOURNAL_DB_SIZE, "journal database maximum size");
 
 	return KNOT_EOK;
 }
diff --git a/src/knot/journal/old_journal.c b/src/knot/journal/old_journal.c
new file mode 100644
index 0000000000000000000000000000000000000000..9fa273e2279a1ac1342d7fa6cfcd9a4a01cb3125
--- /dev/null
+++ b/src/knot/journal/old_journal.c
@@ -0,0 +1,479 @@
+/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <assert.h>
+
+#include "knot/common/log.h"
+#include "contrib/files.h"
+#include "knot/journal/old_journal.h"
+#include "knot/journal/serialization.h"
+#include "libknot/libknot.h"
+
+typedef enum {
+	JOURNAL_NULL  = 0 << 0, /*!< Invalid journal entry. */
+	JOURNAL_FREE  = 1 << 0, /*!< Free journal entry. */
+	JOURNAL_VALID = 1 << 1, /*!< Valid journal entry. */
+	JOURNAL_DIRTY = 1 << 2  /*!< Journal entry cannot be evicted. */
+} journal_flag_t;
+
+typedef struct {
+	uint64_t id;    /*!< Node ID. */
+	uint16_t flags; /*!< Node flags. */
+	uint16_t next;  /*!< UNUSED */
+	uint32_t pos;   /*!< Position in journal file. */
+	uint32_t len;   /*!< Entry data length. */
+} journal_node_t;
+
+typedef struct {
+	int fd;
+	char *path;             /*!< Path to journal file. */
+	uint16_t tmark;         /*!< Transaction start mark. */
+	uint16_t max_nodes;     /*!< Number of nodes. */
+	uint16_t qhead;         /*!< Node queue head. */
+	uint16_t qtail;         /*!< Node queue tail. */
+	uint16_t bflags;        /*!< Initial flags for each written node. */
+	size_t fsize;           /*!< Journal file size. */
+	size_t fslimit;         /*!< File size limit. */
+	journal_node_t free;    /*!< Free segment. */
+	journal_node_t *nodes;  /*!< Array of nodes. */
+} old_journal_t;
+
+#define JOURNAL_NCOUNT 1024 /*!< Default node count. */
+#define JOURNAL_MAGIC {'k', 'n', 'o', 't', '1', '5', '2'}
+#define MAGIC_LENGTH 7
+/* HEADER = magic, crc, max_entries, qhead, qtail */
+#define JOURNAL_HSIZE (MAGIC_LENGTH + sizeof(uint32_t) + sizeof(uint16_t) * 3)
+
+/*! \brief Infinite file size limit. */
+#define FSLIMIT_INF (~((size_t)0))
+
+/*! \brief Next node. */
+#define jnode_next(j, i) (((i) + 1) % (j)->max_nodes)
+
+/*! \brief Previous node. */
+#define jnode_prev(j, i) (((i) == 0) ? (j)->max_nodes - 1 : (i) - 1)
+
+/*! \bref Starting node data position. */
+#define jnode_base_pos(max_nodes) (JOURNAL_HSIZE + (max_nodes + 1) * sizeof(journal_node_t))
+
+static inline int sfread(void *dst, size_t len, int fd)
+{
+	return read(fd, dst, len) == len;
+}
+
+/*! \brief Return 'serial_from' part of the key. */
+static inline uint32_t journal_key_from(uint64_t k)
+{
+	return (uint32_t)(k & ((uint64_t)0x00000000ffffffff));
+}
+
+/*! \brief Compare function to match entries with starting serial. */
+static inline int journal_key_from_cmp(uint64_t k, uint64_t from)
+{
+	return ((uint64_t)journal_key_from(k)) - from;
+}
+
+/*! \brief Open journal file for r/w (returns error if not exists). */
+static int old_journal_open_file(old_journal_t *j)
+{
+	assert(j != NULL);
+
+	int ret = KNOT_EOK;
+	j->fd = open(j->path, O_RDWR);
+	if (j->fd < 0) {
+		return knot_map_errno();
+	}
+
+	/* File lock. */
+	struct flock lock = { .l_type = F_WRLCK, .l_whence = SEEK_SET,
+	                      .l_start  = 0, .l_len = 0, .l_pid = 0 };
+	/* Attempt to lock. */
+	ret = fcntl(j->fd, F_SETLKW, &lock);
+	if (ret < 0) {
+		return knot_map_errno();
+	}
+
+	/* Read magic bytes. */
+	const char magic_req[MAGIC_LENGTH] = JOURNAL_MAGIC;
+	char magic[MAGIC_LENGTH];
+	if (!sfread(magic, MAGIC_LENGTH, j->fd)) {
+		goto open_file_error;
+	}
+	if (memcmp(magic, magic_req, MAGIC_LENGTH) != 0) {
+		log_warning("old journal '%s', version too old", j->path);
+		close(j->fd);
+		j->fd = -1;
+		return KNOT_ENOTSUP;
+	}
+
+	/* Skip CRC */
+	if (lseek(j->fd, MAGIC_LENGTH + sizeof(uint32_t), SEEK_SET) < 0) {
+		goto open_file_error;
+	}
+
+	/* Get journal file size. */
+	struct stat st;
+	if (fstat(j->fd, &st) < 0) {
+		goto open_file_error;
+	}
+
+	/* Set file size. */
+	j->fsize = st.st_size;
+
+	/* Read maximum number of entries. */
+	if (!sfread(&j->max_nodes, sizeof(uint16_t), j->fd)) {
+		goto open_file_error;
+	}
+
+	/* Allocate nodes. */
+	const size_t node_len = sizeof(journal_node_t);
+	j->nodes = malloc(j->max_nodes * node_len);
+	if (j->nodes == NULL) {
+		goto open_file_error;
+	} else {
+		memset(j->nodes, 0, j->max_nodes * node_len);
+	}
+
+	/* Load node queue state. */
+	j->qhead = j->qtail = 0;
+	if (!sfread(&j->qhead, sizeof(uint16_t), j->fd)) {
+		goto open_file_error;
+	}
+
+	/* Load queue tail. */
+	if (!sfread(&j->qtail, sizeof(uint16_t), j->fd)) {
+		goto open_file_error;
+	}
+
+	/* Load empty segment descriptor. */
+	if (!sfread(&j->free, node_len, j->fd)) {
+		goto open_file_error;
+	}
+
+	/* Read journal descriptors table. */
+	if (!sfread(j->nodes, j->max_nodes * node_len, j->fd)) {
+		goto open_file_error;
+	}
+
+	/* Save file lock and return. */
+	return KNOT_EOK;
+
+	/* Unlock and close file and return error. */
+open_file_error:
+	free(j->nodes);
+	j->nodes = NULL;
+	close(j->fd);
+	j->fd = -1;
+	return KNOT_ERROR;
+}
+
+/*! \brief Close journal file. */
+static int old_journal_close_file(old_journal_t *journal)
+{
+	/* Close file. */
+	if (journal->fd > 0) {
+		close(journal->fd);
+		journal->fd = -1;
+	}
+
+	/* Free nodes. */
+	free(journal->nodes);
+	journal->nodes = NULL;
+
+	return KNOT_EOK;
+}
+
+static int old_journal_close(old_journal_t *journal)
+{
+	/* Check journal. */
+	if (journal == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	/* Close file. */
+	old_journal_close_file(journal);
+
+	/* Free allocated resources. */
+	free(journal->path);
+	free(journal);
+
+	return KNOT_EOK;
+}
+
+static int old_journal_open(old_journal_t **journal, const char *path, size_t fslimit)
+{
+	if (journal == NULL || path == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	old_journal_t *j = malloc(sizeof(*j));
+	if (j == NULL) {
+		return KNOT_ENOMEM;
+	}
+
+	memset(j, 0, sizeof(*j));
+	j->bflags = JOURNAL_DIRTY;
+	j->fd = -1;
+
+	j->fslimit = fslimit;
+
+	/* Copy path. */
+	j->path = strdup(path);
+	if (j->path == NULL) {
+		free(j);
+		return KNOT_ENOMEM;
+	}
+
+	/* Open journal file. */
+	int ret = old_journal_open_file(j);
+	if (ret != KNOT_EOK) {
+		log_error("old journal '%s', failed to open (%s)", path,
+		          knot_strerror(ret));
+		old_journal_close(j);
+		return ret;
+	}
+
+	*journal = j;
+
+	return KNOT_EOK;
+}
+
+typedef int (*journal_cmp_t)(uint64_t k1, uint64_t k2);
+
+static int old_journal_fetch(old_journal_t *journal, uint64_t id,
+		  journal_cmp_t cf, journal_node_t** dst)
+{
+	if (journal == NULL || dst == NULL) {
+		return KNOT_EINVAL;
+	}
+
+	size_t i = jnode_prev(journal, journal->qtail);
+	size_t endp = jnode_prev(journal, journal->qhead);
+	for(; i != endp; i = jnode_prev(journal, i)) {
+		journal_node_t *n = journal->nodes + i;
+
+		/* Skip invalid nodes. */
+		if (!(n->flags & JOURNAL_VALID)) {
+			continue;
+		}
+
+		if (cf(n->id, id) == 0) {
+			*dst = journal->nodes + i;
+			return KNOT_EOK;
+		}
+	}
+
+	return KNOT_ENOENT;
+}
+
+static int old_journal_read_node(old_journal_t *journal, journal_node_t *n, char *dst)
+{
+	/* Check valid flag. */
+	if (!(n->flags & JOURNAL_VALID)) {
+		return KNOT_EINVAL;
+	}
+
+	/* Seek journal node. */
+	int seek_ret = lseek(journal->fd, n->pos, SEEK_SET);
+
+	/* Read journal node content. */
+	if (seek_ret < 0 || !sfread(dst, n->len, journal->fd)) {
+		return KNOT_ERROR;
+	}
+
+	return KNOT_EOK;
+}
+
+bool old_journal_exists(const char *path)
+{
+	if (path == NULL) {
+		return false;
+	}
+	struct stat st;
+	return stat(path, &st) == 0;
+}
+
+/*! \brief No doc here. Moved from zones.h (@mvavrusa) */
+static int changesets_unpack(changeset_t *chs)
+{
+	/* Read changeset flags. */
+	if (chs->data == NULL) {
+		return KNOT_EMALF;
+	}
+	size_t remaining = chs->size;
+
+	/* Read initial changeset RRSet - SOA. */
+	uint8_t *stream = chs->data + (chs->size - remaining);
+	knot_rrset_t rrset;
+	int ret = rrset_deserialize(stream, &remaining, &rrset);
+	if (ret != KNOT_EOK) {
+		return KNOT_EMALF;
+	}
+
+	assert(rrset.type == KNOT_RRTYPE_SOA);
+	chs->soa_from = knot_rrset_copy(&rrset, NULL);
+	knot_rrset_clear(&rrset, NULL);
+	if (chs->soa_from == NULL) {
+		return KNOT_ENOMEM;
+	}
+
+	/* Read remaining RRSets */
+	bool in_remove_section = true;
+	while (remaining > 0) {
+
+		/* Parse next RRSet. */
+		stream = chs->data + (chs->size - remaining);
+		knot_rrset_init_empty(&rrset);
+		ret = rrset_deserialize(stream, &remaining, &rrset);
+		if (ret != KNOT_EOK) {
+			return KNOT_EMALF;
+		}
+
+		/* Check for next SOA. */
+		if (rrset.type == KNOT_RRTYPE_SOA) {
+			/* Move to ADD section if in REMOVE. */
+			if (in_remove_section) {
+				chs->soa_to = knot_rrset_copy(&rrset, NULL);
+				if (chs->soa_to == NULL) {
+					ret = KNOT_ENOMEM;
+					break;
+				}
+				in_remove_section = false;
+			} else {
+				/* Final SOA, no-op. */
+				;
+			}
+		} else {
+			/* Remove RRSets. */
+			if (in_remove_section) {
+				ret = changeset_add_removal(chs, &rrset, 0);
+			} else {
+				/* Add RRSets. */
+				ret = changeset_add_addition(chs, &rrset, 0);
+			}
+		}
+		knot_rrset_clear(&rrset, NULL);
+		if (ret != KNOT_EOK) {
+			break;
+		}
+	}
+	return ret;
+}
+
+/*! \brief Helper for iterating journal (this is temporary until #80) */
+typedef int (*journal_apply_t)(old_journal_t *, journal_node_t *, const knot_dname_t *, list_t *);
+static int old_journal_walk(const char *fn, uint32_t from, uint32_t to,
+			journal_apply_t cb, const knot_dname_t *zone, list_t *chgs)
+{
+	/* Open journal for reading. */
+	old_journal_t *journal = NULL;
+	int ret = old_journal_open(&journal, fn, FSLIMIT_INF);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+	/* Read entries from starting serial until finished. */
+	uint32_t found_to = from;
+	journal_node_t *n = 0;
+	ret = old_journal_fetch(journal, from, journal_key_from_cmp, &n);
+	if (ret != KNOT_EOK) {
+		goto finish;
+	}
+
+	size_t i = n - journal->nodes;
+	assert(i < journal->max_nodes);
+
+	for (; i != journal->qtail; i = jnode_next(journal, i)) {
+		journal_node_t *n = journal->nodes + i;
+
+		if (!(n->flags & JOURNAL_VALID)) {
+			continue;
+		}
+		if (to == found_to) {
+			break;
+		}
+		ret = cb(journal, n, zone, chgs);
+		if (ret != KNOT_EOK) {
+			break;
+		}
+	}
+
+finish:
+	old_journal_close(journal);
+	return ret;
+}
+
+static int load_changeset(old_journal_t *journal, journal_node_t *n,
+                          const knot_dname_t *zone, list_t *chgs)
+{
+	changeset_t *ch = changeset_new(zone);
+	if (ch == NULL) {
+		return KNOT_ENOMEM;
+	}
+
+	/* Initialize changeset. */
+	ch->data = malloc(n->len);
+	if (!ch->data) {
+		return KNOT_ENOMEM;
+	}
+
+	/* Read journal entry. */
+	int ret = old_journal_read_node(journal, n, (char*)ch->data);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	/* Update changeset binary size. */
+	ch->size = n->len;
+
+	/* Insert into changeset list. */
+	add_tail(chgs, &ch->n);
+
+	return KNOT_EOK;
+}
+
+int old_journal_load_changesets(const char *path, const knot_dname_t *zone,
+                                list_t *dst, uint32_t from, uint32_t to)
+{
+	int ret = old_journal_walk(path, from, to, &load_changeset, zone, dst);
+	if (ret != KNOT_EOK) {
+		return ret;
+	}
+
+	assert(dst != NULL);
+
+	changeset_t* chs = NULL;
+	WALK_LIST(chs, *dst) {
+		ret = changesets_unpack(chs);
+		if (ret != KNOT_EOK) {
+			return ret;
+		}
+	}
+
+	/* Check for complete history. */
+	changeset_t *last = TAIL(*dst);
+	if (to != knot_soa_serial(&last->soa_to->rrs)) {
+		return KNOT_ERANGE;
+	}
+
+	return KNOT_EOK;
+}
diff --git a/src/knot/journal/old_journal.h b/src/knot/journal/old_journal.h
new file mode 100644
index 0000000000000000000000000000000000000000..57e07dda69c5d0f0194f71e75fa1932c490d4e89
--- /dev/null
+++ b/src/knot/journal/old_journal.h
@@ -0,0 +1,46 @@
+/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include "contrib/ucw/lists.h"
+
+/*!
+ * \brief Check if the journal file is used or not.
+ *
+ * \param path Journal file.
+ *
+ * \return true or false
+ */
+bool old_journal_exists(const char *path);
+
+/*!
+ * \brief Load changesets from journal.
+ *
+ * \param path Path to journal file.
+ * \param zone Corresponding zone.
+ * \param dst Store changesets here.
+ * \param from Start serial.
+ * \param to End serial.
+ *
+ * \retval KNOT_EOK on success.
+ * \retval KNOT_ERANGE if given entry was not found.
+ * \return < KNOT_EOK on error.
+ */
+int old_journal_load_changesets(const char *path, const knot_dname_t *zone,
+                                list_t *dst, uint32_t from, uint32_t to);
diff --git a/src/knot/server/server.c b/src/knot/server/server.c
index 68f78c935a3424d7e4f1edec5afce0e1f1fc4720..3a768c756f1b4c2e5d6d3b25c7b3e931f1f1f427 100644
--- a/src/knot/server/server.c
+++ b/src/knot/server/server.c
@@ -380,7 +380,7 @@ int server_init(server_t *server, int bg_workers)
 	}
 
 	char * journal_dir = conf_journalfile(conf());
-	conf_val_t journal_size = conf_default_get(conf(), C_MAX_JOURNAL_SIZE);
+	conf_val_t journal_size = conf_default_get(conf(), C_MAX_JOURNAL_DB_SIZE);
 	int ret = init_journal_db(&server->journal_db, journal_dir, conf_int(&journal_size));
 	free(journal_dir);
 	if (ret != KNOT_EOK) {
diff --git a/src/knot/zone/zone-load.c b/src/knot/zone/zone-load.c
index d384d983d830c3610da2a99751601ba98c18f83c..0c86935f7cd6b9883122c0bc6c7c7fe15c3882a1 100644
--- a/src/knot/zone/zone-load.c
+++ b/src/knot/zone/zone-load.c
@@ -16,6 +16,7 @@
 
 #include "knot/common/log.h"
 #include "knot/journal/journal.h"
+#include "knot/journal/old_journal.h"
 #include "knot/zone/zone-diff.h"
 #include "knot/zone/zone-load.h"
 #include "knot/zone/zonefile.h"
@@ -90,6 +91,68 @@ int zone_load_check(conf_t *conf, zone_contents_t *contents)
 	return KNOT_EOK;
 }
 
+/*!
+ * \brief If old journal exists, warn the user and append the changes to chgs
+ *
+ * \todo Remove in the future together with journal/old_journal.[ch] and conf_old_journalfile()
+ */
+static void try_old_journal(conf_t *conf, zone_t *zone, uint32_t zone_c_serial, list_t *chgs)
+{
+	list_t old_chgs;
+	init_list(&old_chgs);
+
+	// fetch old journal name
+	char *jfile = conf_old_journalfile(conf, zone->name);
+	if (jfile == NULL) {
+		return;
+	}
+
+	if (!old_journal_exists(jfile)) {
+		goto toj_end;
+	}
+	log_zone_notice(zone->name, "journal, obsolete exists, file '%s'", jfile);
+
+	// determine serial to load from
+	if (!EMPTY_LIST(*chgs)) {
+		changeset_t * lastch = TAIL(*chgs);
+		zone_c_serial = knot_soa_serial(&lastch->soa_to->rrs);
+	}
+
+	// load changesets from old journal
+	int ret = old_journal_load_changesets(jfile, zone->name, &old_chgs,
+	                                  zone_c_serial, zone_c_serial - 1);
+	if (ret != KNOT_ERANGE && ret != KNOT_ENOENT && ret != KNOT_EOK) {
+		log_zone_warning(zone->name, "journal, failed to load obsolete history (%s)",
+		                 knot_strerror(ret));
+		goto toj_end;
+	}
+
+	if (EMPTY_LIST(old_chgs)) {
+		goto toj_end;
+	}
+	log_zone_notice(zone->name, "journal, loaded obsolete history since serial '%u'",
+	                zone_c_serial);
+
+	// store them to new journal
+	ret = zone_changes_store(conf, zone, &old_chgs);
+	if (ret != KNOT_EOK) {
+		log_zone_warning(zone->name, "journal, failed to store obsolete history (%s)",
+		                 knot_strerror(ret));
+		goto toj_end;
+	}
+
+	// append them to chgs
+	changeset_t *ch, *nxt;
+	WALK_LIST_DELSAFE(ch, nxt, old_chgs) {
+		rem_node(&ch->n);
+		add_tail(chgs, &ch->n);
+	}
+
+toj_end:
+	changesets_free(&old_chgs);
+	free(jfile);
+}
+
 int zone_load_journal(conf_t *conf, zone_t *zone, zone_contents_t *contents)
 {
 	if (conf == NULL || zone == NULL || contents == NULL) {
@@ -104,19 +167,20 @@ int zone_load_journal(conf_t *conf, zone_t *zone, zone_contents_t *contents)
 	/* Fetch SOA serial. */
 	uint32_t serial = zone_contents_serial(contents);
 
-	/*! \todo Check what should be the upper bound. */
+	/* Load journal */
 	list_t chgs;
 	init_list(&chgs);
-
 	int ret = zone_changes_load(conf, zone, &chgs, serial);
-	if (ret != KNOT_EOK) {
+	if (ret != KNOT_EOK && ret != KNOT_ENOENT) {
 		changesets_free(&chgs);
-		/* Absence of records is not an error. */
-		if (ret == KNOT_ENOENT) {
-			return KNOT_EOK;
-		} else {
-			return ret;
-		}
+		return ret;
+	}
+
+	/* Load old journal (to be obsoleted) */
+	try_old_journal(conf, zone, serial, &chgs);
+
+	if (EMPTY_LIST(chgs)) {
+		return KNOT_EOK;
 	}
 
 	/* Apply changesets. */
diff --git a/tests-extra/tools/dnstest/server.py b/tests-extra/tools/dnstest/server.py
index 1e12368fadee604bb0f8af50857c74ddf1a3fe7b..165e383a0f167e59c6121aadc74679161ea347f4 100644
--- a/tests-extra/tools/dnstest/server.py
+++ b/tests-extra/tools/dnstest/server.py
@@ -118,8 +118,8 @@ class Server(object):
         self.max_udp6_payload = None
         self.disable_any = None
         self.disable_notify = None
-        self.zonefile_sync = None
-        self.journal_size = 20 * 1024 * 1024
+        self.zonefile_sync = "1d"
+        self.journal_db_size = 20 * 1024 * 1024
         self.zone_size_limit = None
 
         self.inquirer = None
@@ -1044,10 +1044,8 @@ class Knot(Server):
         s.id_item("id", "default")
         s.item_str("storage", self.dir)
         s.item_str("kasp-db", self.keydir)
-        if self.zonefile_sync:
-            s.item_str("zonefile-sync", self.zonefile_sync)
-        else:
-            s.item_str("zonefile-sync", "1d")
+        s.item_str("zonefile-sync", self.zonefile_sync)
+        s.item_str("max-journal-db-size", self.journal_db_size)
         s.item_str("semantic-checks", "on")
         if self.disable_any:
             s.item_str("disable-any", "on")
@@ -1060,7 +1058,6 @@ class Knot(Server):
             s.item("global-module", "[%s]" % modules)
         if self.zone_size_limit:
             s.item("max-zone-size", self.zone_size_limit)
-        s.item_str("max-journal-usage", self.journal_size)
         s.end()
 
         s.begin("zone")