diff --git a/modules/setting/indexer.go b/modules/setting/indexer.go
index 15f6150242..cec364d370 100644
--- a/modules/setting/indexer.go
+++ b/modules/setting/indexer.go
@@ -58,7 +58,7 @@ func loadIndexerFrom(rootCfg ConfigProvider) {
 		if !filepath.IsAbs(Indexer.IssuePath) {
 			Indexer.IssuePath = filepath.ToSlash(filepath.Join(AppWorkPath, Indexer.IssuePath))
 		}
-		fatalDuplicatedPath("issue_indexer", Indexer.IssuePath)
+		checkOverlappedPath("indexer.ISSUE_INDEXER_PATH", Indexer.IssuePath)
 	} else {
 		Indexer.IssueConnStr = sec.Key("ISSUE_INDEXER_CONN_STR").MustString(Indexer.IssueConnStr)
 		if Indexer.IssueType == "meilisearch" {
diff --git a/modules/setting/path.go b/modules/setting/path.go
index b2cca0acbf..0fdc305aa1 100644
--- a/modules/setting/path.go
+++ b/modules/setting/path.go
@@ -66,12 +66,8 @@ func init() {
 		AppWorkPath = filepath.Dir(AppPath)
 	}
 
-	fatalDuplicatedPath("app_work_path", AppWorkPath)
-
 	appWorkPathBuiltin = AppWorkPath
 	customPathBuiltin = CustomPath
-
-	fatalDuplicatedPath("custom_path", CustomPath)
 	customConfBuiltin = CustomConf
 }
 
diff --git a/modules/setting/repository.go b/modules/setting/repository.go
index 7990021aaa..a332d6adb3 100644
--- a/modules/setting/repository.go
+++ b/modules/setting/repository.go
@@ -286,7 +286,7 @@ func loadRepositoryFrom(rootCfg ConfigProvider) {
 		RepoRootPath = filepath.Clean(RepoRootPath)
 	}
 
-	fatalDuplicatedPath("repository.ROOT", RepoRootPath)
+	checkOverlappedPath("repository.ROOT", RepoRootPath)
 
 	defaultDetectedCharsetsOrder := make([]string, 0, len(Repository.DetectedCharsetsOrder))
 	for _, charset := range Repository.DetectedCharsetsOrder {
diff --git a/modules/setting/server.go b/modules/setting/server.go
index 0dea4e1ac7..315faaeb21 100644
--- a/modules/setting/server.go
+++ b/modules/setting/server.go
@@ -324,7 +324,6 @@ func loadServerFrom(rootCfg ConfigProvider) {
 	if !filepath.IsAbs(AppDataPath) {
 		AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, AppDataPath))
 	}
-	fatalDuplicatedPath("app_data_path", AppDataPath)
 
 	EnableGzip = sec.Key("ENABLE_GZIP").MustBool()
 	EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false)
@@ -332,7 +331,7 @@ func loadServerFrom(rootCfg ConfigProvider) {
 	if !filepath.IsAbs(PprofDataPath) {
 		PprofDataPath = filepath.Join(AppWorkPath, PprofDataPath)
 	}
-	fatalDuplicatedPath("pprof_data_path", PprofDataPath)
+	checkOverlappedPath("server.PPROF_DATA_PATH", PprofDataPath)
 
 	landingPage := sec.Key("LANDING_PAGE").MustString("home")
 	switch landingPage {
diff --git a/modules/setting/session.go b/modules/setting/session.go
index 70497e5eaa..3cb1bfe7b5 100644
--- a/modules/setting/session.go
+++ b/modules/setting/session.go
@@ -46,7 +46,7 @@ func loadSessionFrom(rootCfg ConfigProvider) {
 	SessionConfig.ProviderConfig = strings.Trim(sec.Key("PROVIDER_CONFIG").MustString(filepath.Join(AppDataPath, "sessions")), "\" ")
 	if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) {
 		SessionConfig.ProviderConfig = filepath.Join(AppWorkPath, SessionConfig.ProviderConfig)
-		fatalDuplicatedPath("session", SessionConfig.ProviderConfig)
+		checkOverlappedPath("session.PROVIDER_CONFIG", SessionConfig.ProviderConfig)
 	}
 	SessionConfig.CookieName = sec.Key("COOKIE_NAME").MustString("i_like_gitea")
 	SessionConfig.CookiePath = AppSubURL
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index 13821da44d..6aca9ec6cf 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -230,11 +230,14 @@ func LoadSettingsForInstall() {
 	loadMailerFrom(CfgProvider)
 }
 
-var uniquePaths = make(map[string]string)
+var configuredPaths = make(map[string]string)
 
-func fatalDuplicatedPath(name, p string) {
-	if targetName, ok := uniquePaths[p]; ok && targetName != name {
-		log.Fatal("storage path %q is being used by %q and %q and all storage paths must be unique to prevent data loss.", p, targetName, name)
+func checkOverlappedPath(name, path string) {
+	// TODO: some paths shouldn't overlap (storage.xxx.path), while some could (data path is the base path for storage path)
+	if targetName, ok := configuredPaths[path]; ok && targetName != name {
+		msg := fmt.Sprintf("Configured path %q is used by %q and %q at the same time. The paths must be unique to prevent data loss.", path, targetName, name)
+		log.Error("%s", msg)
+		DeprecatedWarnings = append(DeprecatedWarnings, msg)
 	}
-	uniquePaths[p] = name
+	configuredPaths[path] = name
 }
diff --git a/modules/setting/storage.go b/modules/setting/storage.go
index 23b08df101..f4e33a53af 100644
--- a/modules/setting/storage.go
+++ b/modules/setting/storage.go
@@ -240,7 +240,7 @@ func getStorageForLocal(targetSec, overrideSec ConfigSection, tp targetSecType,
 		}
 	}
 
-	fatalDuplicatedPath("storage."+name, storage.Path)
+	checkOverlappedPath("storage."+name+".PATH", storage.Path)
 
 	return &storage, nil
 }
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index b7bcf20d30..39b9855186 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2775,6 +2775,7 @@ teams.invite.by = Invited by %s
 teams.invite.description = Please click the button below to join the team.
 
 [admin]
+maintenance = Maintenance
 dashboard = Dashboard
 self_check = Self Check
 identity_access = Identity & Access
@@ -2798,7 +2799,7 @@ settings = Admin Settings
 
 dashboard.new_version_hint = Gitea %s is now available, you are running %s. Check <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">the blog</a> for more details.
 dashboard.statistic = Summary
-dashboard.operations = Maintenance Operations
+dashboard.maintenance_operations = Maintenance Operations
 dashboard.system_status = System Status
 dashboard.operation_name = Operation Name
 dashboard.operation_switch = Switch
@@ -3305,6 +3306,7 @@ notices.op = Op.
 notices.delete_success = The system notices have been deleted.
 
 self_check.no_problem_found = No problem found yet.
+self_check.startup_warnings = Startup warnings:
 self_check.database_collation_mismatch = Expect database to use collation: %s
 self_check.database_collation_case_insensitive = Database is using a collation %s, which is an insensitive collation. Although Gitea could work with it, there might be some rare cases which don't work as expected.
 self_check.database_inconsistent_collation_columns = Database is using collation %s, but these columns are using mismatched collations. It might cause some unexpected problems.
diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go
index f3f10fd1b8..4dc0dfdef8 100644
--- a/routers/web/admin/admin.go
+++ b/routers/web/admin/admin.go
@@ -190,6 +190,14 @@ func DashboardPost(ctx *context.Context) {
 
 func SelfCheck(ctx *context.Context) {
 	ctx.Data["PageIsAdminSelfCheck"] = true
+
+	ctx.Data["DeprecatedWarnings"] = setting.DeprecatedWarnings
+	if len(setting.DeprecatedWarnings) == 0 && !setting.IsProd {
+		if time.Now().Unix()%2 == 0 {
+			ctx.Data["DeprecatedWarnings"] = []string{"This is a test warning message in dev mode"}
+		}
+	}
+
 	r, err := db.CheckCollationsDefaultEngine()
 	if err != nil {
 		ctx.Flash.Error(fmt.Sprintf("CheckCollationsDefaultEngine: %v", err), true)
diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl
index bfd2ee6670..589fc5048a 100644
--- a/templates/admin/dashboard.tmpl
+++ b/templates/admin/dashboard.tmpl
@@ -6,7 +6,7 @@
 			</div>
 		{{end}}
 		<h4 class="ui top attached header">
-			{{ctx.Locale.Tr "admin.dashboard.operations"}}
+			{{ctx.Locale.Tr "admin.dashboard.maintenance_operations"}}
 		</h4>
 		<div class="ui attached table segment">
 			<form method="post" action="{{AppSubUrl}}/admin">
diff --git a/templates/admin/navbar.tmpl b/templates/admin/navbar.tmpl
index d01a6ab964..1b3b9d6efc 100644
--- a/templates/admin/navbar.tmpl
+++ b/templates/admin/navbar.tmpl
@@ -1,12 +1,18 @@
 <div class="flex-container-nav">
 	<div class="ui fluid vertical menu">
 		<div class="header item">{{ctx.Locale.Tr "admin.settings"}}</div>
-		<a class="{{if .PageIsAdminDashboard}}active {{end}}item" href="{{AppSubUrl}}/admin">
-			{{ctx.Locale.Tr "admin.dashboard"}}
-		</a>
-		<a class="{{if .PageIsAdminSelfCheck}}active {{end}}item" href="{{AppSubUrl}}/admin/self_check">
-			{{ctx.Locale.Tr "admin.self_check"}}
-		</a>
+
+		<details class="item toggleable-item" {{if or .PageIsAdminDashboard .PageIsAdminSelfCheck}}open{{end}}>
+			<summary>{{ctx.Locale.Tr "admin.maintenance"}}</summary>
+			<div class="menu">
+				<a class="{{if .PageIsAdminDashboard}}active {{end}}item" href="{{AppSubUrl}}/admin">
+					{{ctx.Locale.Tr "admin.dashboard"}}
+				</a>
+				<a class="{{if .PageIsAdminSelfCheck}}active {{end}}item" href="{{AppSubUrl}}/admin/self_check">
+					{{ctx.Locale.Tr "admin.self_check"}}
+				</a>
+			</div>
+		</details>
 		<details class="item toggleable-item" {{if or .PageIsAdminUsers .PageIsAdminEmails .PageIsAdminOrganizations .PageIsAdminAuthentications}}open{{end}}>
 			<summary>{{ctx.Locale.Tr "admin.identity_access"}}</summary>
 			<div class="menu">
diff --git a/templates/admin/self_check.tmpl b/templates/admin/self_check.tmpl
index 94c4673a49..c100ffd504 100644
--- a/templates/admin/self_check.tmpl
+++ b/templates/admin/self_check.tmpl
@@ -4,33 +4,47 @@
 	<h4 class="ui top attached header">
 		{{ctx.Locale.Tr "admin.self_check"}}
 	</h4>
+
+	{{if .DeprecatedWarnings}}
 	<div class="ui attached segment">
-		{{if .DatabaseCheckHasProblems}}
-			{{if .DatabaseType.IsMySQL}}
-				<div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mysql"}}</div>
-			{{else if .DatabaseType.IsMSSQL}}
-				<div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mssql"}}</div>
-			{{end}}
-			{{if .DatabaseCheckCollationMismatch}}
-				<div class="ui red message">{{ctx.Locale.Tr "admin.self_check.database_collation_mismatch" .DatabaseCheckResult.ExpectedCollation}}</div>
-			{{end}}
-			{{if .DatabaseCheckCollationCaseInsensitive}}
-				<div class="ui warning message">{{ctx.Locale.Tr "admin.self_check.database_collation_case_insensitive" .DatabaseCheckResult.DatabaseCollation}}</div>
-			{{end}}
-			{{if .DatabaseCheckInconsistentCollationColumns}}
-				<div class="ui red message">
-					{{ctx.Locale.Tr "admin.self_check.database_inconsistent_collation_columns" .DatabaseCheckResult.DatabaseCollation}}
-					<ul class="tw-w-full">
-					{{range .DatabaseCheckInconsistentCollationColumns}}
-						<li>{{.}}</li>
-					{{end}}
-					</ul>
-				</div>
-			{{end}}
-		{{else}}
-			<div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.no_problem_found"}}</div>
+		<div class="ui warning message">
+			<div>{{ctx.Locale.Tr "admin.self_check.startup_warnings"}}</div>
+			<ul class="tw-w-full">{{range .DeprecatedWarnings}}<li>{{.}}</li>{{end}}</ul>
+		</div>
+	</div>
+	{{end}}
+
+	{{if .DatabaseCheckHasProblems}}
+	<div class="ui attached segment">
+		{{if .DatabaseType.IsMySQL}}
+			<div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mysql"}}</div>
+		{{else if .DatabaseType.IsMSSQL}}
+			<div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mssql"}}</div>
+		{{end}}
+		{{if .DatabaseCheckCollationMismatch}}
+			<div class="ui red message">{{ctx.Locale.Tr "admin.self_check.database_collation_mismatch" .DatabaseCheckResult.ExpectedCollation}}</div>
 		{{end}}
+		{{if .DatabaseCheckCollationCaseInsensitive}}
+			<div class="ui warning message">{{ctx.Locale.Tr "admin.self_check.database_collation_case_insensitive" .DatabaseCheckResult.DatabaseCollation}}</div>
+		{{end}}
+		{{if .DatabaseCheckInconsistentCollationColumns}}
+			<div class="ui red message">
+				{{ctx.Locale.Tr "admin.self_check.database_inconsistent_collation_columns" .DatabaseCheckResult.DatabaseCollation}}
+				<ul class="tw-w-full">
+				{{range .DatabaseCheckInconsistentCollationColumns}}
+					<li>{{.}}</li>
+				{{end}}
+				</ul>
+			</div>
+		{{end}}
+	</div>
+	{{end}}
+
+	{{if and (not .DeprecatedWarnings) (not .DatabaseCheckHasProblems)}}
+	<div class="ui attached segment">
+		{{ctx.Locale.Tr "admin.self_check.no_problem_found"}}
 	</div>
+	{{end}}
 </div>
 
 {{template "admin/layout_footer" .}}