diff --git a/src/database.rs b/src/database.rs
index 20049d11..a30982ec 100644
--- a/src/database.rs
+++ b/src/database.rs
@@ -26,9 +26,9 @@ use rocket::{
     try_outcome, State,
 };
 use ruma::{DeviceId, ServerName, UserId};
-use serde::Deserialize;
+use serde::{de::IgnoredAny, Deserialize};
 use std::{
-    collections::HashMap,
+    collections::{BTreeMap, HashMap},
     fs::{self, remove_dir_all},
     io::Write,
     ops::Deref,
@@ -42,8 +42,8 @@ use self::proxy::ProxyConfig;
 pub struct Config {
     server_name: Box<ServerName>,
     database_path: String,
-    cache_capacity: Option<u32>, // deprecated
-    db_cache_capacity: Option<u32>,
+    #[serde(default = "default_db_cache_capacity")]
+    db_cache_capacity: u32,
     #[serde(default = "default_sqlite_read_pool_size")]
     sqlite_read_pool_size: usize,
     #[serde(default = "false_fn")]
@@ -71,28 +71,28 @@ pub struct Config {
     trusted_servers: Vec<Box<ServerName>>,
     #[serde(default = "default_log")]
     pub log: String,
+
+    #[serde(flatten)]
+    catchall: BTreeMap<String, IgnoredAny>,
 }
 
-macro_rules! deprecate_with {
-    ($self:expr ; $from:ident -> $to:ident) => {
-        if let Some(v) = $self.$from {
-            let from = stringify!($from);
-            let to = stringify!($to);
-            log::warn!("{} is deprecated, use {} instead", from, to);
-
-            $self.$to.get_or_insert(v);
-        }
-    };
-    ($self:expr ; $from:ident -> $to:ident or $default:expr) => {
-        deprecate_with!($self ; $from -> $to);
-        $self.$to.get_or_insert_with($default);
-    };
-}
+const DEPRECATED_KEYS: &[&str] = &["cache_capacity"];
 
 impl Config {
-    pub fn process_fallbacks(&mut self) {
-        // TODO: have a proper way handle into above struct (maybe serde supports something like this?)
-        deprecate_with!(self ; cache_capacity -> db_cache_capacity or default_db_cache_capacity);
+    pub fn warn_deprecated(&self) {
+        let mut was_deprecated = false;
+        for key in self
+            .catchall
+            .keys()
+            .filter(|key| DEPRECATED_KEYS.iter().any(|s| s == key))
+        {
+            log::warn!("Config parameter {} is deprecated", key);
+            was_deprecated = true;
+        }
+
+        if was_deprecated {
+            log::warn!("Read conduit documentation and check your configuration if any new configuration parameters should be adjusted");
+        }
     }
 }
 
diff --git a/src/database/abstraction/sqlite.rs b/src/database/abstraction/sqlite.rs
index 6864287e..d4ab9ad0 100644
--- a/src/database/abstraction/sqlite.rs
+++ b/src/database/abstraction/sqlite.rs
@@ -128,7 +128,7 @@ impl DatabaseEngine for Engine {
         let pool = Pool::new(
             Path::new(&config.database_path).join("conduit.db"),
             config.sqlite_read_pool_size,
-            config.db_cache_capacity.expect("fallbacks hasn't been called") / 1024, // bytes -> kb
+            config.db_cache_capacity / 1024, // bytes -> kb
         )?;
 
         pool.write_lock()
diff --git a/src/main.rs b/src/main.rs
index 84dc4cbd..e0d2e3df 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -196,7 +196,7 @@ async fn main() {
 
     std::env::set_var("RUST_LOG", "warn");
 
-    let mut config = raw_config
+    let config = raw_config
         .extract::<Config>()
         .expect("It looks like your config is invalid. Please take a look at the error");
 
@@ -218,8 +218,7 @@ async fn main() {
         tracing_subscriber::fmt::init();
     }
 
-    // Required here to process fallbacks while logging is enabled, but before config is actually used for anything
-    config.process_fallbacks();
+    config.warn_deprecated();
 
     let db = Database::load_or_create(config)
         .await