diff --git a/.gitmodules b/.gitmodules
index 3fe5a7f033aff5ea06feebcc9290bb19f8667238..0705e3f49682503bc64771c3768d95969d31ece7 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -17,6 +17,6 @@
 	path = third_party/luafun
 	url = https://github.com/rtsisyk/luafun.git
 [submodule "sophia"]
-	path = third_party/sophia
-	url = https://github.com/pmwkaa/sophia.git
-	branch = dev
+       path = third_party/sophia
+       url = https://github.com/pmwkaa/sophia.git
+       branch = dev
diff --git a/extra/dist/default/tarantool b/extra/dist/default/tarantool
new file mode 100644
index 0000000000000000000000000000000000000000..3efafa35fe99bd8ec5450ad140cf5af840e87af4
--- /dev/null
+++ b/extra/dist/default/tarantool
@@ -0,0 +1,10 @@
+
+
+SNAPS=/var/lib/tarantool
+XLOGS=/var/lib/tarantool
+LOGS=/var/log/tarantool
+PIDS=/var/pid/tarantool
+USERNAME=tarantool
+
+
+# vim: set ft=sh :
diff --git a/extra/dist/dist.lua b/extra/dist/dist.lua
new file mode 100755
index 0000000000000000000000000000000000000000..533bef8d9d2b3c4755d57fdbcc9ddb85b405928c
--- /dev/null
+++ b/extra/dist/dist.lua
@@ -0,0 +1,216 @@
+#!env tarantool
+
+local DEFAULTS  = '/etc/default/tarantool'
+local DEFAULT_CFG = {
+    PIDS        = '/var/pid/tarantool',
+    SNAPS       = '/var/lib/tarantool',
+    XLOGS       = '/var/lib/tarantool',
+    LOGS        = '/var/log/tarantool',
+    USERNAME    = 'tarantool',
+}
+
+if os.getenv('TARANTOOL_DEFAULTS') ~= nil then
+    local nd = os.getenv('TARANTOOL_DEFAULTS')
+    if #nd then
+        DEFAULTS = nd
+    end
+end
+
+local fio = require 'fio'
+local log = require 'log'
+local errno = require 'errno'
+local yaml = require 'yaml'
+local console = require 'console'
+local socket = require 'socket'
+local ffi = require 'ffi'
+
+ffi.cdef[[ int kill(int pid, int sig); ]]
+
+
+function read_cfg(name)
+    if name == nil then
+        return {}
+    end
+    local stat = fio.stat(name)
+    if stat == nil then
+        log.error("Can't stat file %s: %s", name, errno.strerror())
+        return {}
+    end
+
+    local f = fio.open(name, 'O_RDONLY')
+    if f == nil then
+        log.error("Can't open file %s: %s", name, errno.strerror())
+        return {}
+    end
+
+    local data = f:read(32768)
+    if data == nil then
+        log.error("Can't read file %s: %s", name, errno.strerror())
+        f:close()
+        return {}
+    end
+    f:close()
+
+    local result = {}
+    repeat
+        local line
+        if string.match(data, "\n") == nil then
+            line = data
+            data = ''
+        else
+            line = string.match(data, "^(.-)\n")
+            data = string.sub(data, #line + 1 + 1)
+        end
+
+
+        local name, value = string.match(line, "^%s*(.-)%s*=%s*(.-)%s*$")
+
+        if name ~= nil and value ~= nil and #name > 0 and #value > 0 then
+            if string.match(value, '^".*"$') ~= nil then
+                value = string.sub(value, 2, #value - 2)
+            elseif string.match(value, "^'.*'$") ~= nil then
+                value = string.sub(value, 2, #value - 2)
+            end
+
+            if string.match(name, '^%s*#') == nil then
+                result[name] = value
+            end
+        end
+
+    until #data == 0
+
+    return result
+end
+
+if arg[1] == nil or arg[2] == nil then
+    log.error("Usage: dist.lua {start|stop} user_script.lua [ ... ]")
+    os.exit(-1)
+end
+
+
+local cfg = read_cfg(DEFAULTS)
+for i, v in pairs(DEFAULT_CFG) do
+    if cfg[i] == nil then
+        cfg[i] = v
+    end
+end
+
+local function mkdir(dirname)
+    log.info("mkdir %s", dirname)
+    if not fio.mkdir(dirname, tonumber('0750', 8)) then
+        log.error("Can't mkdir %s: %s", dirname, errno.strerror())
+        os.exit(-1)
+    end
+
+    if not fio.chown(dirname, cfg.USERNAME, cfg.USERNAME) then
+        log.error("Can't chown(%s, %s, %s): %s",
+            cfg.USERNAME, cfg.USERNAME, dirname, errno.strerror())
+    end
+end
+
+local cmd = arg[1]
+local main_lua = arg[2]
+for i = 0, 128 do
+    arg[i] = arg[i + 2]
+    if arg[i] == nil then
+        break
+    end
+end
+
+local instance = fio.basename(main_lua, '.lua')
+
+local force_cfg = {
+    pid_file    = fio.pathjoin(cfg.PIDS, instance .. '.pid'),
+    wal_dir     = fio.pathjoin(cfg.XLOGS, instance),
+    snap_dir    = fio.pathjoin(cfg.SNAPS, instance),
+    work_dir    = fio.pathjoin(cfg.XLOGS, instance),
+    username    = cfg.USERNAME,
+    logger      = fio.pathjoin(cfg.LOGS, instance .. '.log'),
+    console     = fio.pathjoin(cfg.PIDS, instance .. '.control'),
+    background  = true,
+}
+
+
+
+local orig_cfg = box.cfg
+box.cfg = function(cfg)
+    for i, v in pairs(force_cfg) do
+        cfg[i] = v
+    end
+    local res = orig_cfg(cfg)
+
+    log.info('Run console at %s', force_cfg.console)
+    console.listen(force_cfg.console)
+    
+    return res
+end
+
+
+if cmd == 'start' then
+    -- create PIDDIR
+    if fio.stat(cfg.PIDS) == nil then
+        mkdir(cfg.PIDS)
+    end
+
+    -- create xlogdir
+    if fio.stat(force_cfg.wal_dir) == nil then
+        mkdir(force_cfg.wal_dir)
+    end
+
+    -- create snapdir
+    if fio.stat(force_cfg.snap_dir) == nil then
+        mkdir(force_cfg.snap_dir)
+    end
+    dofile(main_lua)
+
+elseif cmd == 'stop' then
+    if fio.stat(force_cfg.pid_file) == nil then
+        log.error("Process is not running (pid: %s)", force_cfg.pid_file)
+        os.exit(-1)
+    end
+
+    local f = fio.open(force_cfg.pid_file, 'O_RDONLY')
+    if f == nil then
+        log.error("Can't read pid file %s: %s",
+            force_cfg.pid_file, errno.strerror())
+    end
+
+    local str = f:read(64)
+    f:close()
+
+    local pid = tonumber(str)
+
+    if pid == nil or pid <= 0 then
+        log.error("Broken pid file %s", force_cfg.pid_file)
+        fio.unlink(force_cfg.pid_file)
+        os.exit(-1)
+    end
+
+    if ffi.C.kill(pid, 15) < 0 then
+        log.error("Can't kill process %d: %s", pid, errno.strerror())
+        fio.unlink(force_cfg.pid_file)
+    end
+    os.exit(-1)
+
+elseif cmd == 'logrotate' then
+    if fio.stat(force_cfg.console) == nil then
+        log.error("Process is not running (pid: %s)", force_cfg.pid_file)
+        os.exit(-1)
+    end
+
+    local s = socket.tcp_connect('unix/', force_cfg.console)
+    if s == nil then
+        log.error("Can't connect to %s: %s",
+            force_cfg.console, errno.strerror())
+    end
+
+    s:write[[
+        require('log'):rotate()
+        require('log').info("Rotate log file")
+    ]]
+
+    os.exit(-1)
+else
+    log.error("Unknown command '%s'", cmd)
+    os.exit(-1)
+end
diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index a827e95026c59b53c14fea87538fa6790e496576..867cfb0404dc5ecf2a7cd491374fa98ba5d74569 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -7,6 +7,7 @@ lua_source(lua_sources lua/load_cfg.lua)
 lua_source(lua_sources lua/schema.lua)
 lua_source(lua_sources lua/tuple.lua)
 lua_source(lua_sources lua/session.lua)
+lua_source(lua_sources lua/snap_daemon.lua)
 set(bin_sources)
 bin_source(bin_sources bootstrap.snap bootstrap.h)
 
diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc
index 5e3a8171d5123973fed17118e62f7de992fdc3f4..9c3ab1c83086da95f8ced73b3e1a9af4df543189 100644
--- a/src/box/lua/call.cc
+++ b/src/box/lua/call.cc
@@ -51,8 +51,18 @@
 #include "box/schema.h"
 
 /* contents of box.lua, misc.lua, box.net.lua respectively */
-extern char session_lua[], schema_lua[], load_cfg_lua[];
-static const char *lua_sources[] = { session_lua, schema_lua, load_cfg_lua, NULL };
+extern char session_lua[],
+	schema_lua[],
+	load_cfg_lua[],
+	snap_daemon_lua[];
+
+static const char *lua_sources[] = {
+	session_lua,
+	schema_lua,
+	snap_daemon_lua,
+	load_cfg_lua,
+	NULL
+};
 
 /*
  * Functions, exported in box_lua.h should have prefix
diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
index 232e4d2d26223064dfb9933b65d361890f642831..39e0de7ddafd117750e569f85891410d98297424 100644
--- a/src/box/lua/load_cfg.lua
+++ b/src/box/lua/load_cfg.lua
@@ -50,6 +50,10 @@ local default_cfg = {
     background          = false,
     username            = nil ,
     coredump            = false,
+
+    -- snap_daemon
+    snapshot_period     = 3600 * 4,
+    snapshot_count      = 6,
 }
 
 -- dynamically settable options
@@ -60,6 +64,10 @@ local dynamic_cfg = {
     io_collect_interval     = ffi.C.box_set_io_collect_interval,
     too_long_threshold      = ffi.C.box_set_too_long_threshold,
     snap_io_rate_limit      = ffi.C.box_set_snap_io_rate_limit,
+
+    -- snap_daemon
+    snapshot_period         = box.internal.snap_daemon.set_snapshot_period,
+    snapshot_count          = box.internal.snap_daemon.set_snapshot_count,
 }
 
 local function reload_cfg(oldcfg, newcfg)
@@ -120,12 +128,14 @@ function box.cfg(cfg)
     box_configured = nil
     box.cfg = setmetatable(cfg,
         {
-		    __newindex = function(table, index)
-		        error('Attempt to modify a read-only table')
-		    end,
-            __call = reload_cfg
+            __newindex = function(table, index)
+                error('Attempt to modify a read-only table')
+            end,
+            __call = reload_cfg,
         })
     ffi.C.load_cfg()
+
+    box.internal.snap_daemon.start()
 end
 jit.off(box.cfg)
 jit.off(reload_cfg)
diff --git a/src/box/lua/snap_daemon.lua b/src/box/lua/snap_daemon.lua
new file mode 100644
index 0000000000000000000000000000000000000000..aec2d047bf133f23a2f4be2d9e0162b402de0b1f
--- /dev/null
+++ b/src/box/lua/snap_daemon.lua
@@ -0,0 +1,253 @@
+-- snap_daemon.lua (internal file)
+
+do
+    local log = require 'log'
+    local fiber = require 'fiber'
+    local fio = require 'fio'
+    local yaml = require 'yaml'
+    local errno = require 'errno'
+
+    local PREFIX = 'snap_daemon'
+
+    local daemon = { status = 'stopped' }
+
+    local function sprintf(fmt, ...) return string.format(fmt, ...) end
+
+    -- create snapshot, return true if no errors
+    local function snapshot()
+        log.info("%s: making snapshot...", PREFIX)
+        local s, e = pcall(function() box.snapshot() end)
+        if s then
+            return true
+        end
+        -- don't complain log if snapshot is already exists
+        if errno() == errno.EEXIST then
+            return false
+        end
+        log.error("%s: error while creating snapshot: %s", PREFIX, e)
+        return false
+    end
+
+    -- create snapshot by several options
+    local function make_snapshot(last_snap)
+
+        if box.cfg.snapshot_period == nil then
+            return false
+        end
+
+        if not(box.cfg.snapshot_period > 0) then
+            return false
+        end
+
+
+        if last_snap == nil then
+            return snapshot()
+        end
+
+        local vclock = box.info.vclock
+        local lsn = 0
+        for i, vlsn in pairs(vclock) do
+            lsn = lsn + vlsn
+        end
+
+        local snap_name = sprintf('%020d.snap', tonumber(lsn))
+        if fio.basename(last_snap) == snap_name then
+            log.debug('%s: snapshot %s is already exists', PREFIX, last_snap)
+            return false
+        end
+
+        local snstat = fio.stat(last_snap)
+        if snstat == nil then
+            log.error("%s: Can't stat %s: %s",
+                PREFIX, snaps[#snaps], errno.strerror())
+            return false
+        end
+        if snstat.mtime <= fiber.time() + box.cfg.snapshot_period then
+            return snapshot(snaps)
+        end
+    end
+
+    -- check filesystem and current time
+    local function process()
+        local snaps = fio.glob(fio.pathjoin(box.cfg.snap_dir, '*.snap'))
+
+        if snaps == nil then
+            log.error("%s: Can't read snap_dir: %s", PREFIX, errno.strerror())
+            return
+        end
+
+        if not make_snapshot(snaps[#snaps]) then
+            return
+        end
+
+        -- cleanup code
+        if box.cfg.snapshot_count == nil then
+            return
+        end
+
+        if not (box.cfg.snapshot_count > 0) then
+            return
+        end
+
+
+        -- reload snap list after snapshot
+        snaps = fio.glob(fio.pathjoin(box.cfg.snap_dir, '*.snap'))
+        local xlogs = fio.glob(fio.pathjoin(box.cfg.wal_dir, '*.xlog'))
+        if xlogs == nil then
+            log.error("%s: Can't read wal_dir: %s", PREFIX, errno.strerror())
+            return
+        end
+
+        while #snaps > box.cfg.snapshot_count do
+            local rm = snaps[1]
+            table.remove(snaps, 1)
+
+            log.info("%s: Removing old snapshot %s", PREFIX, rm)
+            if not fio.unlink(rm) then
+                log.error("%s: Error while removing %s: %s",
+                    PREFIX, rm, errno.strerror())
+                return
+            end
+        end
+
+
+        local snapno = fio.basename(snaps[1], '.snap')
+
+        while #xlogs > 0 do
+            if #xlogs < 2 then
+                break
+            end
+
+            if fio.basename(xlogs[1], '.xlog') > snapno then
+                break
+            end
+
+            if fio.basename(xlogs[2], '.xlog') > snapno then
+                break
+            end
+
+
+            local rm = xlogs[1]
+            table.remove(xlogs, 1)
+            log.info("%s: Removing old xlog %s", PREFIX, rm)
+
+            if not fio.unlink(rm) then
+                log.error("%s: Error while removing %s: %s",
+                    PREFIX, rm, errno.strerror())
+                return
+            end
+        end
+    end
+
+
+    local function next_snap_interval()
+        local interval
+        
+        if box.cfg.snapshot_period == nil or box.cfg.snapshot_period <= 0 then
+            return interval
+        end
+
+        local interval = box.cfg.snapshot_period / 10
+
+        local time = fiber.time()
+        local snaps = fio.glob(fio.pathjoin(box.cfg.snap_dir, '*.snap'))
+        if snaps == nil or #snaps == 0 then
+            return interval
+        end
+
+        local last_snap = snaps[ #snaps ]
+        local stat = fio.stat(last_snap)
+
+        if stat == nil then
+            return interval
+        end
+
+
+        -- there is no activity in xlogs
+        if box.cfg.snapshot_period * 2 + stat.mtime < time then
+            return interval
+        end
+
+        local time_left = box.cfg.snapshot_period + stat.mtime - time
+        if time_left > 0 then
+            return time_left
+        end
+
+        return interval
+
+    end
+
+    local function daemon_fiber(self)
+        fiber.name(PREFIX)
+        log.info("%s: status: %s", PREFIX, self.status)
+        while true do
+            local interval = next_snap_interval()
+            fiber.sleep(interval)
+            if self.status ~= 'started' then
+                break
+            end
+
+            local s, e = pcall(process)
+
+            if not s then
+                log.error("%s: %s", PREFIX, e)
+            end
+        end
+        log.info("%s: status: %s", PREFIX, self.status)
+        log.info("%s: finished daemon fiber", PREFIX)
+    end
+
+    setmetatable(daemon, {
+        __index = {
+            start = function()
+                local daemon = box.internal[PREFIX] or daemon
+                if daemon.status == 'started' then
+                    error(
+                        sprintf("%s: %s", PREFIX, "Daemon is already started"))
+                end
+                daemon.status = 'started'
+                daemon.fiber = fiber.create(daemon_fiber, daemon)
+            end,
+
+            stop = function()
+                local daemon = box.internal[PREFIX] or daemon
+                if daemon.status == 'stopped' then
+                    error(
+                        sprintf('%s: %s', PREFIX, "Daemon is already stopped"))
+                end
+                daemon.status = 'stopped'
+                if daemon.fiber ~= nil then
+                    daemon.fiber:wakeup()
+                end
+                daemon.fiber = nil
+            end,
+
+            set_snapshot_period = function(snapshot_period)
+                local daemon = box.internal[PREFIX] or daemon
+                log.info("%s: new snapshot_period: %s", PREFIX,
+                    tostring(snapshot_period))
+                if daemon.fiber ~= nil then
+                    daemon.fiber:wakeup()
+                end
+                return snapshot_period
+            end,
+
+            set_snapshot_count = function(snapshot_count)
+                local daemon = box.internal[PREFIX] or daemon
+                log.info("%s: new snapshot_count: %s",
+                    PREFIX, tostring(snapshot_count))
+
+                if daemon.fiber ~= nil then
+                    daemon.fiber:wakeup()
+                end
+                return snapshot_period
+            end
+        }
+    })
+
+    if box.internal == nil then
+        box.internal = { [PREFIX] = daemon }
+    else
+        box.internal[PREFIX] = daemon
+    end
+end
diff --git a/src/lua/fiber.cc b/src/lua/fiber.cc
index 216096a2845c7d8361eac0511ad37f4303dd0110..ee0eb3fa4b69be686c3065698f126f4d8f9833a8 100644
--- a/src/lua/fiber.cc
+++ b/src/lua/fiber.cc
@@ -505,12 +505,21 @@ lbox_fiber_time64(struct lua_State *L)
 	return 1;
 }
 
+static int
+lbox_fiber_wakeup(struct lua_State *L)
+{
+	struct fiber *f = lbox_checkfiber(L, 1);
+	fiber_wakeup(f);
+	return 0;
+}
+
 static const struct luaL_reg lbox_fiber_meta [] = {
 	{"id", lbox_fiber_id},
 	{"name", lbox_fiber_name},
 	{"cancel", lbox_fiber_cancel},
 	{"status", lbox_fiber_status},
 	{"testcancel", lbox_fiber_testcancel},
+	{"wakeup", lbox_fiber_wakeup},
 	{"__index", lbox_fiber_index},
 	{"__gc", lbox_fiber_gc},
 	{NULL, NULL}
diff --git a/src/lua/log.lua b/src/lua/log.lua
index fdad8836637dea08104a4d4c3e40e055eff822a1..8b37eab52196a980c223529b21f05e1acab8e19b 100644
--- a/src/lua/log.lua
+++ b/src/lua/log.lua
@@ -20,7 +20,16 @@ ffi.cdef[[
 ]]
 
 local function say(level, fmt, ...)
-    local str = string.format(fmt, ...)
+    local str
+   
+    args = { ... }
+    if not pcall(function() str = string.format(fmt, unpack(args)) end) then
+        str = fmt .. ' ['
+        for i = 1, select('#', ...) do
+            str = str .. select(i, ...) .. ', '
+        end
+        str = str .. ']'
+    end
     local frame = debug.getinfo(3, "Sl")
     local line = 0
     local file = 'eval'
diff --git a/test/app/cfg.result b/test/app/cfg.result
index 95ae0c7b722267ffe46671ff400ca4330fdbedb2..17f172e65116d1b808545b324c2852e5dffe3dc7 100644
--- a/test/app/cfg.result
+++ b/test/app/cfg.result
@@ -1,2 +1,2 @@
-false	[string "-- load_cfg.lua - internal file..."]:97: Please call box.cfg{} first
+false	[string "-- load_cfg.lua - internal file..."]:105: Please call box.cfg{} first
 true	table
diff --git a/test/app/float_value.result b/test/app/float_value.result
index e6c4ad1368da5444942e9d4c048530e8dce8584a..a590ec2707af2d28e27b03505d839c13886c13ac 100644
--- a/test/app/float_value.result
+++ b/test/app/float_value.result
@@ -1,23 +1,25 @@
 box.cfg
-1	pid_file:box.pid
-2	slab_alloc_factor:2
-3	rows_per_wal:50
-4	background:false
-5	logger:tarantool.log
-6	slab_alloc_arena:0.1
-7	wal_dir:.
-8	logger_nonblock:true
-9	snap_dir:.
-10	coredump:false
-11	wal_mode:write
-12	panic_on_snap_error:true
-13	panic_on_wal_error:false
-14	log_level:5
-15	admin:3313
+1	snapshot_count:6
+2	too_long_threshold:0.01
+3	slab_alloc_factor:2
+4	rows_per_wal:50
+5	background:false
+6	logger:tarantool.log
+7	slab_alloc_arena:0.1
+8	log_level:5
+9	logger_nonblock:true
+10	snap_dir:.
+11	coredump:false
+12	wal_mode:write
+13	pid_file:box.pid
+14	panic_on_snap_error:true
+15	panic_on_wal_error:false
 16	readahead:16320
-17	too_long_threshold:0.01
-18	slab_alloc_minimal:64
-19	wal_dir_rescan_delay:0.1
+17	admin:3313
+18	wal_dir:.
+19	snapshot_period:14400
+20	slab_alloc_minimal:64
+21	wal_dir_rescan_delay:0.1
 ------------------------------------------------------
 Check that too_long_threshold = 0.01
 0.01
diff --git a/test/app/init_script.result b/test/app/init_script.result
index ce6ab4aa1bf947f847f16d260135e17b143d1a27..54e60c1cf385cc8f3fbf63ce550a390baef5c085 100644
--- a/test/app/init_script.result
+++ b/test/app/init_script.result
@@ -3,26 +3,28 @@
 --
 
 box.cfg
-1	too_long_threshold:0.5
-2	slab_alloc_factor:2
-3	slab_alloc_minimal:64
-4	background:false
-5	logger:tarantool.log
-6	slab_alloc_arena:1
-7	wal_dir:.
-8	listen:3314
-9	logger_nonblock:true
-10	snap_dir:.
-11	coredump:false
-12	wal_mode:write
-13	panic_on_snap_error:true
-14	panic_on_wal_error:false
-15	pid_file:box.pid
-16	admin:3313
-17	rows_per_wal:500000
-18	readahead:16320
-19	log_level:5
-20	wal_dir_rescan_delay:0.1
+1	snapshot_count:6
+2	pid_file:box.pid
+3	slab_alloc_factor:2
+4	slab_alloc_minimal:64
+5	background:false
+6	logger:tarantool.log
+7	slab_alloc_arena:1
+8	log_level:5
+9	listen:3314
+10	logger_nonblock:true
+11	snap_dir:.
+12	coredump:false
+13	wal_mode:write
+14	too_long_threshold:0.5
+15	panic_on_snap_error:true
+16	panic_on_wal_error:false
+17	readahead:16320
+18	admin:3313
+19	rows_per_wal:500000
+20	wal_dir:.
+21	snapshot_period:14400
+22	wal_dir_rescan_delay:0.1
 --
 -- Test insert from detached fiber
 --
diff --git a/test/box/admin.result b/test/box/admin.result
index 717155b1c129d3a2ec15f484fbba8c965ab0da88..b6f0ca7529b7bbb64c390326dd7c35c59a37477e 100644
--- a/test/box/admin.result
+++ b/test/box/admin.result
@@ -18,11 +18,12 @@ help()
 ...
 box.cfg
 ---
-- too_long_threshold: 0.5
+- snapshot_count: 6
+  too_long_threshold: 0.5
   slab_alloc_factor: 2
-  rows_per_wal: 50
+  slab_alloc_minimal: 64
   background: false
-  readahead: 16320
+  slab_alloc_arena: 0.1
   log_level: 5
   listen: <uri>
   logger_nonblock: true
@@ -31,10 +32,11 @@ box.cfg
   wal_mode: write
   panic_on_snap_error: true
   panic_on_wal_error: false
-  slab_alloc_arena: 0.1
   pid_file: tarantool.pid
-  slab_alloc_minimal: 64
+  readahead: 16320
   wal_dir: .
+  snapshot_period: 14400
+  rows_per_wal: 50
   wal_dir_rescan_delay: 0.1
 ...
 space:insert{1, 'tuple'}
diff --git a/test/box/cfg.result b/test/box/cfg.result
index 3dee6fc5551bdfaa79ffb7f08b0d2918a904c10a..d5f4fefb435ad7fc83ce544d6320c4eec0e2bf8d 100644
--- a/test/box/cfg.result
+++ b/test/box/cfg.result
@@ -2,7 +2,7 @@
 --# push filter 'admin: .*' to 'admin: <uri>'
 box.cfg.nosuchoption = 1
 ---
-- error: '[string "-- load_cfg.lua - internal file..."]:124: Attempt to modify a read-only
+- error: '[string "-- load_cfg.lua - internal file..."]:132: Attempt to modify a read-only
     table'
 ...
 t = {} for k,v in pairs(box.cfg) do if type(v) ~= 'table' and type(v) ~= 'function' then table.insert(t, k..': '..tostring(v)) end end
@@ -10,11 +10,12 @@ t = {} for k,v in pairs(box.cfg) do if type(v) ~= 'table' and type(v) ~= 'functi
 ...
 t
 ---
-- - 'too_long_threshold: 0.5'
+- - 'snapshot_count: 6'
+  - 'too_long_threshold: 0.5'
   - 'slab_alloc_factor: 2'
-  - 'rows_per_wal: 50'
+  - 'slab_alloc_minimal: 64'
   - 'background: false'
-  - 'readahead: 16320'
+  - 'slab_alloc_arena: 0.1'
   - 'log_level: 5'
   - 'primary: <uri>
   - 'logger_nonblock: true'
@@ -23,10 +24,11 @@ t
   - 'wal_mode: write'
   - 'panic_on_snap_error: true'
   - 'panic_on_wal_error: false'
-  - 'slab_alloc_arena: 0.1'
   - 'pid_file: tarantool.pid'
-  - 'slab_alloc_minimal: 64'
+  - 'readahead: 16320'
   - 'wal_dir: .'
+  - 'snapshot_period: 14400'
+  - 'rows_per_wal: 50'
   - 'wal_dir_rescan_delay: 0.1'
 ...
 -- must be read-only
@@ -38,11 +40,12 @@ t = {} for k,v in pairs(box.cfg) do if type(v) ~= 'table' and type(v) ~= 'functi
 ...
 t
 ---
-- - 'too_long_threshold: 0.5'
+- - 'snapshot_count: 6'
+  - 'too_long_threshold: 0.5'
   - 'slab_alloc_factor: 2'
-  - 'rows_per_wal: 50'
+  - 'slab_alloc_minimal: 64'
   - 'background: false'
-  - 'readahead: 16320'
+  - 'slab_alloc_arena: 0.1'
   - 'log_level: 5'
   - 'primary: <uri>
   - 'logger_nonblock: true'
@@ -51,10 +54,11 @@ t
   - 'wal_mode: write'
   - 'panic_on_snap_error: true'
   - 'panic_on_wal_error: false'
-  - 'slab_alloc_arena: 0.1'
   - 'pid_file: tarantool.pid'
-  - 'slab_alloc_minimal: 64'
+  - 'readahead: 16320'
   - 'wal_dir: .'
+  - 'snapshot_period: 14400'
+  - 'rows_per_wal: 50'
   - 'wal_dir_rescan_delay: 0.1'
 ...
 --# clear filter
diff --git a/test/box/misc.result b/test/box/misc.result
index f144c5debf03902a95d1cf37a324d084b26a372d..59d1a1f60e010132f48ee7731552a3e2b23ba1fc 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -22,6 +22,7 @@ t
   - error
   - index
   - info
+  - internal
   - rollback
   - schema
   - session
diff --git a/test/box/snap_daemon.result b/test/box/snap_daemon.result
new file mode 100644
index 0000000000000000000000000000000000000000..164ef5994ff2b5cfd7c4484584aeed08f48d24c9
--- /dev/null
+++ b/test/box/snap_daemon.result
@@ -0,0 +1,110 @@
+fio = require 'fio'
+---
+...
+errno = require 'errno'
+---
+...
+fiber = require 'fiber'
+---
+...
+PERIOD = 0.03
+---
+...
+--# setopt delimiter ';'
+ffi = require 'ffi'
+ffi.cdef[[int uname(char *buf)]]
+
+function uname()
+    local name = ffi.new('char[?]', 4096)
+    ffi.C.uname(name)
+    return ffi.string(name)
+end
+
+if uname() ~= 'Linux' then
+    PERIOD = 1.5
+end
+
+---
+...
+--# setopt delimiter ''
+ffi = require 'ffi'
+ffi.cdef[[int uname(char *buf)]]
+
+function uname()
+    local name = ffi.new('char[?]', 4096)
+    ffi.C.uname(name)
+    return ffi.string(name)
+end
+
+if uname() ~= 'Linux' then
+    PERIOD = 1.5
+end
+
+
+---
+...
+space = box.schema.create_space('snap_daemon')
+---
+...
+space:create_index('pk', { type = 'tree', parts = { 1, 'num' }})
+---
+...
+box.cfg{snapshot_period = PERIOD, snapshot_count = 2 }
+---
+...
+no = 1
+---
+...
+-- first xlog
+for i = 1, box.cfg.rows_per_wal + 10 do space:insert { no } no = no + 1 end
+---
+...
+-- second xlog
+for i = 1, box.cfg.rows_per_wal + 10 do space:insert { no } no = no + 1 end
+---
+...
+-- wait for last snapshot
+fiber.sleep(1.5 * PERIOD)
+---
+...
+-- third xlog
+for i = 1, box.cfg.rows_per_wal + 10 do space:insert { no } no = no + 1 end
+---
+...
+-- fourth xlog
+for i = 1, box.cfg.rows_per_wal + 10 do space:insert { no } no = no + 1 end
+---
+...
+-- wait for last snapshot
+fiber.sleep(2.5 * PERIOD)
+---
+...
+snaps = fio.glob(fio.pathjoin(box.cfg.snap_dir, '*.snap'))
+---
+...
+xlogs = fio.glob(fio.pathjoin(box.cfg.wal_dir, '*.xlog'))
+---
+...
+#snaps == 2 or snaps
+---
+- true
+...
+#xlogs > 0
+---
+- true
+...
+fio.basename(snaps[1], '.snap') >= fio.basename(xlogs[1], '.xlog')
+---
+- true
+...
+-- restore default options
+box.cfg{snapshot_period = 3600 * 4, snapshot_count = 4 }
+---
+...
+space:drop()
+---
+...
+PERIOD
+---
+- 0.03
+...
diff --git a/test/box/snap_daemon.test.lua b/test/box/snap_daemon.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..82eb4385640416b351de2673113962dfc5060bfc
--- /dev/null
+++ b/test/box/snap_daemon.test.lua
@@ -0,0 +1,60 @@
+fio = require 'fio'
+errno = require 'errno'
+fiber = require 'fiber'
+
+
+PERIOD = 0.03
+
+--# setopt delimiter ';'
+
+ffi = require 'ffi'
+ffi.cdef[[int uname(char *buf)]]
+
+function uname()
+    local name = ffi.new('char[?]', 4096)
+    ffi.C.uname(name)
+    return ffi.string(name)
+end
+
+if uname() ~= 'Linux' then
+    PERIOD = 1.5
+end
+
+--# setopt delimiter ''
+
+
+space = box.schema.create_space('snap_daemon')
+space:create_index('pk', { type = 'tree', parts = { 1, 'num' }})
+
+
+box.cfg{snapshot_period = PERIOD, snapshot_count = 2 }
+
+
+no = 1
+-- first xlog
+for i = 1, box.cfg.rows_per_wal + 10 do space:insert { no } no = no + 1 end
+-- second xlog
+for i = 1, box.cfg.rows_per_wal + 10 do space:insert { no } no = no + 1 end
+-- wait for last snapshot
+fiber.sleep(1.5 * PERIOD)
+-- third xlog
+for i = 1, box.cfg.rows_per_wal + 10 do space:insert { no } no = no + 1 end
+-- fourth xlog
+for i = 1, box.cfg.rows_per_wal + 10 do space:insert { no } no = no + 1 end
+
+-- wait for last snapshot
+fiber.sleep(2.5 * PERIOD)
+
+snaps = fio.glob(fio.pathjoin(box.cfg.snap_dir, '*.snap'))
+xlogs = fio.glob(fio.pathjoin(box.cfg.wal_dir, '*.xlog'))
+
+#snaps == 2 or snaps
+#xlogs > 0
+
+fio.basename(snaps[1], '.snap') >= fio.basename(xlogs[1], '.xlog')
+
+-- restore default options
+box.cfg{snapshot_period = 3600 * 4, snapshot_count = 4 }
+space:drop()
+
+PERIOD