diff --git a/.travis.yml b/.travis.yml
index 07a336ef4..4610da83b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -59,7 +59,7 @@ install:
   - git clone https://github.com/openresty/openresty.git ../openresty
   - git clone https://github.com/openresty/openresty-devel-utils.git
   - git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module
-  - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module
+  - git clone -b feat/incr-exptime-ffi https://github.com/thibaultcha/lua-nginx-module.git ../lua-nginx-module
   - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx
   - git clone https://github.com/openresty/echo-nginx-module.git ../echo-nginx-module
   - git clone https://github.com/openresty/lua-resty-lrucache.git
diff --git a/lib/resty/core/shdict.lua b/lib/resty/core/shdict.lua
index b9fc0447d..55f2a88a9 100644
--- a/lib/resty/core/shdict.lua
+++ b/lib/resty/core/shdict.lua
@@ -29,7 +29,7 @@ ffi.cdef[[
 
     int ngx_http_lua_ffi_shdict_incr(void *zone, const unsigned char *key,
         size_t key_len, double *value, char **err, int has_init, double init,
-        int *forcible);
+        long init_ttl, int *forcible);
 
     int ngx_http_lua_ffi_shdict_store(void *zone, int op,
         const unsigned char *key, size_t key_len, int value_type,
@@ -337,7 +337,7 @@ local function shdict_get_stale(zone, key)
 end
 
 
-local function shdict_incr(zone, key, value, init)
+local function shdict_incr(zone, key, value, init, init_ttl)
     zone = check_zone(zone)
 
     if key == nil then
@@ -361,8 +361,6 @@ local function shdict_incr(zone, key, value, init)
     end
     num_value[0] = value
 
-    local has_init
-
     if init then
         local typ = type(init)
         if typ ~= "number" then
@@ -372,22 +370,39 @@ local function shdict_incr(zone, key, value, init)
                 return error("bad init arg: number expected, got " .. typ)
             end
         end
+    end
 
-        has_init = 1
+    if init_ttl ~= nil then
+        local typ = type(init_ttl)
+        if typ ~= "number" then
+            init_ttl = tonumber(init_ttl)
+
+            if not init_ttl then
+                error("bad init_ttl arg: number expected, got " .. typ, 2)
+            end
+        end
+
+        if init_ttl < 0 then
+            error('bad "init_ttl" argument', 2)
+        end
+
+        if not init then
+            error('must provide "init" when providing "init_ttl"', 2)
+        end
 
     else
-        has_init = 0
-        init = 0
+        init_ttl = 0
     end
 
     local rc = C.ngx_http_lua_ffi_shdict_incr(zone, key, key_len, num_value,
-                                              errmsg, has_init, init,
+                                              errmsg, init and 1 or 0,
+                                              init or 0, init_ttl * 1000,
                                               forcible)
     if rc ~= 0 then  -- ~= NGX_OK
         return nil, ffi_str(errmsg[0])
     end
 
-    if has_init == 0 then
+    if not init then
         return tonumber(num_value[0])
     end
 
diff --git a/t/shdict.t b/t/shdict.t
index 550ecdfd6..4279409f5 100644
--- a/t/shdict.t
+++ b/t/shdict.t
@@ -1284,3 +1284,247 @@ free_page_bytes: (?:0|32768)
 [error]
 [alert]
 [crit]
+
+
+
+=== TEST 39: incr bad init_ttl argument
+--- http_config eval: $::HttpConfig
+--- config
+    location = /t {
+        content_by_lua_block {
+            local dogs = ngx.shared.dogs
+            local pok, err = pcall(dogs.incr, dogs, "foo", 1, 0, -1)
+            if not pok then
+                ngx.say("not ok: ", err)
+                return
+            end
+
+            ngx.say("ok")
+        }
+    }
+--- request
+GET /t
+--- response_body
+not ok: bad "init_ttl" argument
+--- no_error_log
+[error]
+[alert]
+[crit]
+
+
+
+=== TEST 40: incr init_ttl argument is not a number
+--- http_config eval: $::HttpConfig
+--- config
+    location = /t {
+        content_by_lua_block {
+            local dogs = ngx.shared.dogs
+            local pok, err = pcall(dogs.incr, dogs, "foo", 1, 0, "bar")
+            if not pok then
+                ngx.say("not ok: ", err)
+                return
+            end
+
+            ngx.say("ok")
+        }
+    }
+--- request
+GET /t
+--- response_body
+not ok: bad init_ttl arg: number expected, got string
+--- no_error_log
+[error]
+[alert]
+[crit]
+
+
+
+=== TEST 41: incr init_ttl argument without init
+--- http_config eval: $::HttpConfig
+--- config
+    location = /t {
+        content_by_lua_block {
+            local dogs = ngx.shared.dogs
+            local pok, err = pcall(dogs.incr, dogs, "foo", 1, nil, 0.001)
+            if not pok then
+                ngx.say("not ok: ", err)
+                return
+            end
+
+            ngx.say("ok")
+        }
+    }
+--- request
+GET /t
+--- response_body
+not ok: must provide "init" when providing "init_ttl"
+--- no_error_log
+[error]
+[alert]
+[crit]
+
+
+
+=== TEST 42: incr key with init_ttl (key exists)
+--- http_config eval: $::HttpConfig
+--- config
+    location = /t {
+        content_by_lua_block {
+            local dogs = ngx.shared.dogs
+            dogs:set("foo", 32)
+
+            local res, err = dogs:incr("foo", 10502, 0, 0.001)
+            ngx.say("incr: ", res, " ", err)
+            ngx.say("foo = ", dogs:get("foo"))
+
+            ngx.sleep(0.002)
+
+            ngx.say("foo after incr init_ttl = ", dogs:get("foo"))
+        }
+    }
+--- request
+GET /t
+--- response_body
+incr: 10534 nil
+foo = 10534
+foo after incr init_ttl = 10534
+--- no_error_log
+[error]
+[alert]
+[crit]
+
+
+
+=== TEST 43: incr key with init and init_ttl (key not exists)
+--- http_config eval: $::HttpConfig
+--- config
+    location = /t {
+        content_by_lua_block {
+            local dogs = ngx.shared.dogs
+            dogs:flush_all()
+
+            local res, err = dogs:incr("foo", 10502, 1, 0.001)
+            ngx.say("incr: ", res, " ", err)
+            ngx.say("foo = ", dogs:get("foo"))
+
+            ngx.sleep(0.002)
+
+            ngx.say("foo after init_ttl = ", dogs:get("foo"))
+        }
+    }
+--- request
+GET /t
+--- response_body
+incr: 10503 nil
+foo = 10503
+foo after init_ttl = nil
+--- no_error_log
+[error]
+[alert]
+[crit]
+
+
+
+=== TEST 44: incr key with init and init_ttl as string (key not exists)
+--- http_config eval: $::HttpConfig
+--- config
+    location = /t {
+        content_by_lua_block {
+            local dogs = ngx.shared.dogs
+            dogs:flush_all()
+
+            local res, err = dogs:incr("foo", 10502, 1, "0.001")
+            ngx.say("incr: ", res, " ", err)
+            ngx.say("foo = ", dogs:get("foo"))
+
+            ngx.sleep(0.002)
+
+            ngx.say("foo after init_ttl = ", dogs:get("foo"))
+        }
+    }
+--- request
+GET /t
+--- response_body
+incr: 10503 nil
+foo = 10503
+foo after init_ttl = nil
+--- no_error_log
+[error]
+[alert]
+[crit]
+
+
+
+=== TEST 45: incr key with init and init_ttl (key expired and size matched)
+--- http_config eval: $::HttpConfig
+--- config
+    location = /t {
+        content_by_lua_block {
+            local dogs = ngx.shared.dogs
+            for i = 1, 20 do
+                dogs:set("bar" .. i, i, 0.002)
+            end
+            dogs:set("foo", 32, 0.002)
+            ngx.sleep(0.003)
+
+            local res, err = dogs:incr("foo", 10502, 0, 0.001)
+            ngx.say("incr: ", res, " ", err)
+            ngx.say("foo = ", dogs:get("foo"))
+
+            ngx.sleep(0.002)
+
+            ngx.say("foo after init_ttl = ", dogs:get("foo"))
+        }
+    }
+--- request
+GET /t
+--- response_body
+incr: 10502 nil
+foo = 10502
+foo after init_ttl = nil
+--- no_error_log
+[error]
+[alert]
+[crit]
+
+
+
+=== TEST 46: incr key with init and init_ttl (forcibly override other valid entries)
+--- http_config eval: $::HttpConfig
+--- config
+    location = /t {
+        content_by_lua_block {
+            local dogs = ngx.shared.dogs
+            dogs:flush_all()
+
+            local long_prefix = string.rep("1234567890", 100)
+            for i = 1, 1000 do
+                local success, err, forcible = dogs:set(long_prefix .. i, i)
+                if forcible then
+                    dogs:delete(long_prefix .. i)
+                    break
+                end
+            end
+
+            local res, err, forcible = dogs:incr(long_prefix .. "bar", 10502, 0)
+            ngx.say("incr: ", res, " ", err, " ", forcible)
+
+            local res, err, forcible = dogs:incr(long_prefix .. "foo", 10502, 0, 0.001)
+            ngx.say("incr: ", res, " ", err, " ", forcible)
+            ngx.say("foo = ", dogs:get(long_prefix .. "foo"))
+
+            ngx.sleep(0.002)
+            ngx.say("foo after init_ttl = ", dogs:get("foo"))
+        }
+    }
+--- request
+GET /t
+--- response_body
+incr: 10502 nil false
+incr: 10502 nil true
+foo = 10502
+foo after init_ttl = nil
+--- no_error_log
+[error]
+[alert]
+[crit]