diff --git a/.dir-locals.el b/.dir-locals.el
index 62d367c3dfa99cd1cdd4cc0667704ae5a8026e90..d24c5d8cda2cb03947b8e0a1ac1a5f4aefd7b415 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -28,7 +28,7 @@
                               (p/def-map-type '(2 nil nil (:defn)))
                               (p.types/defrecord+ '(2 nil nil (:defn)))
                               (tools.macro/macrolet '(1 (:defn))))))
-                  ;; if you're using clj-refactor (highly recommended!), prefer prefix notation when cleaning the ns form
+                  ;; if you're using clj-refactor (highly recommended!)
                   (cljr-favor-prefix-notation . nil)
                   ;; prefer keeping source width about ~118, GitHub seems to cut off stuff at either 119 or 120 and
                   ;; it's nicer to look at code in GH when you don't have to scroll back and forth
diff --git a/dev/src/dev.clj b/dev/src/dev.clj
index b742ba9a7a698a8c51995ed81fa38df9553b79a0..acf515463d6c4287e6ad24629bb8972539624087 100644
--- a/dev/src/dev.clj
+++ b/dev/src/dev.clj
@@ -4,13 +4,14 @@
             [honeysql.core :as hsql]
             [metabase.api.common :as api-common]
             [metabase.core :as mbc]
+            [metabase.core.initialization-status :as init-status]
             [metabase.db :as mdb]
             [metabase.driver :as driver]
             [metabase.driver.sql-jdbc.execute :as sql-jdbc.execute]
-            [metabase.handler :as handler]
-            [metabase.plugins :as pluguns]
+            [metabase.plugins :as plugins]
             [metabase.query-processor.timezone :as qp.timezone]
             [metabase.server :as server]
+            [metabase.server.handler :as handler]
             [metabase.test :as mt]
             [metabase.test.data.impl :as data.impl]
             [metabase.util :as u]))
@@ -27,10 +28,10 @@
   []
   (when-not @initialized?
     (init!))
-  (metabase.server/start-web-server! #'metabase.handler/app)
-  (metabase.db/setup-db!)
-  (metabase.plugins/load-plugins!)
-  (metabase.core.initialization-status/set-complete!))
+  (server/start-web-server! #'handler/app)
+  (mdb/setup-db!)
+  (plugins/load-plugins!)
+  (init-status/set-complete!))
 
 (defn stop!
   []
diff --git a/enterprise/backend/src/metabase_enterprise/sandbox/api/routes.clj b/enterprise/backend/src/metabase_enterprise/sandbox/api/routes.clj
index 498f3088ca244756b7e02bc6b2d12714efddc928..52e4f343801c1cfadea9fc593cf27892a95971a9 100644
--- a/enterprise/backend/src/metabase_enterprise/sandbox/api/routes.clj
+++ b/enterprise/backend/src/metabase_enterprise/sandbox/api/routes.clj
@@ -4,7 +4,7 @@
             [metabase-enterprise.sandbox.api.gtap :as gtap]
             [metabase-enterprise.sandbox.api.table :as table]
             [metabase-enterprise.sandbox.api.user :as user]
-            [metabase.middleware.auth :as middleware.auth]))
+            [metabase.server.middleware.auth :as middleware.auth]))
 
 ;; this is copied from `metabase.api.routes` because if we require that above we will destroy startup times for `lein
 ;; ring server`
@@ -16,6 +16,7 @@
   (compojure/context
    "/mt"
    []
+
    (compojure/routes
     (compojure/context "/gtap" [] (+auth gtap/routes))
     (compojure/context "/user" [] (+auth user/routes))))
diff --git a/enterprise/backend/src/metabase_enterprise/sandbox/models/group_table_access_policy.clj b/enterprise/backend/src/metabase_enterprise/sandbox/models/group_table_access_policy.clj
index 87a5d52523e832afbf140af54248758a222d7926..c959b4c366f392f9e1c69423ec9d8e6bbe8a7ecd 100644
--- a/enterprise/backend/src/metabase_enterprise/sandbox/models/group_table_access_policy.clj
+++ b/enterprise/backend/src/metabase_enterprise/sandbox/models/group_table_access_policy.clj
@@ -6,12 +6,12 @@
             [clojure.walk :as walk]
             [medley.core :as m]
             [metabase.mbql.normalize :as normalize]
-            [metabase.middleware.session :as session]
             [metabase.models.card :as card :refer [Card]]
             [metabase.models.interface :as i]
             [metabase.models.table :as table]
             [metabase.plugins.classloader :as classloader]
             [metabase.query-processor.error-type :as qp.error-type]
+            [metabase.server.middleware.session :as session]
             [metabase.util :as u]
             [metabase.util.i18n :refer [tru]]
             [metabase.util.schema :as su]
diff --git a/enterprise/backend/src/metabase_enterprise/sso/api/routes.clj b/enterprise/backend/src/metabase_enterprise/sso/api/routes.clj
index 3d44fb13eadf0af672ead96402a993deaef70e0e..c2ee1580ef624cbd948482279105152a211ff5c3 100644
--- a/enterprise/backend/src/metabase_enterprise/sso/api/routes.clj
+++ b/enterprise/backend/src/metabase_enterprise/sso/api/routes.clj
@@ -2,7 +2,7 @@
   (:require [compojure.core :as compojure]
             [metabase-enterprise.sso.api.sso :as sso]))
 
-;; This needs to be installed in the `metabase.routes/routes` -- not `metabase.api.routes/routes` !!!
+;; This needs to be installed in the `metabase.server.routes/routes` -- not `metabase.api.routes/routes` !!!
 (compojure/defroutes ^{:doc "Ring routes for auth (SAML) API endpoints."} routes
   (compojure/context
    "/auth"
diff --git a/enterprise/backend/src/metabase_enterprise/sso/integrations/jwt.clj b/enterprise/backend/src/metabase_enterprise/sso/integrations/jwt.clj
index b5c69798fa4c884d964a3f3e9fcc6fb44153839f..319c0f7f9a6f992a0732a41bd219f51c5b82d52b 100644
--- a/enterprise/backend/src/metabase_enterprise/sso/integrations/jwt.clj
+++ b/enterprise/backend/src/metabase_enterprise/sso/integrations/jwt.clj
@@ -7,7 +7,7 @@
             [metabase.api.common :as api]
             [metabase.api.session :as session]
             [metabase.integrations.common :as integrations.common]
-            [metabase.middleware.session :as mw.session]
+            [metabase.server.middleware.session :as mw.session]
             [metabase.util.i18n :refer [tru]]
             [ring.util.response :as resp])
   (:import java.net.URLEncoder))
diff --git a/enterprise/backend/src/metabase_enterprise/sso/integrations/saml.clj b/enterprise/backend/src/metabase_enterprise/sso/integrations/saml.clj
index baa49da0b67b81ae598b7d56f5e1c79e53fca594..619a41d8dc4b02101017b5ee9847017dd797e536 100644
--- a/enterprise/backend/src/metabase_enterprise/sso/integrations/saml.clj
+++ b/enterprise/backend/src/metabase_enterprise/sso/integrations/saml.clj
@@ -10,8 +10,8 @@
             [metabase.api.common :as api]
             [metabase.api.session :as session]
             [metabase.integrations.common :as integrations.common]
-            [metabase.middleware.session :as mw.session]
             [metabase.public-settings :as public-settings]
+            [metabase.server.middleware.session :as mw.session]
             [metabase.util :as u]
             [metabase.util.i18n :refer [trs tru]]
             [ring.util.codec :as codec]
diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/api/gtap_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/api/gtap_test.clj
index 55b823b41dc949b3a9bcdd38177f1ea243aab811..82cadf4a2a3ca19084954e5bbdfc2ed3ce72b002 100644
--- a/enterprise/backend/test/metabase_enterprise/sandbox/api/gtap_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/sandbox/api/gtap_test.clj
@@ -2,11 +2,11 @@
   (:require [expectations :refer :all]
             [metabase-enterprise.sandbox.models.group-table-access-policy :refer [GroupTableAccessPolicy]]
             [metabase.http-client :as http]
-            [metabase.middleware.util :as middleware.u]
             [metabase.models.card :refer [Card]]
             [metabase.models.permissions-group :refer [PermissionsGroup]]
             [metabase.models.table :refer [Table]]
             [metabase.public-settings.metastore :as metastore]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.test.data.users :refer :all]
             [metabase.test.util :as tu]
             [toucan.util.test :as tt]))
diff --git a/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj b/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj
index 90064f24cf5421e8e58f6312b4dc898f063ee868..e3bb5e9ff0aea930c4af1923f80935240bb94f15 100644
--- a/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj
+++ b/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj
@@ -5,12 +5,12 @@
             [metabase-enterprise.sso.integrations.sso-settings :as sso-settings]
             [metabase.config :as config]
             [metabase.http-client :as http]
-            [metabase.middleware.session :as mw.session]
             [metabase.models.permissions-group :as group :refer [PermissionsGroup]]
             [metabase.models.permissions-group-membership :refer [PermissionsGroupMembership]]
             [metabase.models.user :refer [User]]
             [metabase.public-settings :as public-settings]
             [metabase.public-settings.metastore-test :as metastore-test]
+            [metabase.server.middleware.session :as mw.session]
             [metabase.test :as mt]
             [metabase.test.fixtures :as fixtures]
             [metabase.test.util :as tu]
diff --git a/project.clj b/project.clj
index 0234d2aeb9654f378bdcb86f0af20d60533a653b..bc4d7df9513e47b7271e3f4b58738032dd58c816 100644
--- a/project.clj
+++ b/project.clj
@@ -19,8 +19,8 @@
    "run-ee"                            ["with-profile" "+run,+ee" "run"]
    "run-with-repl"                     ["with-profile" "+run-with-repl" "repl"]
    "run-with-repl-ee"                  ["with-profile" "+run-with-repl,+ee" "repl"]
-   "ring"                              ["with-profile" "+ring" "ring"]
-   "ring-ee"                           ["with-profile" "+ring,+ee" "ring"]
+   ;; "ring"                              ["with-profile" "+ring" "ring"]
+   ;; "ring-ee"                           ["with-profile" "+ring,+ee" "ring"]
    "test"                              ["with-profile" "+test" "test"]
    "test-ee"                           ["with-profile" "+test,+ee" "test"]
    "bikeshed"                          ["with-profile" "+bikeshed" "bikeshed"
@@ -274,24 +274,25 @@
                    (metabase.core/-main))
       :timeout 60000}}]
 
+   ;; DISABLED FOR NOW SINCE IT'S BROKEN -- SEE #12181
    ;; start the dev HTTP server with 'lein ring server'
-   :ring
-   [:exclude-tests
-    :include-all-drivers
-    {:dependencies
-     ;; used internally by lein ring to track namespace changes. Newer version contains fix by yours truly with 1000x
-     ;; faster launch time
-     [[ns-tracker "0.4.0"]]
-
-     :plugins
-     [[lein-ring "0.12.5" :exclusions [org.clojure/clojure]]]
-
-     :ring
-     {:handler      metabase.handler/app
-      :init         metabase.core/init!
-      :async?       true
-      :destroy      metabase.core/destroy
-      :reload-paths ["src"]}}]
+   ;; :ring
+   ;; [:exclude-tests
+   ;;  :include-all-drivers
+   ;;  {:dependencies
+   ;;   ;; used internally by lein ring to track namespace changes. Newer version contains fix by yours truly with 1000x
+   ;;   ;; faster launch time
+   ;;   [[ns-tracker "0.4.0"]]
+
+   ;;   :plugins
+   ;;   [[lein-ring "0.12.5" :exclusions [org.clojure/clojure]]]
+
+   ;;   :ring
+   ;;   {:handler      metabase.server.handler/app
+   ;;    :init         metabase.core/init!
+   ;;    :async?       true
+   ;;    :destroy      metabase.core/destroy
+   ;;    :reload-paths ["src"]}}]
 
    :with-include-drivers-middleware
    {:plugins
diff --git a/resources/frontend_client/index_template.html b/resources/frontend_client/index_template.html
index 595774ca9936f4645c4840f5a37d827d9dd23c9b..fefdb6dd80788d1be7982e32d70ea34f4785fabf 100644
--- a/resources/frontend_client/index_template.html
+++ b/resources/frontend_client/index_template.html
@@ -36,7 +36,7 @@
       {{{localizationJSON}}}
     </script>
 
-    <!-- If you modify this script, make sure you update the whitelisted Content-Security-Policy hash in metabase.middleware.security -->
+    <!-- If you modify this script, make sure you update the whitelisted Content-Security-Policy hash in metabase.server.middleware.security -->
     <script type="text/javascript">{{{bootstrapJS}}}</script>
   </head>
 
@@ -49,7 +49,7 @@
     {{/enableGoogleAuth}}
 
     <!-- Google Analytics -->
-    <!-- If you modify this script, make sure you update the whitelisted Content-Security-Policy hash in metabase.middleware.security -->
+    <!-- If you modify this script, make sure you update the whitelisted Content-Security-Policy hash in metabase.server.middleware.security -->
     {{#enableAnonTracking}}
       <script type="text/javascript">{{{googleAnalyticsJS}}}</script>
     {{/enableAnonTracking}}
diff --git a/src/metabase/api/routes.clj b/src/metabase/api/routes.clj
index ba84fa84c68040eb3b64522699a43d99d66443de..f1752df14cb1714c12bb4900f850b6c11aaef7d8 100644
--- a/src/metabase/api/routes.clj
+++ b/src/metabase/api/routes.clj
@@ -39,9 +39,9 @@
             [metabase.api.user :as user]
             [metabase.api.util :as util]
             [metabase.config :as config]
-            [metabase.middleware.auth :as middleware.auth]
-            [metabase.middleware.exceptions :as middleware.exceptions]
             [metabase.plugins.classloader :as classloader]
+            [metabase.server.middleware.auth :as middleware.auth]
+            [metabase.server.middleware.exceptions :as middleware.exceptions]
             [metabase.util :as u]
             [metabase.util.i18n :refer [deferred-tru]]))
 
diff --git a/src/metabase/api/session.clj b/src/metabase/api/session.clj
index c2a386c2656df8b58f65712b9e1c5ae1a463171e..b0f900c362e63e44a39c04c5342f1d9a00a2a96b 100644
--- a/src/metabase/api/session.clj
+++ b/src/metabase/api/session.clj
@@ -10,11 +10,11 @@
             [metabase.email.messages :as email]
             [metabase.events :as events]
             [metabase.integrations.ldap :as ldap]
-            [metabase.middleware.session :as mw.session]
             [metabase.models.session :refer [Session]]
             [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]
             [metabase.util :as u]
             [metabase.util.i18n :as ui18n :refer [deferred-tru trs tru]]
             [metabase.util.password :as pass]
diff --git a/src/metabase/api/setup.clj b/src/metabase/api/setup.clj
index 3c933bd72192c02c01d9d5834801fb279082a610..8fa2c32bfd5b3d610d0ac6821d44f74955d83b3b 100644
--- a/src/metabase/api/setup.clj
+++ b/src/metabase/api/setup.clj
@@ -6,7 +6,6 @@
             [metabase.email :as email]
             [metabase.events :as events]
             [metabase.integrations.slack :as slack]
-            [metabase.middleware.session :as mw.session]
             [metabase.models.card :refer [Card]]
             [metabase.models.collection :refer [Collection]]
             [metabase.models.dashboard :refer [Dashboard]]
@@ -19,6 +18,7 @@
             [metabase.models.table :refer [Table]]
             [metabase.models.user :as user :refer [User]]
             [metabase.public-settings :as public-settings]
+            [metabase.server.middleware.session :as mw.session]
             [metabase.setup :as setup]
             [metabase.util :as u]
             [metabase.util.i18n :as i18n :refer [tru]]
diff --git a/src/metabase/async/api_response.clj b/src/metabase/async/api_response.clj
index c7013ebec45e9d97b4e79089bb51931831946b24..72e00ceda1a04650dde4064b5ef30b876e175a58 100644
--- a/src/metabase/async/api_response.clj
+++ b/src/metabase/async/api_response.clj
@@ -12,7 +12,7 @@
             [clojure.java.io :as io]
             [clojure.tools.logging :as log]
             [compojure.response :refer [Sendable]]
-            [metabase.middleware.exceptions :as mw.exceptions]
+            [metabase.server.middleware.exceptions :as mw.exceptions]
             [metabase.util :as u]
             [metabase.util.i18n :as ui18n :refer [trs]]
             [ring.core.protocols :as ring.protocols]
diff --git a/src/metabase/core.clj b/src/metabase/core.clj
index 9d21d7081b19daea6faeef0db2e05c832622989c..1121cafbef8f7ee0e78c465a2b0169d75c0293fc 100644
--- a/src/metabase/core.clj
+++ b/src/metabase/core.clj
@@ -7,13 +7,13 @@
             [metabase.core.initialization-status :as init-status]
             [metabase.db :as mdb]
             [metabase.events :as events]
-            [metabase.handler :as handler]
             [metabase.metabot :as metabot]
             [metabase.models.user :refer [User]]
             [metabase.plugins :as plugins]
             [metabase.plugins.classloader :as classloader]
             [metabase.sample-data :as sample-data]
             [metabase.server :as server]
+            [metabase.server.handler :as handler]
             [metabase.setup :as setup]
             [metabase.task :as task]
             [metabase.troubleshooting :as troubleshooting]
diff --git a/src/metabase/handler.clj b/src/metabase/handler.clj
deleted file mode 100644
index dc8c1c10fc1da85189a5ff4f26f16528ef53af9d..0000000000000000000000000000000000000000
--- a/src/metabase/handler.clj
+++ /dev/null
@@ -1,53 +0,0 @@
-(ns metabase.handler
-  "Top-level Metabase Ring handler."
-  (:require [metabase.config :as config]
-            [metabase.middleware.auth :as mw.auth]
-            [metabase.middleware.exceptions :as mw.exceptions]
-            [metabase.middleware.json :as mw.json]
-            [metabase.middleware.log :as mw.log]
-            [metabase.middleware.misc :as mw.misc]
-            [metabase.middleware.security :as mw.security]
-            [metabase.middleware.session :as mw.session]
-            [metabase.middleware.ssl :as mw.ssl]
-            [metabase.plugins.classloader :as classloader]
-            [metabase.routes :as routes]
-            [ring.middleware.cookies :refer [wrap-cookies]]
-            [ring.middleware.gzip :refer [wrap-gzip]]
-            [ring.middleware.keyword-params :refer [wrap-keyword-params]]
-            [ring.middleware.params :refer [wrap-params]]))
-
-;; required here because this namespace is not actually used anywhere but we need it to be loaded because it adds
-;; impls for handling `core.async` channels as web server responses
-(classloader/require 'metabase.async.api-response)
-
-
-(def app
-  "The primary entry point to the Ring HTTP server."
-  (->
-   ;; in production, dereference routes now because they will not change at runtime, so we don't need to waste time
-   ;; dereferencing the var on every request. For dev & test, use the var instead so it can be tweaked without having
-   ;; to restart the web server
-   (if config/is-prod?
-     routes/routes
-     #'routes/routes)
-   ;; ▼▼▼ POST-PROCESSING ▼▼▼ happens from TOP-TO-BOTTOM
-   mw.exceptions/catch-uncaught-exceptions ; catch any Exceptions that weren't passed to `raise`
-   mw.exceptions/catch-api-exceptions      ; catch exceptions and return them in our expected format
-   mw.log/log-api-call
-   mw.security/add-security-headers        ; Add HTTP headers to API responses to prevent them from being cached
-   mw.json/wrap-json-body                  ; extracts json POST body and makes it avaliable on request
-   mw.json/wrap-streamed-json-response     ; middleware to automatically serialize suitable objects as JSON in responses
-   wrap-keyword-params                     ; converts string keys in :params to keyword keys
-   wrap-params                             ; parses GET and POST params as :query-params/:form-params and both as :params
-   mw.misc/maybe-set-site-url              ; set the value of `site-url` if it hasn't been set yet
-   mw.session/bind-current-user            ; Binds *current-user* and *current-user-id* if :metabase-user-id is non-nil
-   mw.session/wrap-current-user-info       ; looks for :metabase-session-id and sets :metabase-user-id and other info if Session ID is valid
-   mw.session/wrap-session-id              ; looks for a Metabase Session ID and assoc as :metabase-session-id
-   mw.auth/wrap-api-key                    ; looks for a Metabase API Key on the request and assocs as :metabase-api-key
-   wrap-cookies                            ; Parses cookies in the request map and assocs as :cookies
-   mw.misc/add-content-type                ; Adds a Content-Type header for any response that doesn't already have one
-   mw.misc/disable-streaming-buffering     ; Add header to streaming (async) responses so ngnix doesn't buffer keepalive bytes
-   wrap-gzip                               ; GZIP response if client can handle it
-   mw.misc/bind-request                    ; bind `metabase.middleware.misc/*request*` for the duration of the request
-   mw.ssl/redirect-to-https-middleware))   ; Redirect to HTTPS if configured to do so
-;; ▲▲▲ PRE-PROCESSING ▲▲▲ happens from BOTTOM-TO-TOP
diff --git a/src/metabase/models/card.clj b/src/metabase/models/card.clj
index acce8955900792186ca8f23086ed34f1d95df36c..1b1194a468e150f6e68d40d77be8d3f987e9a92f 100644
--- a/src/metabase/models/card.clj
+++ b/src/metabase/models/card.clj
@@ -6,7 +6,6 @@
             [metabase.api.common :as api :refer [*current-user-id*]]
             [metabase.mbql.normalize :as normalize]
             [metabase.mbql.util :as mbql.u]
-            [metabase.middleware.session :as session]
             [metabase.models.collection :as collection]
             [metabase.models.dependency :as dependency]
             [metabase.models.field-values :as field-values]
@@ -19,6 +18,7 @@
             [metabase.plugins.classloader :as classloader]
             [metabase.public-settings :as public-settings]
             [metabase.query-processor.util :as qputil]
+            [metabase.server.middleware.session :as session]
             [metabase.util :as u]
             [metabase.util.i18n :as ui18n :refer [tru]]
             [toucan.db :as db]
diff --git a/src/metabase/models/session.clj b/src/metabase/models/session.clj
index fd6255a36399da0ae7f06d3ad5eb5126650ed74c..04f4976ab7f31f919b5f0454f3692adf03d3290d 100644
--- a/src/metabase/models/session.clj
+++ b/src/metabase/models/session.clj
@@ -1,8 +1,8 @@
 (ns metabase.models.session
   (:require [buddy.core.codecs :as codecs]
             [buddy.core.nonce :as nonce]
-            [metabase.middleware.misc :as mw.misc]
-            [metabase.middleware.util :as mw.util]
+            [metabase.server.middleware.misc :as mw.misc]
+            [metabase.server.middleware.util :as mw.util]
             [metabase.util :as u]
             [schema.core :as s]
             [toucan.models :as models]))
diff --git a/src/metabase/pulse.clj b/src/metabase/pulse.clj
index 9087f919b04279a90d7bac7d6562a7aa3bbdbcf6..c7a570bc5be103fa4ca704eeeb5e47b713460f0d 100644
--- a/src/metabase/pulse.clj
+++ b/src/metabase/pulse.clj
@@ -4,7 +4,6 @@
             [metabase.email :as email]
             [metabase.email.messages :as messages]
             [metabase.integrations.slack :as slack]
-            [metabase.middleware.session :as session]
             [metabase.models.card :refer [Card]]
             [metabase.models.dashboard :refer [Dashboard]]
             [metabase.models.dashboard-card :refer [DashboardCard]]
@@ -13,6 +12,7 @@
             [metabase.pulse.render :as render]
             [metabase.query-processor :as qp]
             [metabase.query-processor.timezone :as qp.timezone]
+            [metabase.server.middleware.session :as session]
             [metabase.util :as u]
             [metabase.util.i18n :refer [deferred-tru trs tru]]
             [metabase.util.ui-logic :as ui]
diff --git a/src/metabase/query_processor/streaming/xlsx.clj b/src/metabase/query_processor/streaming/xlsx.clj
index 1594f3fd20d7d10007e37563125046e19cafedd5..0845bd4930bae78289bc324ebc3608abccebc473 100644
--- a/src/metabase/query_processor/streaming/xlsx.clj
+++ b/src/metabase/query_processor/streaming/xlsx.clj
@@ -89,7 +89,7 @@
 
 ;; add a generic implementation for the method that writes values to XLSX cells that just piggybacks off the
 ;; implementations we've already defined for encoding things as JSON. These implementations live in
-;; `metabase.middleware`.
+;; `metabase.server.middleware`.
 (defmethod spreadsheet/set-cell! Object
   [^Cell cell, value]
   (when (= (.getCellType cell) CellType/FORMULA)
diff --git a/src/metabase/server.clj b/src/metabase/server.clj
index d96545bded9484fbde660d0b65666779f2098e24..d905be3b27403f78601c13acf3b4d2e211dfe46c 100644
--- a/src/metabase/server.clj
+++ b/src/metabase/server.clj
@@ -1,4 +1,5 @@
 (ns metabase.server
+  "Code related to configuring, starting, and stopping the Metabase Jetty web server."
   (:require [clojure.core :as core]
             [clojure.string :as str]
             [clojure.tools.logging :as log]
@@ -100,7 +101,7 @@
   "Start the embedded Jetty web server. Returns `:started` if a new server was started; `nil` if there was already a
   running server.
 
-    (start-web-server! #'metabase.handler/app)"
+    (start-web-server! #'metabase.server.handler/app)"
   [handler]
   (when-not (instance)
     ;; NOTE: we always start jetty w/ join=false so we can start the server first then do init in the background
diff --git a/src/metabase/server/README.md b/src/metabase/server/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..ef7130e329960ecf79ba9745d367ffb46cb6c237
--- /dev/null
+++ b/src/metabase/server/README.md
@@ -0,0 +1,4 @@
+# `metabase.server.*`
+
+Code related to the Metabase Jetty web server, excluding the REST API endpoints themselves, which live in
+`metabase.api.*`.
diff --git a/src/metabase/server/handler.clj b/src/metabase/server/handler.clj
new file mode 100644
index 0000000000000000000000000000000000000000..c0fe2e6ef18fea9eafbb2a97088f6b2d644a703e
--- /dev/null
+++ b/src/metabase/server/handler.clj
@@ -0,0 +1,63 @@
+(ns metabase.server.handler
+  "Top-level Metabase Ring handler."
+  (:require [metabase.config :as config]
+            [metabase.plugins.classloader :as classloader]
+            [metabase.server.middleware.auth :as mw.auth]
+            [metabase.server.middleware.exceptions :as mw.exceptions]
+            [metabase.server.middleware.json :as mw.json]
+            [metabase.server.middleware.log :as mw.log]
+            [metabase.server.middleware.misc :as mw.misc]
+            [metabase.server.middleware.security :as mw.security]
+            [metabase.server.middleware.session :as mw.session]
+            [metabase.server.middleware.ssl :as mw.ssl]
+            [metabase.server.routes :as routes]
+            [ring.middleware.cookies :refer [wrap-cookies]]
+            [ring.middleware.gzip :refer [wrap-gzip]]
+            [ring.middleware.keyword-params :refer [wrap-keyword-params]]
+            [ring.middleware.params :refer [wrap-params]]))
+
+;; required here because this namespace is not actually used anywhere but we need it to be loaded because it adds
+;; impls for handling `core.async` channels as web server responses
+(classloader/require 'metabase.async.api-response)
+
+(def ^:private middleware
+  ;; ▼▼▼ POST-PROCESSING ▼▼▼ happens from TOP-TO-BOTTOM
+  [#'mw.exceptions/catch-uncaught-exceptions ; catch any Exceptions that weren't passed to `raise`
+   #'mw.exceptions/catch-api-exceptions      ; catch exceptions and return them in our expected format
+   #'mw.log/log-api-call
+   #'mw.security/add-security-headers        ; Add HTTP headers to API responses to prevent them from being cached
+   #'mw.json/wrap-json-body                  ; extracts json POST body and makes it avaliable on request
+   #'mw.json/wrap-streamed-json-response     ; middleware to automatically serialize suitable objects as JSON in responses
+   #'wrap-keyword-params                     ; converts string keys in :params to keyword keys
+   #'wrap-params                             ; parses GET and POST params as :query-params/:form-params and both as :params
+   #'mw.misc/maybe-set-site-url              ; set the value of `site-url` if it hasn't been set yet
+   #'mw.session/bind-current-user            ; Binds *current-user* and *current-user-id* if :metabase-user-id is non-nil
+   #'mw.session/wrap-current-user-info       ; looks for :metabase-session-id and sets :metabase-user-id and other info if Session ID is valid
+   #'mw.session/wrap-session-id              ; looks for a Metabase Session ID and assoc as :metabase-session-id
+   #'mw.auth/wrap-api-key                    ; looks for a Metabase API Key on the request and assocs as :metabase-api-key
+   #'wrap-cookies                            ; Parses cookies in the request map and assocs as :cookies
+   #'mw.misc/add-content-type                ; Adds a Content-Type header for any response that doesn't already have one
+   #'mw.misc/disable-streaming-buffering     ; Add header to streaming (async) responses so ngnix doesn't buffer keepalive bytes
+   #'wrap-gzip                               ; GZIP response if client can handle it
+   #'mw.misc/bind-request                    ; bind `metabase.middleware.misc/*request*` for the duration of the request
+   #'mw.ssl/redirect-to-https-middleware])
+;; ▲▲▲ PRE-PROCESSING ▲▲▲ happens from BOTTOM-TO-TOP
+
+(defn- apply-middleware [handler]
+  (reduce
+   (fn [handler middleware-fn]
+     (middleware-fn handler))
+   handler
+   middleware))
+
+(def app
+  "The primary entry point to the Ring HTTP server."
+  (apply-middleware routes/routes))
+
+;; during interactive dev, recreate `app` whenever a middleware var or `routes/routes` changes.
+(when config/is-dev?
+  (doseq [varr  (cons #'routes/routes middleware)
+          :when (instance? clojure.lang.IRef varr)]
+    (add-watch varr ::reload (fn [_ _ _ _]
+                               (printf "%s changed, rebuilding %s" varr #'app)
+                               (alter-var-root #'app (constantly (apply-middleware routes/routes)))))))
diff --git a/src/metabase/middleware/auth.clj b/src/metabase/server/middleware/auth.clj
similarity index 92%
rename from src/metabase/middleware/auth.clj
rename to src/metabase/server/middleware/auth.clj
index 40fedf57126e58885407cc87b711159d36961d01..7180fc7458e18b0281a41c1cdb29542ec96ca195 100644
--- a/src/metabase/middleware/auth.clj
+++ b/src/metabase/server/middleware/auth.clj
@@ -1,8 +1,8 @@
-(ns metabase.middleware.auth
+(ns metabase.server.middleware.auth
   "Middleware related to enforcing authentication/API keys (when applicable). Unlike most other middleware most of this
   is not used as part of the normal `app`; it is instead added selectively to appropriate routes."
-  (:require [metabase.middleware.util :as middleware.u]
-            [metabase.models.setting :refer [defsetting]]))
+  (:require [metabase.models.setting :refer [defsetting]]
+            [metabase.server.middleware.util :as middleware.u]))
 
 (def ^:private ^:const ^String metabase-api-key-header "x-metabase-apikey")
 
diff --git a/src/metabase/middleware/exceptions.clj b/src/metabase/server/middleware/exceptions.clj
similarity index 97%
rename from src/metabase/middleware/exceptions.clj
rename to src/metabase/server/middleware/exceptions.clj
index d36e8522ffb8a1e75b948b1342c4a84eb593940c..fafebde5511fd6be180fc798fe467cdc4349b4b8 100644
--- a/src/metabase/middleware/exceptions.clj
+++ b/src/metabase/server/middleware/exceptions.clj
@@ -1,9 +1,9 @@
-(ns metabase.middleware.exceptions
+(ns metabase.server.middleware.exceptions
   "Ring middleware for handling Exceptions thrown in API request handler functions."
   (:require [clojure.java.jdbc :as jdbc]
             [clojure.string :as str]
             [clojure.tools.logging :as log]
-            [metabase.middleware.security :as mw.security]
+            [metabase.server.middleware.security :as mw.security]
             [metabase.util.i18n :as ui18n :refer [trs]])
   (:import java.sql.SQLException
            org.eclipse.jetty.io.EofException))
diff --git a/src/metabase/middleware/json.clj b/src/metabase/server/middleware/json.clj
similarity index 99%
rename from src/metabase/middleware/json.clj
rename to src/metabase/server/middleware/json.clj
index 8b14f90eb29c5d9e86550dc58eb5126b18e69874..f06e3b837a749348a82080d2af31020d25c60d21 100644
--- a/src/metabase/middleware/json.clj
+++ b/src/metabase/server/middleware/json.clj
@@ -1,4 +1,4 @@
-(ns metabase.middleware.json
+(ns metabase.server.middleware.json
   "Middleware related to parsing JSON requests and generating JSON responses."
   (:require [cheshire.core :as json]
             [cheshire.generate :as json.generate]
diff --git a/src/metabase/middleware/log.clj b/src/metabase/server/middleware/log.clj
similarity index 98%
rename from src/metabase/middleware/log.clj
rename to src/metabase/server/middleware/log.clj
index 07b2d3fddd4d0ee809c3bd36ab2bbd5f4fabafe9..2ac56aa959923134e1364f66d1e3521e00bbabec 100644
--- a/src/metabase/middleware/log.clj
+++ b/src/metabase/server/middleware/log.clj
@@ -1,4 +1,4 @@
-(ns metabase.middleware.log
+(ns metabase.server.middleware.log
   "Ring middleware for logging API requests/responses."
   (:require [clojure.core.async :as a]
             [clojure.string :as str]
@@ -6,8 +6,8 @@
             [metabase.async.streaming-response :as streaming-response]
             [metabase.async.streaming-response.thread-pool :as streaming-response.thread-pool]
             [metabase.async.util :as async.u]
-            [metabase.middleware.util :as middleware.u]
             [metabase.server :as server]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.util :as u]
             [metabase.util.i18n :refer [trs]]
             [toucan.db :as db])
diff --git a/src/metabase/middleware/misc.clj b/src/metabase/server/middleware/misc.clj
similarity index 97%
rename from src/metabase/middleware/misc.clj
rename to src/metabase/server/middleware/misc.clj
index 8cc2aa6614c665b901737539a0ce09bc0841f8e7..d696fe94e8f86038ad124692ef767dd11376baed 100644
--- a/src/metabase/middleware/misc.clj
+++ b/src/metabase/server/middleware/misc.clj
@@ -1,11 +1,11 @@
-(ns metabase.middleware.misc
+(ns metabase.server.middleware.misc
   "Misc Ring middleware."
   (:require [clojure.tools.logging :as log]
             [metabase.api.common :as api]
             metabase.async.streaming-response
             [metabase.db :as mdb]
-            [metabase.middleware.util :as middleware.u]
             [metabase.public-settings :as public-settings]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.util.i18n :refer [trs]])
   (:import clojure.core.async.impl.channels.ManyToManyChannel
            metabase.async.streaming_response.StreamingResponse))
diff --git a/src/metabase/middleware/security.clj b/src/metabase/server/middleware/security.clj
similarity index 98%
rename from src/metabase/middleware/security.clj
rename to src/metabase/server/middleware/security.clj
index 47483c4ebb836ae88a3c3f266f9fc59febba5181..608de9d7d98b1e2ded42ef893a1fea276be5d140 100644
--- a/src/metabase/middleware/security.clj
+++ b/src/metabase/server/middleware/security.clj
@@ -1,12 +1,12 @@
-(ns metabase.middleware.security
+(ns metabase.server.middleware.security
   "Ring middleware for adding security-related headers to API responses."
   (:require [clojure.java.io :as io]
             [clojure.string :as str]
             [java-time :as t]
             [metabase.config :as config]
-            [metabase.middleware.util :as middleware.u]
             [metabase.models.setting :refer [defsetting]]
             [metabase.public-settings :as public-settings]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.util.i18n :as ui18n :refer [deferred-tru]]
             [ring.util.codec :refer [base64-encode]])
   (:import java.security.MessageDigest))
diff --git a/src/metabase/middleware/session.clj b/src/metabase/server/middleware/session.clj
similarity index 99%
rename from src/metabase/middleware/session.clj
rename to src/metabase/server/middleware/session.clj
index b607f55eaf1527da1e2fa7827e6d142877daaec2..eee00507354d96a0aca3e407fa5944734e1192d5 100644
--- a/src/metabase/middleware/session.clj
+++ b/src/metabase/server/middleware/session.clj
@@ -1,4 +1,4 @@
-(ns metabase.middleware.session
+(ns metabase.server.middleware.session
   "Ring middleware related to session (binding current user and permissions)."
   (:require [clojure.java.jdbc :as jdbc]
             [clojure.string :as str]
diff --git a/src/metabase/middleware/ssl.clj b/src/metabase/server/middleware/ssl.clj
similarity index 94%
rename from src/metabase/middleware/ssl.clj
rename to src/metabase/server/middleware/ssl.clj
index 016e7bd6d9dbe6fb42c17f88af4abf9cb6d40981..70f6d37593120bc42e2f30e07d0c68c82c5d451c 100644
--- a/src/metabase/middleware/ssl.clj
+++ b/src/metabase/server/middleware/ssl.clj
@@ -1,8 +1,8 @@
-(ns metabase.middleware.ssl
+(ns metabase.server.middleware.ssl
   "Middleware for redirecting users to HTTPS sessions"
   (:require [clojure.string :as str]
-            [metabase.middleware.session :as mw.session]
             [metabase.public-settings :as public-settings]
+            [metabase.server.middleware.session :as mw.session]
             [ring.util.request :as req]
             [ring.util.response :as resp]))
 
diff --git a/src/metabase/middleware/util.clj b/src/metabase/server/middleware/util.clj
similarity index 98%
rename from src/metabase/middleware/util.clj
rename to src/metabase/server/middleware/util.clj
index 4fa6ef1189a367faa985433387b112ae8499fef7..41485de9d063f00199c109436429119823d119a9 100644
--- a/src/metabase/middleware/util.clj
+++ b/src/metabase/server/middleware/util.clj
@@ -1,4 +1,4 @@
-(ns metabase.middleware.util
+(ns metabase.server.middleware.util
   "Ring middleware utility functions."
   (:require [clojure.string :as str]))
 
diff --git a/src/metabase/routes.clj b/src/metabase/server/routes.clj
similarity index 97%
rename from src/metabase/routes.clj
rename to src/metabase/server/routes.clj
index c1172c59e90c1898720e1c4e60686d368e5424da..c354611c96e1a8c9b682a0a3ab7a35dd6e76186d 100644
--- a/src/metabase/routes.clj
+++ b/src/metabase/server/routes.clj
@@ -1,4 +1,4 @@
-(ns metabase.routes
+(ns metabase.server.routes
   "Main Compojure routes tables. See https://github.com/weavejester/compojure/wiki/Routes-In-Detail for details about
    how these work. `/api/` routes are in `metabase.api.routes`."
   (:require [compojure.core :refer [context defroutes GET]]
@@ -8,7 +8,7 @@
             [metabase.core.initialization-status :as init-status]
             [metabase.plugins.classloader :as classloader]
             [metabase.public-settings :as public-settings]
-            [metabase.routes.index :as index]
+            [metabase.server.routes.index :as index]
             [metabase.util :as u]
             [ring.util.response :as resp]))
 
diff --git a/src/metabase/routes/index.clj b/src/metabase/server/routes/index.clj
similarity index 99%
rename from src/metabase/routes/index.clj
rename to src/metabase/server/routes/index.clj
index 5cb12ea8f1f9bcaee111f83f4e971490cf7b4176..daab2cd3006c602e571097092169ca21f5fb3487 100644
--- a/src/metabase/routes/index.clj
+++ b/src/metabase/server/routes/index.clj
@@ -1,4 +1,4 @@
-(ns metabase.routes.index
+(ns metabase.server.routes.index
   "Logic related to loading various versions of the index.html template. The actual template lives in
   `resources/frontend_client/index_template.html`; when the frontend is built (e.g. via `./bin/build frontend`)
   different versions that include the FE app are created as `index.html`, `public.html`, and `embed.html`."
diff --git a/test/metabase/api/alert_test.clj b/test/metabase/api/alert_test.clj
index 027e566282b3a3e96d0258834460dc8ea4b2433f..70e8da71ea4239bd2321054a2d8d9095e9da8f56 100644
--- a/test/metabase/api/alert_test.clj
+++ b/test/metabase/api/alert_test.clj
@@ -4,12 +4,12 @@
             [medley.core :as m]
             [metabase.email-test :as et]
             [metabase.http-client :as http]
-            [metabase.middleware.util :as middleware.u]
             [metabase.models :refer [Card Collection Pulse PulseCard PulseChannel PulseChannelRecipient]]
             [metabase.models.permissions :as perms]
             [metabase.models.permissions-group :as group]
             [metabase.models.pulse :as pulse]
             [metabase.models.pulse-test :as pulse-test]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.test :as mt]
             [metabase.test.data.users :as users :refer :all]
             [metabase.test.mock.util :refer [pulse-channel-defaults]]
diff --git a/test/metabase/api/card_test.clj b/test/metabase/api/card_test.clj
index 29af396132b53a70353f0e968524f7d86fced930..c63b7cb22f99b8086fa7010bb3f32ef3739b32fa 100644
--- a/test/metabase/api/card_test.clj
+++ b/test/metabase/api/card_test.clj
@@ -9,13 +9,13 @@
             [metabase.api.card :as card-api]
             [metabase.driver.sql-jdbc.execute :as sql-jdbc.execute]
             [metabase.http-client :as http :refer :all]
-            [metabase.middleware.util :as middleware.u]
             [metabase.models :refer [Card CardFavorite Collection Dashboard Database Pulse PulseCard PulseChannel PulseChannelRecipient Table ViewLog]]
             [metabase.models.permissions :as perms]
             [metabase.models.permissions-group :as perms-group]
             [metabase.query-processor.async :as qp.async]
             [metabase.query-processor.middleware.constraints :as constraints]
             [metabase.query-processor.middleware.results-metadata :as results-metadata]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.test :as mt]
             [metabase.util :as u]
             [schema.core :as s]
diff --git a/test/metabase/api/common_test.clj b/test/metabase/api/common_test.clj
index 8a46eb82209dbe77ea21ddbb27f6f6d222751d76..5541406519558a1fdeba07e1285612a75d49a991 100644
--- a/test/metabase/api/common_test.clj
+++ b/test/metabase/api/common_test.clj
@@ -2,9 +2,9 @@
   (:require [clojure.test :refer :all]
             [metabase.api.common :as api :refer :all]
             [metabase.api.common.internal :refer :all]
-            [metabase.middleware.exceptions :as mw.exceptions]
-            [metabase.middleware.misc :as mw.misc]
-            [metabase.middleware.security :as mw.security]
+            [metabase.server.middleware.exceptions :as mw.exceptions]
+            [metabase.server.middleware.misc :as mw.misc]
+            [metabase.server.middleware.security :as mw.security]
             [metabase.test.data :refer :all]
             [metabase.util.schema :as su]))
 
diff --git a/test/metabase/api/dashboard_test.clj b/test/metabase/api/dashboard_test.clj
index 43b77fac69c2e918f7926420dfebb6442f4e7fed..5963ddb53f22b30304e1a586b7c77b67ac51d994 100644
--- a/test/metabase/api/dashboard_test.clj
+++ b/test/metabase/api/dashboard_test.clj
@@ -8,7 +8,6 @@
             [metabase.api.card-test :as card-api-test]
             [metabase.api.dashboard :as dashboard-api]
             [metabase.http-client :as http]
-            [metabase.middleware.util :as middleware.u]
             [metabase.models.card :refer [Card]]
             [metabase.models.collection :refer [Collection]]
             [metabase.models.dashboard :refer [Dashboard]]
@@ -22,6 +21,7 @@
             [metabase.models.pulse :refer [Pulse]]
             [metabase.models.revision :refer [Revision]]
             [metabase.models.table :refer [Table]]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.test :as mt]
             [metabase.util :as u]
             [ring.util.codec :as codec]
diff --git a/test/metabase/api/geojson_test.clj b/test/metabase/api/geojson_test.clj
index 644a1100ef09b3063d492310e06bc48d67d5c037..6f1cccd06b46d02c0f650de3aa96f0a44c4a8e7e 100644
--- a/test/metabase/api/geojson_test.clj
+++ b/test/metabase/api/geojson_test.clj
@@ -2,7 +2,7 @@
   (:require [clojure.test :refer :all]
             [metabase.api.geojson :as geojson-api]
             [metabase.http-client :as client]
-            [metabase.middleware.security :as mw.security]
+            [metabase.server.middleware.security :as mw.security]
             [metabase.test :as mt]
             [metabase.util :as u]
             [schema.core :as s]))
diff --git a/test/metabase/api/metric_test.clj b/test/metabase/api/metric_test.clj
index 1bec0191fd164abb15cdd9a8033c165f839ae287..5c77ffc57c743df09f30e0601f0ce822a0bfa69a 100644
--- a/test/metabase/api/metric_test.clj
+++ b/test/metabase/api/metric_test.clj
@@ -2,13 +2,13 @@
   "Tests for /api/metric endpoints."
   (:require [expectations :refer [expect]]
             [metabase.http-client :as http]
-            [metabase.middleware.util :as middleware.u]
             [metabase.models.database :refer [Database]]
             [metabase.models.metric :as metric :refer [Metric]]
             [metabase.models.permissions :as perms]
             [metabase.models.permissions-group :as group]
             [metabase.models.revision :refer [Revision]]
             [metabase.models.table :refer [Table]]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.test.data :as data :refer :all]
             [metabase.test.data.users :refer [fetch-user user->client user->id]]
             [metabase.test.util :as tu]
diff --git a/test/metabase/api/notify_test.clj b/test/metabase/api/notify_test.clj
index 9cb2a061520af3ae677fa0d0e61d70ea0b45607a..aefe2a327cc809f056b9ad7642164d98d87aac95 100644
--- a/test/metabase/api/notify_test.clj
+++ b/test/metabase/api/notify_test.clj
@@ -2,7 +2,7 @@
   (:require [clj-http.client :as client]
             [clojure.test :refer :all]
             [metabase.http-client :as http]
-            [metabase.middleware.util :as middleware.u]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.test.fixtures :as fixtures]))
 
 (use-fixtures :once (fixtures/initialize :db :web-server))
diff --git a/test/metabase/api/pulse_test.clj b/test/metabase/api/pulse_test.clj
index 542a41ddf075aa3df6c81e8e65b5abb41a0e5e50..586c6a3bbc7d43009bd6e51be73ded17a3cd86e2 100644
--- a/test/metabase/api/pulse_test.clj
+++ b/test/metabase/api/pulse_test.clj
@@ -5,13 +5,13 @@
             [metabase.api.pulse :as pulse-api]
             [metabase.http-client :as http]
             [metabase.integrations.slack :as slack]
-            [metabase.middleware.util :as middleware.u]
             [metabase.models :refer [Card Collection Dashboard Pulse PulseCard PulseChannel PulseChannelRecipient]]
             [metabase.models.permissions :as perms]
             [metabase.models.permissions-group :as perms-group]
             [metabase.models.pulse :as pulse]
             [metabase.models.pulse-test :as pulse-test]
             [metabase.pulse.render.png :as png]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.test :as mt]
             [metabase.test.mock.util :refer [pulse-channel-defaults]]
             [metabase.util :as u]
diff --git a/test/metabase/api/segment_test.clj b/test/metabase/api/segment_test.clj
index 9feed783d80525396409b7fc260b9dc88697765d..fc9b9d591ab1b8520d230057afac01988dc9ef4a 100644
--- a/test/metabase/api/segment_test.clj
+++ b/test/metabase/api/segment_test.clj
@@ -2,13 +2,13 @@
   "Tests for /api/segment endpoints."
   (:require [expectations :refer [expect]]
             [metabase.http-client :as http]
-            [metabase.middleware.util :as middleware.u]
             [metabase.models.database :refer [Database]]
             [metabase.models.permissions :as perms]
             [metabase.models.permissions-group :as group]
             [metabase.models.revision :refer [Revision]]
             [metabase.models.segment :as segment :refer [Segment]]
             [metabase.models.table :refer [Table]]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.test.data :refer :all]
             [metabase.test.data.users :refer :all]
             [metabase.test.util :as tu]
diff --git a/test/metabase/api/table_test.clj b/test/metabase/api/table_test.clj
index 8f44511377aa9d2c9a8508cd01ee19f082c1e381..d693271c8886ed4152531239778b0ad7b343f59a 100644
--- a/test/metabase/api/table_test.clj
+++ b/test/metabase/api/table_test.clj
@@ -7,11 +7,11 @@
             [metabase.api.table :as table-api]
             [metabase.driver.util :as driver.u]
             [metabase.http-client :as http]
-            [metabase.middleware.util :as middleware.u]
             [metabase.models :refer [Card Database Field FieldValues Table]]
             [metabase.models.permissions :as perms]
             [metabase.models.permissions-group :as perms-group]
             [metabase.models.table :as table]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.sync :as sync]
             [metabase.test :as mt]
             [metabase.test.data :as data]
diff --git a/test/metabase/api/user_test.clj b/test/metabase/api/user_test.clj
index 7731dd9d33a6e84abc864a6387fcf487acc32ee9..c4820f2d2d0834d15e6ab80992763cac70c5ae85 100644
--- a/test/metabase/api/user_test.clj
+++ b/test/metabase/api/user_test.clj
@@ -4,12 +4,12 @@
             [expectations :refer [expect]]
             [metabase.email-test :as et]
             [metabase.http-client :as http]
-            [metabase.middleware.util :as middleware.u]
             [metabase.models.collection :as collection :refer [Collection]]
             [metabase.models.permissions-group :as group :refer [PermissionsGroup]]
             [metabase.models.permissions-group-membership :refer [PermissionsGroupMembership]]
             [metabase.models.user :refer [User]]
             [metabase.models.user-test :as user-test]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.test :as mt]
             [metabase.test.data :refer :all]
             [metabase.test.fixtures :as fixtures]
diff --git a/test/metabase/http_client.clj b/test/metabase/http_client.clj
index a2c8d603fbc6127e759cb4b554c4b46b0450d279..84414f6f9db68d74b81d18a8be274067c68845d1 100644
--- a/test/metabase/http_client.clj
+++ b/test/metabase/http_client.clj
@@ -7,7 +7,7 @@
             [clojure.tools.logging :as log]
             [java-time :as java-time]
             [metabase.config :as config]
-            [metabase.middleware.session :as mw.session]
+            [metabase.server.middleware.session :as mw.session]
             [metabase.test.initialize :as initialize]
             [metabase.test.util.log :as tu.log]
             [metabase.util :as u]
diff --git a/test/metabase/models/database_test.clj b/test/metabase/models/database_test.clj
index 3ee44b9e30b3993b38156f4b10ecf30cfbee2a88..d271f448ce2cc21b94a6f2da91fb846d7f07cb43 100644
--- a/test/metabase/models/database_test.clj
+++ b/test/metabase/models/database_test.clj
@@ -2,12 +2,12 @@
   (:require [cheshire.core :refer [decode encode]]
             [clojure.string :as str]
             [clojure.test :refer :all]
-            [metabase.middleware.session :as mw.session]
             [metabase.models :refer [Database]]
             [metabase.models.database :as mdb]
             [metabase.models.permissions :as perms]
             [metabase.models.user :as user]
             [metabase.plugins.classloader :as classloader]
+            [metabase.server.middleware.session :as mw.session]
             [metabase.task :as task]
             [metabase.test :as mt]
             [schema.core :as s]
diff --git a/test/metabase/models/session_test.clj b/test/metabase/models/session_test.clj
index ba24f47bd32e52d930e1cce1323f782ce1fb19f1..1db82df7b47442fced76ba136e1a4243de551c83 100644
--- a/test/metabase/models/session_test.clj
+++ b/test/metabase/models/session_test.clj
@@ -1,7 +1,7 @@
 (ns metabase.models.session-test
   (:require [expectations :refer [expect]]
-            [metabase.middleware.misc :as mw.misc]
             [metabase.models.session :as session :refer [Session]]
+            [metabase.server.middleware.misc :as mw.misc]
             [metabase.test.data.users :as test-users]
             [toucan.db :as db]
             [toucan.models :as t.models]))
diff --git a/test/metabase/middleware/auth_test.clj b/test/metabase/server/middleware/auth_test.clj
similarity index 95%
rename from test/metabase/middleware/auth_test.clj
rename to test/metabase/server/middleware/auth_test.clj
index c3d6bb5c02ec0d2617ae21d6e654b1a14a1e822b..70437732543c61005a2b2ddcd6c8101621c1f3b9 100644
--- a/test/metabase/middleware/auth_test.clj
+++ b/test/metabase/server/middleware/auth_test.clj
@@ -1,10 +1,10 @@
-(ns metabase.middleware.auth-test
+(ns metabase.server.middleware.auth-test
   (:require [clojure.test :refer :all]
             [java-time :as t]
-            [metabase.middleware.auth :as mw.auth]
-            [metabase.middleware.session :as mw.session]
-            [metabase.middleware.util :as middleware.u]
             [metabase.models.session :refer [Session]]
+            [metabase.server.middleware.auth :as mw.auth]
+            [metabase.server.middleware.session :as mw.session]
+            [metabase.server.middleware.util :as middleware.u]
             [metabase.test :as mt]
             [metabase.test.data.users :as test-users]
             [metabase.test.fixtures :as fixtures]
diff --git a/test/metabase/middleware/json_test.clj b/test/metabase/server/middleware/json_test.clj
similarity index 85%
rename from test/metabase/middleware/json_test.clj
rename to test/metabase/server/middleware/json_test.clj
index 1393c1af8f0d4b1a07a1ace38404cead462a1495..03db9f5aded15346a10d8afbaafbc0b37fdad9b3 100644
--- a/test/metabase/middleware/json_test.clj
+++ b/test/metabase/server/middleware/json_test.clj
@@ -1,11 +1,11 @@
-(ns metabase.middleware.json-test
+(ns metabase.server.middleware.json-test
   (:require [cheshire.core :as json]
             [expectations :refer [expect]]
             [metabase.plugins.classloader :as classloader]))
 
 ;;; JSON encoding tests
 ;; Required here so so custom Cheshire encoders are loaded
-(classloader/require 'metabase.middleware.json)
+(classloader/require 'metabase.server.middleware.json)
 
 ;; Check that we encode byte arrays as the hex values of their first four bytes
 (expect
diff --git a/test/metabase/middleware/log_test.clj b/test/metabase/server/middleware/log_test.clj
similarity index 75%
rename from test/metabase/middleware/log_test.clj
rename to test/metabase/server/middleware/log_test.clj
index c480a0109f9248b37be728d17ec092c35884df63..4a89a06e28a99439670c2697e0c82d8d0b47d04a 100644
--- a/test/metabase/middleware/log_test.clj
+++ b/test/metabase/server/middleware/log_test.clj
@@ -1,6 +1,6 @@
-(ns metabase.middleware.log-test
+(ns metabase.server.middleware.log-test
   (:require [clojure.test :refer :all]
-            [metabase.middleware.log :as log]))
+            [metabase.server.middleware.log :as log]))
 
 (deftest log-info-input-tests
   (testing "log-info handles nil status input"
diff --git a/test/metabase/middleware/misc_test.clj b/test/metabase/server/middleware/misc_test.clj
similarity index 94%
rename from test/metabase/middleware/misc_test.clj
rename to test/metabase/server/middleware/misc_test.clj
index d95b4d340592e4ef079d6d6e4dc0a3fe65dd015b..93daf616b2d6accada2b395d99cd3f46a0779c39 100644
--- a/test/metabase/middleware/misc_test.clj
+++ b/test/metabase/server/middleware/misc_test.clj
@@ -1,8 +1,8 @@
-(ns metabase.middleware.misc-test
+(ns metabase.server.middleware.misc-test
   (:require [clojure.test :refer :all]
             [medley.core :as m]
-            [metabase.middleware.misc :as mw.misc]
             [metabase.public-settings :as public-settings]
+            [metabase.server.middleware.misc :as mw.misc]
             [metabase.test :as mt]
             [ring.mock.request :as ring.mock]))
 
diff --git a/test/metabase/middleware/security_test.clj b/test/metabase/server/middleware/security_test.clj
similarity index 95%
rename from test/metabase/middleware/security_test.clj
rename to test/metabase/server/middleware/security_test.clj
index ec474ec69baeef66392a52fe8ebd3dd4aef63a02..a61f184c5abc0fd5573df908c95fe62fb388aad4 100644
--- a/test/metabase/middleware/security_test.clj
+++ b/test/metabase/server/middleware/security_test.clj
@@ -1,7 +1,7 @@
-(ns metabase.middleware.security-test
+(ns metabase.server.middleware.security-test
   (:require [clojure.string :as str]
             [clojure.test :refer :all]
-            [metabase.middleware.security :as mw.security]
+            [metabase.server.middleware.security :as mw.security]
             [metabase.test.util :as tu]))
 
 (defn- csp-frame-ancestors-directive
diff --git a/test/metabase/middleware/session_test.clj b/test/metabase/server/middleware/session_test.clj
similarity index 99%
rename from test/metabase/middleware/session_test.clj
rename to test/metabase/server/middleware/session_test.clj
index 2e4f3aeebf6a914fcb1ca1297f24156343c30030..0cad59cef5291a2dd27322be00ff7c14b9446794 100644
--- a/test/metabase/middleware/session_test.clj
+++ b/test/metabase/server/middleware/session_test.clj
@@ -1,4 +1,4 @@
-(ns metabase.middleware.session-test
+(ns metabase.server.middleware.session-test
   (:require [clojure.string :as str]
             [clojure.test :refer :all]
             [environ.core :as env]
@@ -8,8 +8,8 @@
             [metabase.core.initialization-status :as init-status]
             [metabase.db :as mdb]
             [metabase.driver.sql.query-processor :as sql.qp]
-            [metabase.middleware.session :as mw.session]
             [metabase.models :refer [Session User]]
+            [metabase.server.middleware.session :as mw.session]
             [metabase.test :as mt]
             [metabase.test.data.users :as test-users]
             [metabase.util.i18n :as i18n]
diff --git a/test/metabase/middleware/ssl_test.clj b/test/metabase/server/middleware/ssl_test.clj
similarity index 97%
rename from test/metabase/middleware/ssl_test.clj
rename to test/metabase/server/middleware/ssl_test.clj
index 3ae029249fb3dbec6c0c4941d7ae1042ac0ab496..f89aa005d1d5db79c86a17b6af2f5aa98b00d35b 100644
--- a/test/metabase/middleware/ssl_test.clj
+++ b/test/metabase/server/middleware/ssl_test.clj
@@ -1,6 +1,6 @@
-(ns metabase.middleware.ssl-test
+(ns metabase.server.middleware.ssl-test
   (:require [clojure.test :refer :all]
-            [metabase.middleware.ssl :as mw.ssl]
+            [metabase.server.middleware.ssl :as mw.ssl]
             [metabase.test.util :as tu]
             [ring.mock.request :as mock]
             [ring.util.response :as response]))
diff --git a/test/metabase/middleware/util_test.clj b/test/metabase/server/middleware/util_test.clj
similarity index 88%
rename from test/metabase/middleware/util_test.clj
rename to test/metabase/server/middleware/util_test.clj
index fe3debb2358f94bf10c723131b40dc8d0ec9e15a..52b57ea46b13862fea5b8547e704e5bbf576ee5a 100644
--- a/test/metabase/middleware/util_test.clj
+++ b/test/metabase/server/middleware/util_test.clj
@@ -1,6 +1,6 @@
-(ns metabase.middleware.util-test
+(ns metabase.server.middleware.util-test
   (:require [expectations :refer [expect]]
-            [metabase.middleware.util :as mw.util]))
+            [metabase.server.middleware.util :as mw.util]))
 
 (defn- https? [headers]
   (mw.util/https-request? {:headers headers}))
diff --git a/test/metabase/routes/index_test.clj b/test/metabase/server/routes/index_test.clj
similarity index 96%
rename from test/metabase/routes/index_test.clj
rename to test/metabase/server/routes/index_test.clj
index 7fc20ba926889f1085530e4c8b8c4f89394a4b80..7f03ee9ab7a93ef28df00f8b87238f76f697ddd5 100644
--- a/test/metabase/routes/index_test.clj
+++ b/test/metabase/server/routes/index_test.clj
@@ -1,7 +1,7 @@
-(ns metabase.routes.index-test
+(ns metabase.server.routes.index-test
   (:require [cheshire.core :as json]
             [clojure.test :refer :all]
-            [metabase.routes.index :as index]
+            [metabase.server.routes.index :as index]
             [metabase.test :as mt]
             [metabase.util.i18n :as i18n]))
 
diff --git a/test/metabase/test/data/users.clj b/test/metabase/test/data/users.clj
index db9e4a63836584ae85f96e3e3eb9af1c286bc919..9fe2b325c940c72e18f5d88bfdee111bb967af46 100644
--- a/test/metabase/test/data/users.clj
+++ b/test/metabase/test/data/users.clj
@@ -3,8 +3,8 @@
   (:require [clojure.test :as t]
             [medley.core :as m]
             [metabase.http-client :as http]
-            [metabase.middleware.session :as mw.session]
             [metabase.models.user :as user :refer [User]]
+            [metabase.server.middleware.session :as mw.session]
             [metabase.test.initialize :as initialize]
             [metabase.util :as u]
             [schema.core :as s]
diff --git a/test/metabase/test/initialize/web_server.clj b/test/metabase/test/initialize/web_server.clj
index 96843b5dbfa1cfbcb17448126be28cb6f6693779..6eaa8701fce79086b9ceb01344527c90405913e5 100644
--- a/test/metabase/test/initialize/web_server.clj
+++ b/test/metabase/test/initialize/web_server.clj
@@ -1,9 +1,9 @@
 (ns metabase.test.initialize.web-server
   (:require [metabase.config :as config]
             [metabase.core.initialization-status :as init-status]
-            [metabase.handler :as handler]
             [metabase.models.setting :as setting]
-            [metabase.server :as server]))
+            [metabase.server :as server]
+            [metabase.server.handler :as handler]))
 
 (defn- test-handler
   ([request]