diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index e5f72d436c..94c15a60d9 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -827,6 +827,15 @@ LEVEL = Info
 ;; Dependencies can be added from any repository where the user is granted access or only from the current repository depending on this setting.
 ;ALLOW_CROSS_REPOSITORY_DEPENDENCIES = true
 ;;
+;; Default map service. No external API support has been included. A service has to allow
+;; searching using URL parameters, the location will be appended to the URL as escaped query parameter.
+;; Disabled by default, some example values are:
+;; - OpenStreetMap: https://www.openstreetmap.org/search?query=
+;; - Google Maps: https://www.google.com/maps/place/
+;; - MapQuest: https://www.mapquest.com/search/
+;; - Bing Maps: https://www.bing.com/maps?where1=
+; USER_LOCATION_MAP_URL =
+;;
 ;; Enable heatmap on users profiles.
 ;ENABLE_USER_HEATMAP = true
 ;;
diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md
index f30e0e246a..30751bf071 100644
--- a/docs/content/administration/config-cheat-sheet.en-us.md
+++ b/docs/content/administration/config-cheat-sheet.en-us.md
@@ -648,6 +648,7 @@ And the following unique queues:
 - `DEFAULT_USER_IS_RESTRICTED`: **false**: Give new users restricted permissions by default
 - `DEFAULT_ENABLE_DEPENDENCIES`: **true**: Enable this to have dependencies enabled by default.
 - `ALLOW_CROSS_REPOSITORY_DEPENDENCIES` : **true** Enable this to allow dependencies on issues from any repository where the user is granted access.
+- `USER_LOCATION_MAP_URL`: **""**: A map service URL to show user's location on a map. The location will be appended to the URL as escaped query parameter.
 - `ENABLE_USER_HEATMAP`: **true**: Enable this to display the heatmap on users profiles.
 - `ENABLE_TIMETRACKING`: **true**: Enable Timetracking feature.
 - `DEFAULT_ENABLE_TIMETRACKING`: **true**: Allow repositories to use timetracking by default.
diff --git a/modules/setting/service.go b/modules/setting/service.go
index 597fecee1d..595ea6528f 100644
--- a/modules/setting/service.go
+++ b/modules/setting/service.go
@@ -73,6 +73,7 @@ var Service = struct {
 	AllowCrossRepositoryDependencies        bool
 	DefaultAllowOnlyContributorsToTrackTime bool
 	NoReplyAddress                          string
+	UserLocationMapURL                      string
 	EnableUserHeatmap                       bool
 	AutoWatchNewRepos                       bool
 	AutoWatchOnChanges                      bool
@@ -185,6 +186,7 @@ func loadServiceFrom(rootCfg ConfigProvider) {
 	Service.AllowCrossRepositoryDependencies = sec.Key("ALLOW_CROSS_REPOSITORY_DEPENDENCIES").MustBool(true)
 	Service.DefaultAllowOnlyContributorsToTrackTime = sec.Key("DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME").MustBool(true)
 	Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply." + Domain)
+	Service.UserLocationMapURL = sec.Key("USER_LOCATION_MAP_URL").String()
 	Service.EnableUserHeatmap = sec.Key("ENABLE_USER_HEATMAP").MustBool(true)
 	Service.AutoWatchNewRepos = sec.Key("AUTO_WATCH_NEW_REPOS").MustBool(true)
 	Service.AutoWatchOnChanges = sec.Key("AUTO_WATCH_ON_CHANGES").MustBool(false)
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index cf04830cbb..3256d2ba91 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -601,6 +601,7 @@ user_bio = Biography
 disabled_public_activity = This user has disabled the public visibility of the activity.
 email_visibility.limited = Your email address is visible to all authenticated users
 email_visibility.private = Your email address is only visible to you and administrators
+show_on_map = Show this place on a map
 
 form.name_reserved = The username "%s" is reserved.
 form.name_pattern_not_allowed = The pattern "%s" is not allowed in a username.
@@ -627,6 +628,7 @@ webauthn = Security Keys
 
 public_profile = Public Profile
 biography_placeholder = Tell us a little bit about yourself
+location_placeholder = Share your approximate location with others
 profile_desc = Your email address will be used for notifications and other operations.
 password_username_disabled = Non-local users are not allowed to change their username. Please contact your site administrator for more details.
 full_name = Full Name
diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go
index 15a9197b98..7f6e41d30f 100644
--- a/routers/web/user/profile.go
+++ b/routers/web/user/profile.go
@@ -52,6 +52,7 @@ func userProfile(ctx *context.Context) {
 
 	ctx.Data["Title"] = ctx.ContextUser.DisplayName()
 	ctx.Data["PageIsUserProfile"] = true
+	ctx.Data["UserLocationMapURL"] = setting.Service.UserLocationMapURL
 
 	// prepare heatmap data
 	if setting.Service.EnableUserHeatmap {
diff --git a/templates/shared/user/profile_big_avatar.tmpl b/templates/shared/user/profile_big_avatar.tmpl
index 62b317cdd4..97afbb6580 100644
--- a/templates/shared/user/profile_big_avatar.tmpl
+++ b/templates/shared/user/profile_big_avatar.tmpl
@@ -24,19 +24,28 @@
 	<div class="extra content gt-word-break">
 		<ul>
 			{{if .ContextUser.Location}}
-				<li>{{svg "octicon-location"}} {{.ContextUser.Location}}</li>
+				<li>
+					{{svg "octicon-location"}}
+					<span class="gt-f1">{{.ContextUser.Location}}</span>
+					{{if .UserLocationMapURL}}
+						{{/* We presume that the UserLocationMapURL is safe, as it is provided by the site administrator. */}}
+						<a href="{{.UserLocationMapURL | Safe}}{{.ContextUser.Location | QueryEscape}}" rel="nofollow noreferrer" data-tooltip-content="{{.locale.Tr "user.show_on_map"}}">
+							{{svg "octicon-link-external"}}
+						</a>
+					{{end}}
+				</li>
 			{{end}}
 			{{if (eq .SignedUserID .ContextUser.ID)}}
 				<li>
 					{{svg "octicon-mail"}}
-					<a href="mailto:{{.ContextUser.Email}}" rel="nofollow">{{.ContextUser.Email}}</a>
+					<a class="gt-f1" href="mailto:{{.ContextUser.Email}}" rel="nofollow">{{.ContextUser.Email}}</a>
 					<a href="{{AppSubUrl}}/user/settings#keep-email-private">
 						{{if .ShowUserEmail}}
-							<i class="ui right" data-tooltip-content="{{.locale.Tr "user.email_visibility.limited"}}">
+							<i data-tooltip-content="{{.locale.Tr "user.email_visibility.limited"}}">
 								{{svg "octicon-unlock"}}
 							</i>
 						{{else}}
-							<i class="ui right" data-tooltip-content="{{.locale.Tr "user.email_visibility.private"}}">
+							<i data-tooltip-content="{{.locale.Tr "user.email_visibility.private"}}">
 								{{svg "octicon-lock"}}
 							</i>
 						{{end}}
@@ -69,7 +78,7 @@
 					</li>
 				{{end}}
 			{{end}}
-			<li>{{svg "octicon-calendar"}} {{.locale.Tr "user.joined_on" (DateTime "short" .ContextUser.CreatedUnix) | Safe}}</li>
+			<li>{{svg "octicon-calendar"}} <span>{{.locale.Tr "user.joined_on" (DateTime "short" .ContextUser.CreatedUnix) | Safe}}</span></li>
 			{{if and .Orgs .HasOrgsVisible}}
 			<li>
 				<ul class="user-orgs">
diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl
index 9f1f743f9c..e0240e391f 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -35,7 +35,7 @@
 				</div>
 				<div class="field">
 					<label for="location">{{.locale.Tr "settings.location"}}</label>
-					<input id="location" name="location"  value="{{.SignedUser.Location}}" maxlength="50">
+					<input id="location" name="location" placeholder="{{.locale.Tr "settings.location_placeholder"}}" value="{{.SignedUser.Location}}" maxlength="50">
 				</div>
 
 				<div class="divider"></div>
diff --git a/tests/integration/user_test.go b/tests/integration/user_test.go
index 3e4d967686..ddde415960 100644
--- a/tests/integration/user_test.go
+++ b/tests/integration/user_test.go
@@ -12,6 +12,7 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/test"
 	"code.gitea.io/gitea/modules/translation"
@@ -276,3 +277,23 @@ func TestListStopWatches(t *testing.T) {
 		assert.Greater(t, apiWatches[0].Seconds, int64(0))
 	}
 }
+
+func TestUserLocationMapLink(t *testing.T) {
+	setting.Service.UserLocationMapURL = "https://example/foo/"
+	defer tests.PrepareTestEnv(t)()
+
+	session := loginUser(t, "user2")
+	req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
+		"_csrf":    GetCSRF(t, session, "/user/settings"),
+		"name":     "user2",
+		"email":    "user@example.com",
+		"language": "en-US",
+		"location": "A/b",
+	})
+	session.MakeRequest(t, req, http.StatusSeeOther)
+
+	req = NewRequest(t, "GET", "/user2/")
+	resp := session.MakeRequest(t, req, http.StatusOK)
+	htmlDoc := NewHTMLParser(t, resp.Body)
+	htmlDoc.AssertElement(t, `a[href="https://example/foo/A%2Fb"]`, true)
+}
diff --git a/web_src/css/user.css b/web_src/css/user.css
index 9fcdb3814a..ab94c826b2 100644
--- a/web_src/css/user.css
+++ b/web_src/css/user.css
@@ -22,18 +22,16 @@
 
 .user.profile .ui.card .extra.content > ul > li {
   padding: 10px;
+  display: flex;
   list-style: none;
+  align-items: center;
+  gap: 0.25em;
 }
 
 .user.profile .ui.card .extra.content > ul > li:not(:last-child) {
   border-bottom: 1px solid var(--color-secondary);
 }
 
-.user.profile .ui.card .extra.content > ul > li .svg {
-  margin-left: 1px;
-  margin-right: 5px;
-}
-
 .user.profile .ui.card .extra.content > ul > li.follow .ui.button {
   width: 100%;
 }