diff --git a/build/pass/Dockerfile b/build/pass/Dockerfile
index df082d0a8c57fac6cad3fac6f402656d4625ee44..18482442d4e78179276b00f477ee31c795269bfa 100644
--- a/build/pass/Dockerfile
+++ b/build/pass/Dockerfile
@@ -2,6 +2,10 @@ FROM node:19-bullseye as development
 
 ENV NODE_ENV development
 
+ENV DEBIAN_FRONTEND=noninteractive
+RUN apt update && apt install -y borgbackup && rm -rf /var/lib/apt/lists/*
+ENV DEBIAN_FRONTEND=
+
 RUN mkdir /data && chown 1000:1000 /data
 VOLUME ["/data"]
 
diff --git a/pass/bin/backup b/pass/bin/backup
new file mode 100644
index 0000000000000000000000000000000000000000..0b0783892957af9b83f68625c5628e2c903086ba
--- /dev/null
+++ b/pass/bin/backup
@@ -0,0 +1,68 @@
+const RedisClient = require("../app/RedisClient");
+const config = require("config");
+const path = require("path");
+const fs = require("fs");
+
+(async () => {
+    const redis_client = RedisClient.CLIENT();
+
+    let cursor = 0;
+
+    let keys = {};
+    let order_links = {};
+    let used_tokens = {};
+    let undefined_keys = [];
+
+    do {
+        let scan_result = await redis_client.scan(cursor);
+        cursor = parseInt(scan_result[0]);
+        let redis_keys = scan_result[1];
+        for (let i = 0; i < redis_keys.length; i++) {
+            let key = redis_keys[i];
+            let key_match = key.match(/^key:([a-z0-9]{8}-[a-z0-9]{4}-4[a-z0-9]{3}-[a-z0-9]{4}-[a-z0-9]{12})$/);
+            if (key_match) {
+                let key_expiretime = await redis_client.expiretime(key_match[0]);
+                let key_content = JSON.parse(await redis_client.get(key_match[0]));
+
+                keys[key_match[1]] = {
+                    expireat: key_expiretime,
+                    content: key_content
+                };
+            } else if (key.match(/^order_link_order_key:\d+$/)) {
+                let order_link_expiretime = await redis_client.expiretime(key);
+                let order_link_content = await redis_client.get(key);
+                order_links[key] = {
+                    expireat: order_link_expiretime,
+                    content: order_link_content
+                };
+            } else if (key.match(/^tokens:used:[\d\-]+$/)) {
+                let used_tokens_content = await redis_client.hgetall(key);
+                let used_tokens_expiretime = await redis_client.expiretime(key);
+                used_tokens[key] = {
+                    expireat: used_tokens_expiretime,
+                    content: used_tokens_content
+                };
+            } else {
+                undefined_keys.push(key);
+            }
+        }
+    } while (cursor !== 0);
+    await redis_client.quit();
+
+    let storage_path = path.join(
+        config.get("storage.data_path"),
+        "backup.json"
+    );
+
+    let backup_data = {
+        keys: keys,
+        order_links: order_links,
+        used_tokens: used_tokens
+    };
+
+    fs.writeFileSync(storage_path, JSON.stringify(backup_data, null, 4), { flag: "w" });
+
+    console.log(`Backup stored at ${storage_path}`);
+    console.log("Unsaved Redis Keys:");
+    console.log(undefined_keys);
+})();