diff --git a/Cargo.lock b/Cargo.lock
index c6ba96e683..52ef14933f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -220,6 +220,7 @@ dependencies = [
  "parse-display",
  "pretty_assertions",
  "rand",
+ "redis",
  "regex",
  "schemars",
  "sea-orm",
@@ -524,6 +525,16 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
 
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
 [[package]]
 name = "const-oid"
 version = "0.9.6"
@@ -1858,6 +1869,21 @@ dependencies = [
  "getrandom",
 ]
 
+[[package]]
+name = "redis"
+version = "0.25.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6472825949c09872e8f2c50bde59fcefc17748b6be5c90fd67cd8b4daca73bfd"
+dependencies = [
+ "combine",
+ "itoa",
+ "percent-encoding",
+ "ryu",
+ "sha1_smol",
+ "socket2",
+ "url",
+]
+
 [[package]]
 name = "redox_syscall"
 version = "0.4.1"
@@ -2295,6 +2321,12 @@ dependencies = [
  "digest",
 ]
 
+[[package]]
+name = "sha1_smol"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
+
 [[package]]
 name = "sha2"
 version = "0.10.8"
diff --git a/Cargo.toml b/Cargo.toml
index 7ca43c960b..b82635d8b4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,7 @@ pretty_assertions = "1.4.0"
 proc-macro2 = "1.0.79"
 quote = "1.0.36"
 rand = "0.8.5"
+redis = "0.25.3"
 regex = "1.10.4"
 schemars = "0.8.16"
 sea-orm = "0.12.15"
diff --git a/packages/backend-rs/Cargo.toml b/packages/backend-rs/Cargo.toml
index af9e10cdc1..d077028e13 100644
--- a/packages/backend-rs/Cargo.toml
+++ b/packages/backend-rs/Cargo.toml
@@ -30,6 +30,7 @@ jsonschema = { workspace = true }
 once_cell = { workspace = true }
 parse-display = { workspace = true }
 rand = { workspace = true }
+redis = { workspace = true }
 regex = { workspace = true }
 schemars = { workspace = true, features = ["chrono"] }
 sea-orm = { workspace = true, features = ["sqlx-postgres", "runtime-tokio-rustls"] }
diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts
index d985eb368d..433e80999c 100644
--- a/packages/backend-rs/index.d.ts
+++ b/packages/backend-rs/index.d.ts
@@ -29,7 +29,7 @@ export interface ServerConfig {
   /** `NapiValue` is not implemented for `u64` */
   maxFileSize?: number
   accessLog?: string
-  clusterLimits?: _WorkerConfig
+  clusterLimits?: WorkerConfigInternal
   cuid?: IdConfig
   outgoingAddress?: string
   deliverJobConcurrency?: number
@@ -80,7 +80,7 @@ export interface WorkerConfig {
   web: number
   queue: number
 }
-export interface WorkerConfig {
+export interface WorkerConfigInternal {
   web?: number
   queue?: number
 }
diff --git a/packages/backend-rs/src/config/server.rs b/packages/backend-rs/src/config/server.rs
index 4b9fbc7131..edfdf6109c 100644
--- a/packages/backend-rs/src/config/server.rs
+++ b/packages/backend-rs/src/config/server.rs
@@ -25,7 +25,7 @@ struct ServerConfig {
     /// `NapiValue` is not implemented for `u64`
     pub max_file_size: Option<i64>,
     pub access_log: Option<String>,
-    pub cluster_limits: Option<_WorkerConfig>,
+    pub cluster_limits: Option<WorkerConfigInternal>,
     pub cuid: Option<IdConfig>,
     pub outgoing_address: Option<String>,
 
@@ -103,7 +103,7 @@ pub struct WorkerConfig {
 #[derive(Clone, Debug, PartialEq, Deserialize)]
 #[serde(rename_all = "camelCase")]
 #[crate::export(object, use_nullable = false)]
-pub struct _WorkerConfig {
+pub struct WorkerConfigInternal {
     pub web: Option<u32>,
     pub queue: Option<u32>,
 }
diff --git a/packages/backend-rs/src/database/mod.rs b/packages/backend-rs/src/database/mod.rs
index 26db81c644..7a6277068b 100644
--- a/packages/backend-rs/src/database/mod.rs
+++ b/packages/backend-rs/src/database/mod.rs
@@ -1,2 +1,6 @@
 pub use postgresql::db_conn;
+pub use redis::key as redis_key;
+pub use redis::redis_conn;
+
 pub mod postgresql;
+pub mod redis;
diff --git a/packages/backend-rs/src/database/redis.rs b/packages/backend-rs/src/database/redis.rs
new file mode 100644
index 0000000000..863f57b35d
--- /dev/null
+++ b/packages/backend-rs/src/database/redis.rs
@@ -0,0 +1,47 @@
+use crate::config::server::CONFIG;
+use redis::{Client, Connection, RedisError};
+
+static REDIS_CLIENT: once_cell::sync::OnceCell<Client> = once_cell::sync::OnceCell::new();
+
+fn init_redis() -> Result<Client, RedisError> {
+    let redis_url = {
+        let mut params = vec!["redis://".to_owned()];
+
+        if let Some(user) = &CONFIG.redis.user {
+            params.push(user.to_string())
+        }
+        if let Some(pass) = &CONFIG.redis.pass {
+            params.push(format!(":{}@", pass))
+        }
+        params.push(CONFIG.redis.host.to_string());
+        params.push(format!(":{}", CONFIG.redis.port));
+        params.push(format!("/{}", CONFIG.redis.db));
+
+        params.concat()
+    };
+
+    Client::open(redis_url)
+}
+
+pub fn redis_conn() -> Result<Connection, RedisError> {
+    match REDIS_CLIENT.get() {
+        Some(client) => Ok(client.get_connection()?),
+        None => init_redis()?.get_connection(),
+    }
+}
+
+#[inline]
+/// prefix redis key with the hostname
+pub fn key(key: &str) -> String {
+    format!("{}:{}", CONFIG.hostname, key)
+}
+
+#[cfg(test)]
+mod unit_test {
+    use super::init_redis;
+
+    #[test]
+    fn connect_test() {
+        assert!(init_redis().is_ok());
+    }
+}