diff --git a/src/api/admin.rs b/src/api/admin.rs
index 6e10f0b6735f086254f223d135a91680c7d4b283..656f63bfc7a2feb9c438da441368ebc8829ed31e 100644
--- a/src/api/admin.rs
+++ b/src/api/admin.rs
@@ -6,10 +6,10 @@ use std::env;
 use rocket::serde::json::Json;
 use rocket::{
     form::Form,
-    http::{Cookie, CookieJar, SameSite, Status},
-    request::{self, FromRequest, Outcome, Request},
+    http::{Cookie, CookieJar, MediaType, SameSite, Status},
+    request::{FromRequest, Outcome, Request},
     response::{content::RawHtml as Html, Redirect},
-    Route,
+    Catcher, Route,
 };
 
 use crate::{
@@ -31,7 +31,6 @@ pub fn routes() -> Vec<Route> {
     }
 
     routes![
-        admin_login,
         get_users_json,
         get_user_json,
         post_admin_login,
@@ -57,6 +56,14 @@ pub fn routes() -> Vec<Route> {
     ]
 }
 
+pub fn catchers() -> Vec<Catcher> {
+    if !CONFIG.disable_admin_token() && !CONFIG.is_admin_token_set() {
+        catchers![]
+    } else {
+        catchers![admin_login]
+    }
+}
+
 static DB_TYPE: Lazy<&str> = Lazy::new(|| {
     DbConnType::from_url(&CONFIG.database_url())
         .map(|t| match t {
@@ -87,17 +94,6 @@ fn admin_path() -> String {
     format!("{}{}", CONFIG.domain_path(), ADMIN_PATH)
 }
 
-struct Referer(Option<String>);
-
-#[rocket::async_trait]
-impl<'r> FromRequest<'r> for Referer {
-    type Error = ();
-
-    async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
-        Outcome::Success(Referer(request.headers().get_one("Referer").map(str::to_string)))
-    }
-}
-
 #[derive(Debug)]
 struct IpHeader(Option<String>);
 
@@ -120,25 +116,8 @@ impl<'r> FromRequest<'r> for IpHeader {
     }
 }
 
-/// Used for `Location` response headers, which must specify an absolute URI
-/// (see https://tools.ietf.org/html/rfc2616#section-14.30).
-fn admin_url(referer: Referer) -> String {
-    // If we get a referer use that to make it work when, DOMAIN is not set
-    if let Some(mut referer) = referer.0 {
-        if let Some(start_index) = referer.find(ADMIN_PATH) {
-            referer.truncate(start_index + ADMIN_PATH.len());
-            return referer;
-        }
-    }
-
-    if CONFIG.domain_set() {
-        // Don't use CONFIG.domain() directly, since the user may want to keep a
-        // trailing slash there, particularly when running under a subpath.
-        format!("{}{}{}", CONFIG.domain_origin(), CONFIG.domain_path(), ADMIN_PATH)
-    } else {
-        // Last case, when no referer or domain set, technically invalid but better than nothing
-        ADMIN_PATH.to_string()
-    }
+fn admin_url() -> String {
+    format!("{}{}", CONFIG.domain_origin(), admin_path())
 }
 
 #[derive(Responder)]
@@ -151,18 +130,23 @@ enum AdminResponse {
     TooManyRequests(ApiResult<Html<String>>),
 }
 
-#[get("/", rank = 2)]
-fn admin_login() -> ApiResult<Html<String>> {
-    render_admin_login(None)
+#[catch(401)]
+fn admin_login(request: &Request<'_>) -> ApiResult<Html<String>> {
+    if request.format() == Some(&MediaType::JSON) {
+        err_code!("Authorization failed.", Status::Unauthorized.code);
+    }
+    let redirect = request.segments::<std::path::PathBuf>(0..).unwrap_or_default().display().to_string();
+    render_admin_login(None, Some(redirect))
 }
 
-fn render_admin_login(msg: Option<&str>) -> ApiResult<Html<String>> {
+fn render_admin_login(msg: Option<&str>, redirect: Option<String>) -> ApiResult<Html<String>> {
     // If there is an error, show it
     let msg = msg.map(|msg| format!("Error: {msg}"));
     let json = json!({
         "page_content": "admin/login",
         "version": VERSION,
         "error": msg,
+        "redirect": redirect,
         "urlpath": CONFIG.domain_path()
     });
 
@@ -174,20 +158,25 @@ fn render_admin_login(msg: Option<&str>) -> ApiResult<Html<String>> {
 #[derive(FromForm)]
 struct LoginForm {
     token: String,
+    redirect: Option<String>,
 }
 
 #[post("/", data = "<data>")]
-fn post_admin_login(data: Form<LoginForm>, cookies: &CookieJar<'_>, ip: ClientIp) -> AdminResponse {
+fn post_admin_login(data: Form<LoginForm>, cookies: &CookieJar<'_>, ip: ClientIp) -> Result<Redirect, AdminResponse> {
     let data = data.into_inner();
+    let redirect = data.redirect;
 
     if crate::ratelimit::check_limit_admin(&ip.ip).is_err() {
-        return AdminResponse::TooManyRequests(render_admin_login(Some("Too many requests, try again later.")));
+        return Err(AdminResponse::TooManyRequests(render_admin_login(
+            Some("Too many requests, try again later."),
+            redirect,
+        )));
     }
 
     // If the token is invalid, redirect to login page
     if !_validate_token(&data.token) {
         error!("Invalid admin token. IP: {}", ip.ip);
-        AdminResponse::Unauthorized(render_admin_login(Some("Invalid admin token, please try again.")))
+        Err(AdminResponse::Unauthorized(render_admin_login(Some("Invalid admin token, please try again."), redirect)))
     } else {
         // If the token received is valid, generate JWT and save it as a cookie
         let claims = generate_admin_claims();
@@ -201,7 +190,11 @@ fn post_admin_login(data: Form<LoginForm>, cookies: &CookieJar<'_>, ip: ClientIp
             .finish();
 
         cookies.add(cookie);
-        AdminResponse::Ok(render_admin_page())
+        if let Some(redirect) = redirect {
+            Ok(Redirect::to(format!("{}{}", admin_path(), redirect)))
+        } else {
+            Err(AdminResponse::Ok(render_admin_page()))
+        }
     }
 }
 
@@ -258,7 +251,7 @@ fn render_admin_page() -> ApiResult<Html<String>> {
     Ok(Html(text))
 }
 
-#[get("/", rank = 1)]
+#[get("/")]
 fn admin_page(_token: AdminToken) -> ApiResult<Html<String>> {
     render_admin_page()
 }
@@ -314,9 +307,9 @@ async fn test_smtp(data: Json<InviteData>, _token: AdminToken) -> EmptyResult {
 }
 
 #[get("/logout")]
-fn logout(cookies: &CookieJar<'_>, referer: Referer) -> Redirect {
+fn logout(cookies: &CookieJar<'_>) -> Redirect {
     cookies.remove(Cookie::build(COOKIE_NAME, "").path(admin_path()).finish());
-    Redirect::temporary(admin_url(referer))
+    Redirect::to(admin_path())
 }
 
 #[get("/users")]
@@ -639,7 +632,7 @@ async fn diagnostics(_token: AdminToken, ip_header: IpHeader, mut conn: DbConn)
         "uses_proxy": uses_proxy,
         "db_type": *DB_TYPE,
         "db_version": get_sql_server_version(&mut conn).await,
-        "admin_url": format!("{}/diagnostics", admin_url(Referer(None))),
+        "admin_url": format!("{}/diagnostics", admin_url()),
         "overrides": &CONFIG.get_overrides().join(", "),
         "server_time_local": Local::now().format("%Y-%m-%d %H:%M:%S %Z").to_string(),
         "server_time": Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(), // Run the date/time check as the last item to minimize the difference
@@ -681,15 +674,15 @@ pub struct AdminToken {}
 impl<'r> FromRequest<'r> for AdminToken {
     type Error = &'static str;
 
-    async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
+    async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
         if CONFIG.disable_admin_token() {
-            Outcome::Success(AdminToken {})
+            Outcome::Success(Self {})
         } else {
             let cookies = request.cookies();
 
             let access_token = match cookies.get(COOKIE_NAME) {
                 Some(cookie) => cookie.value(),
-                None => return Outcome::Forward(()), // If there is no cookie, redirect to login
+                None => return Outcome::Failure((Status::Unauthorized, "Unauthorized")),
             };
 
             let ip = match ClientIp::from_request(request).await {
@@ -701,10 +694,10 @@ impl<'r> FromRequest<'r> for AdminToken {
                 // Remove admin cookie
                 cookies.remove(Cookie::build(COOKIE_NAME, "").path(admin_path()).finish());
                 error!("Invalid or expired admin JWT. IP: {}.", ip);
-                return Outcome::Forward(());
+                return Outcome::Failure((Status::Unauthorized, "Session expired"));
             }
 
-            Outcome::Success(AdminToken {})
+            Outcome::Success(Self {})
         }
     }
 }
diff --git a/src/api/mod.rs b/src/api/mod.rs
index 0861ea2d2ba9658171a4347aade54f09e4c130f3..06db310b607ecc8481459940db5c23c7e344f0b5 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -9,6 +9,7 @@ use rocket::serde::json::Json;
 use serde_json::Value;
 
 pub use crate::api::{
+    admin::catchers as admin_catchers,
     admin::routes as admin_routes,
     core::catchers as core_catchers,
     core::purge_sends,
diff --git a/src/main.rs b/src/main.rs
index 70cd5d9f53860fcb9fe828e03746986be87f539d..86bcc36cc879779fb4bb37a8c977b0460d90a48d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -436,6 +436,7 @@ async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error>
         .mount([basepath, "/notifications"].concat(), api::notifications_routes())
         .register([basepath, "/"].concat(), api::web_catchers())
         .register([basepath, "/api"].concat(), api::core_catchers())
+        .register([basepath, "/admin"].concat(), api::admin_catchers())
         .manage(pool)
         .manage(api::start_notification_server())
         .attach(util::AppHeaders())
diff --git a/src/static/templates/admin/diagnostics.hbs b/src/static/templates/admin/diagnostics.hbs
index 20d6f37120ae1cf1feb537851eb8d1c24721a73a..cb63eb4c5505d6b5999772d1c8e9a79992309e39 100644
--- a/src/static/templates/admin/diagnostics.hbs
+++ b/src/static/templates/admin/diagnostics.hbs
@@ -352,7 +352,13 @@
         supportString += "* Reverse proxy and version: \n";
         supportString += "* Other relevant information: \n";
 
-        let jsonResponse = await fetch('{{urlpath}}/admin/diagnostics/config');
+        let jsonResponse = await fetch('{{urlpath}}/admin/diagnostics/config', {
+                'headers': { 'Accept': 'application/json' }
+        });
+        if (!jsonResponse.ok) {
+                alert("Generation failed: " + jsonResponse.statusText);
+                throw new Error(jsonResponse);
+        }
         const configJson = await jsonResponse.json();
         supportString += "\n### Config (Generated via diagnostics page)\n<details><summary>Show Running Config</summary>\n"
         supportString += "\n**Environment settings which are overridden:** {{page_data.overrides}}\n"
diff --git a/src/static/templates/admin/login.hbs b/src/static/templates/admin/login.hbs
index 0492fcab9bacd270492993696d1a88bf6f7e21f2..20b1998ed5b6bd15e8b9894934918ca20b5fa107 100644
--- a/src/static/templates/admin/login.hbs
+++ b/src/static/templates/admin/login.hbs
@@ -12,8 +12,11 @@
             <h6 class="mb-0 text-white">Authentication key needed to continue</h6>
             <small>Please provide it below:</small>
 
-            <form class="form-inline" method="post">
+            <form class="form-inline" method="post" action="{{urlpath}}/admin">
                 <input type="password" class="form-control w-50 mr-2" name="token" placeholder="Enter admin token" autofocus="autofocus">
+                {{#if redirect}}
+                <input type="hidden" id="redirect" name="redirect" value="/{{redirect}}">
+                {{/if}}
                 <button type="submit" class="btn btn-primary">Enter</button>
             </form>
         </div>