diff --git a/src/metabase/api/session.clj b/src/metabase/api/session.clj
index b8856e1b717dcfd82bd8678ebf6e11e5bd738529..19b3ff54986f0a8febdf96fae0e8a515466382e4 100644
--- a/src/metabase/api/session.clj
+++ b/src/metabase/api/session.clj
@@ -15,7 +15,7 @@
    [metabase.models.login-history :refer [LoginHistory]]
    [metabase.models.pulse :as pulse]
    [metabase.models.session :refer [Session]]
-   [metabase.models.setting :as setting]
+   [metabase.models.setting :as setting :refer [defsetting]]
    [metabase.models.user :as user :refer [User]]
    [metabase.public-settings :as public-settings]
    [metabase.server.middleware.session :as mw.session]
@@ -232,9 +232,17 @@
   (forgot-password-impl email)
   api/generic-204-no-content)
 
-(def ^:private ^:const reset-token-ttl-ms
-  "Number of milliseconds a password reset is considered valid."
-  (* 48 60 60 1000)) ; token considered valid for 48 hours
+
+(defsetting reset-token-ttl-hours
+  (deferred-tru "Number of hours a password reset is considered valid.")
+  :visibility :internal
+  :type       :integer
+  :default    48)
+
+(defn reset-token-ttl-ms
+  "number of milliseconds a password reset is considered valid."
+  []
+  (* (reset-token-ttl-hours) 60 60 1000))
 
 (defn- valid-reset-token->user
   "Check if a password reset token is valid. If so, return the `User` ID it corresponds to."
@@ -249,7 +257,7 @@
                 (u.password/bcrypt-verify token reset_token))
           ;; check that the reset was triggered within the last 48 HOURS, after that the token is considered expired
           (let [token-age (- (System/currentTimeMillis) reset_triggered)]
-            (when (< token-age reset-token-ttl-ms)
+            (when (< token-age (reset-token-ttl-ms))
               user)))))))
 
 #_{:clj-kondo/ignore [:deprecated-var]}
diff --git a/test/metabase/api/session_test.clj b/test/metabase/api/session_test.clj
index 881783f098d0b74a8df0899f7ee96efeaee9611b..a80be1933fd11e8a62baa4cdfe5d89d9676427a0 100644
--- a/test/metabase/api/session_test.clj
+++ b/test/metabase/api/session_test.clj
@@ -358,6 +358,28 @@
         (is (= {:valid false}
                (mt/client :get 200 "session/password_reset_token_valid", :token token)))))))
 
+(deftest reset-token-ttl-hours-test
+  (testing "Test reset-token-ttl-hours-test"
+    (testing "reset-token-ttl-hours-test is reset to default when not set"
+      (mt/with-temp-env-var-value [mb-reset-token-ttl-hours nil]
+        (is (= 48 (setting/get-value-of-type :integer :reset-token-ttl-hours)))))
+
+    (testing "reset-token-ttl-hours-test is set to positive value"
+      (mt/with-temp-env-var-value [mb-reset-token-ttl-hours 36]
+        (is (= 36 (setting/get-value-of-type :integer :reset-token-ttl-hours)))))
+
+    (testing "reset-token-ttl-hours-test is set to large positive value"
+      (mt/with-temp-env-var-value [mb-reset-token-ttl-hours (+ Integer/MAX_VALUE 1)]
+        (is (= (+ Integer/MAX_VALUE 1) (setting/get-value-of-type :integer :reset-token-ttl-hours)))))
+
+    (testing "reset-token-ttl-hours-test is set to zero"
+      (mt/with-temp-env-var-value [mb-reset-token-ttl-hours 0]
+        (is (= 0 (setting/get-value-of-type :integer :reset-token-ttl-hours)))))
+
+    (testing "reset-token-ttl-hours-test is set to negative value"
+      (mt/with-temp-env-var-value [mb-reset-token-ttl-hours -1]
+        (is (= -1 (setting/get-value-of-type :integer :reset-token-ttl-hours)))))))
+
 (deftest properties-test
   (reset-throttlers!)
   (testing "GET /session/properties"