diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index f0a8b05d53..3524077ea4 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -532,6 +532,8 @@ var migrations = []Migration{
 	NewMigration("Add Actions artifacts expiration date", v1_21.AddExpiredUnixColumnInActionArtifactTable),
 	// v275 -> v276
 	NewMigration("Add ScheduleID for ActionRun", v1_21.AddScheduleIDForActionRun),
+	// v276 -> v277
+	NewMigration("Add RemoteAddress to mirrors", v1_21.AddRemoteAddressToMirrors),
 }
 
 // GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v1_21/v276.go b/models/migrations/v1_21/v276.go
new file mode 100644
index 0000000000..8933f186dc
--- /dev/null
+++ b/models/migrations/v1_21/v276.go
@@ -0,0 +1,132 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_21 //nolint
+
+import (
+	"context"
+	"fmt"
+	"path/filepath"
+	"strings"
+
+	"code.gitea.io/gitea/modules/git"
+	giturl "code.gitea.io/gitea/modules/git/url"
+	"code.gitea.io/gitea/modules/setting"
+
+	"xorm.io/xorm"
+)
+
+func AddRemoteAddressToMirrors(x *xorm.Engine) error {
+	type Mirror struct {
+		RemoteAddress string `xorm:"VARCHAR(2048)"`
+	}
+
+	type PushMirror struct {
+		RemoteAddress string `xorm:"VARCHAR(2048)"`
+	}
+
+	if err := x.Sync(new(Mirror), new(PushMirror)); err != nil {
+		return err
+	}
+
+	if err := migratePullMirrors(x); err != nil {
+		return err
+	}
+
+	return migratePushMirrors(x)
+}
+
+func migratePullMirrors(x *xorm.Engine) error {
+	type Mirror struct {
+		ID            int64  `xorm:"pk autoincr"`
+		RepoID        int64  `xorm:"INDEX"`
+		RemoteAddress string `xorm:"VARCHAR(2048)"`
+	}
+
+	sess := x.NewSession()
+	defer sess.Close()
+
+	if err := sess.Begin(); err != nil {
+		return err
+	}
+
+	if err := sess.Iterate(new(Mirror), func(_ int, bean any) error {
+		m := bean.(*Mirror)
+		remoteAddress, err := getRemoteAddress(sess, m.RepoID, "origin")
+		if err != nil {
+			return err
+		}
+
+		m.RemoteAddress = remoteAddress
+
+		_, err = sess.ID(m.ID).Cols("remote_address").Update(m)
+		return err
+	}); err != nil {
+		return err
+	}
+
+	return sess.Commit()
+}
+
+func migratePushMirrors(x *xorm.Engine) error {
+	type PushMirror struct {
+		ID            int64 `xorm:"pk autoincr"`
+		RepoID        int64 `xorm:"INDEX"`
+		RemoteName    string
+		RemoteAddress string `xorm:"VARCHAR(2048)"`
+	}
+
+	sess := x.NewSession()
+	defer sess.Close()
+
+	if err := sess.Begin(); err != nil {
+		return err
+	}
+
+	if err := sess.Iterate(new(PushMirror), func(_ int, bean any) error {
+		m := bean.(*PushMirror)
+		remoteAddress, err := getRemoteAddress(sess, m.RepoID, m.RemoteName)
+		if err != nil {
+			return err
+		}
+
+		m.RemoteAddress = remoteAddress
+
+		_, err = sess.ID(m.ID).Cols("remote_address").Update(m)
+		return err
+	}); err != nil {
+		return err
+	}
+
+	return sess.Commit()
+}
+
+func getRemoteAddress(sess *xorm.Session, repoID int64, remoteName string) (string, error) {
+	var ownerName string
+	var repoName string
+	has, err := sess.
+		Table("repository").
+		Cols("owner_name", "lower_name").
+		Where("id=?", repoID).
+		Get(&ownerName, &repoName)
+	if err != nil {
+		return "", err
+	} else if !has {
+		return "", fmt.Errorf("repository [%v] not found", repoID)
+	}
+
+	repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(ownerName), strings.ToLower(repoName)+".git")
+
+	remoteURL, err := git.GetRemoteAddress(context.Background(), repoPath, remoteName)
+	if err != nil {
+		return "", err
+	}
+
+	u, err := giturl.Parse(remoteURL)
+	if err != nil {
+		return "", err
+	}
+	u.User = nil
+
+	return u.String(), nil
+}
diff --git a/models/repo/mirror.go b/models/repo/mirror.go
index 39482037b2..fffc7577c7 100644
--- a/models/repo/mirror.go
+++ b/models/repo/mirror.go
@@ -31,7 +31,7 @@ type Mirror struct {
 	LFS         bool   `xorm:"lfs_enabled NOT NULL DEFAULT false"`
 	LFSEndpoint string `xorm:"lfs_endpoint TEXT"`
 
-	Address string `xorm:"-"`
+	RemoteAddress string `xorm:"VARCHAR(2048)"`
 }
 
 func init() {
diff --git a/models/repo/pushmirror.go b/models/repo/pushmirror.go
index 3edbcceb9b..73c1384444 100644
--- a/models/repo/pushmirror.go
+++ b/models/repo/pushmirror.go
@@ -20,10 +20,11 @@ var ErrPushMirrorNotExist = util.NewNotExistErrorf("PushMirror does not exist")
 
 // PushMirror represents mirror information of a repository.
 type PushMirror struct {
-	ID         int64       `xorm:"pk autoincr"`
-	RepoID     int64       `xorm:"INDEX"`
-	Repo       *Repository `xorm:"-"`
-	RemoteName string
+	ID            int64       `xorm:"pk autoincr"`
+	RepoID        int64       `xorm:"INDEX"`
+	Repo          *Repository `xorm:"-"`
+	RemoteName    string
+	RemoteAddress string `xorm:"VARCHAR(2048)"`
 
 	SyncOnCommit   bool `xorm:"NOT NULL DEFAULT true"`
 	Interval       time.Duration
@@ -31,6 +32,7 @@ type PushMirror struct {
 	LastUpdateUnix timeutil.TimeStamp `xorm:"INDEX last_update"`
 	LastError      string             `xorm:"text"`
 }
+
 type PushMirrorOptions struct {
 	ID         int64
 	RepoID     int64
diff --git a/models/repo/repo.go b/models/repo/repo.go
index b37948fea7..5ebc7bfc24 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -191,12 +191,8 @@ func (repo *Repository) SanitizedOriginalURL() string {
 	if repo.OriginalURL == "" {
 		return ""
 	}
-	u, err := url.Parse(repo.OriginalURL)
-	if err != nil {
-		return ""
-	}
-	u.User = nil
-	return u.String()
+	u, _ := util.SanitizeURL(repo.OriginalURL)
+	return u
 }
 
 // text representations to be returned in SizeDetail.Name
diff --git a/modules/repository/repo.go b/modules/repository/repo.go
index 6bf88e7752..b50c8f75ca 100644
--- a/modules/repository/repo.go
+++ b/modules/repository/repo.go
@@ -180,12 +180,17 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
 	defer committer.Close()
 
 	if opts.Mirror {
+		remoteAddress, err := util.SanitizeURL(opts.CloneAddr)
+		if err != nil {
+			return repo, err
+		}
 		mirrorModel := repo_model.Mirror{
 			RepoID:         repo.ID,
 			Interval:       setting.Mirror.DefaultInterval,
 			EnablePrune:    true,
 			NextUpdateUnix: timeutil.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval),
 			LFS:            opts.LFS,
+			RemoteAddress:  remoteAddress,
 		}
 		if opts.LFS {
 			mirrorModel.LFSEndpoint = opts.LFSEndpoint
diff --git a/modules/util/url.go b/modules/util/url.go
index 75fcf634a9..62370339c8 100644
--- a/modules/util/url.go
+++ b/modules/util/url.go
@@ -39,3 +39,12 @@ func URLJoin(base string, elems ...string) string {
 	}
 	return joinedURL
 }
+
+func SanitizeURL(s string) (string, error) {
+	u, err := url.Parse(s)
+	if err != nil {
+		return "", err
+	}
+	u.User = nil
+	return u.String(), nil
+}
diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go
index 12542c808f..15c7046b49 100644
--- a/routers/api/v1/repo/mirror.go
+++ b/routers/api/v1/repo/mirror.go
@@ -353,12 +353,19 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro
 		return
 	}
 
+	remoteAddress, err := util.SanitizeURL(mirrorOption.RemoteAddress)
+	if err != nil {
+		ctx.ServerError("SanitizeURL", err)
+		return
+	}
+
 	pushMirror := &repo_model.PushMirror{
-		RepoID:       repo.ID,
-		Repo:         repo,
-		RemoteName:   fmt.Sprintf("remote_mirror_%s", remoteSuffix),
-		Interval:     interval,
-		SyncOnCommit: mirrorOption.SyncOnCommit,
+		RepoID:        repo.ID,
+		Repo:          repo,
+		RemoteName:    fmt.Sprintf("remote_mirror_%s", remoteSuffix),
+		Interval:      interval,
+		SyncOnCommit:  mirrorOption.SyncOnCommit,
+		RemoteAddress: remoteAddress,
 	}
 
 	if err = repo_model.InsertPushMirror(ctx, pushMirror); err != nil {
diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go
index 7c85ff2078..68943586ef 100644
--- a/routers/web/repo/setting/setting.go
+++ b/routers/web/repo/setting/setting.go
@@ -243,6 +243,13 @@ func SettingsPost(ctx *context.Context) {
 			return
 		}
 
+		remoteAddress, err := util.SanitizeURL(form.MirrorAddress)
+		if err != nil {
+			ctx.ServerError("SanitizeURL", err)
+			return
+		}
+		pullMirror.RemoteAddress = remoteAddress
+
 		form.LFS = form.LFS && setting.LFS.StartServer
 
 		if len(form.LFSEndpoint) > 0 {
@@ -397,12 +404,19 @@ func SettingsPost(ctx *context.Context) {
 			return
 		}
 
+		remoteAddress, err := util.SanitizeURL(form.PushMirrorAddress)
+		if err != nil {
+			ctx.ServerError("SanitizeURL", err)
+			return
+		}
+
 		m := &repo_model.PushMirror{
-			RepoID:       repo.ID,
-			Repo:         repo,
-			RemoteName:   fmt.Sprintf("remote_mirror_%s", remoteSuffix),
-			SyncOnCommit: form.PushMirrorSyncOnCommit,
-			Interval:     interval,
+			RepoID:        repo.ID,
+			Repo:          repo,
+			RemoteName:    fmt.Sprintf("remote_mirror_%s", remoteSuffix),
+			SyncOnCommit:  form.PushMirrorSyncOnCommit,
+			Interval:      interval,
+			RemoteAddress: remoteAddress,
 		}
 		if err := repo_model.InsertPushMirror(ctx, m); err != nil {
 			ctx.ServerError("InsertPushMirror", err)
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index 37da76e3e5..91c00b049e 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -628,15 +628,6 @@ func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input i
 	return escaped, output, err
 }
 
-func safeURL(address string) string {
-	u, err := url.Parse(address)
-	if err != nil {
-		return address
-	}
-	u.User = nil
-	return u.String()
-}
-
 func checkHomeCodeViewable(ctx *context.Context) {
 	if len(ctx.Repo.Units) > 0 {
 		if ctx.Repo.Repository.IsBeingCreated() {
@@ -660,7 +651,7 @@ func checkHomeCodeViewable(ctx *context.Context) {
 
 			ctx.Data["Repo"] = ctx.Repo
 			ctx.Data["MigrateTask"] = task
-			ctx.Data["CloneAddr"] = safeURL(cfg.CloneAddr)
+			ctx.Data["CloneAddr"], _ = util.SanitizeURL(cfg.CloneAddr)
 			ctx.Data["Failed"] = task.Status == structs.TaskStatusFailed
 			ctx.HTML(http.StatusOK, tplMigrating)
 			return
diff --git a/services/convert/mirror.go b/services/convert/mirror.go
index f7a8e17fd0..5051104526 100644
--- a/services/convert/mirror.go
+++ b/services/convert/mirror.go
@@ -5,21 +5,16 @@ package convert
 
 import (
 	repo_model "code.gitea.io/gitea/models/repo"
-	"code.gitea.io/gitea/modules/git"
 	api "code.gitea.io/gitea/modules/structs"
 )
 
 // ToPushMirror convert from repo_model.PushMirror and remoteAddress to api.TopicResponse
 func ToPushMirror(pm *repo_model.PushMirror) (*api.PushMirror, error) {
 	repo := pm.GetRepository()
-	remoteAddress, err := getRemoteAddress(repo, pm.RemoteName)
-	if err != nil {
-		return nil, err
-	}
 	return &api.PushMirror{
 		RepoName:       repo.Name,
 		RemoteName:     pm.RemoteName,
-		RemoteAddress:  remoteAddress,
+		RemoteAddress:  pm.RemoteAddress,
 		CreatedUnix:    pm.CreatedUnix.FormatLong(),
 		LastUpdateUnix: pm.LastUpdateUnix.FormatLong(),
 		LastError:      pm.LastError,
@@ -27,13 +22,3 @@ func ToPushMirror(pm *repo_model.PushMirror) (*api.PushMirror, error) {
 		SyncOnCommit:   pm.SyncOnCommit,
 	}, nil
 }
-
-func getRemoteAddress(repo *repo_model.Repository, remoteName string) (string, error) {
-	url, err := git.GetRemoteURL(git.DefaultContext, repo.RepoPath(), remoteName)
-	if err != nil {
-		return "", err
-	}
-	// remove confidential information
-	url.User = nil
-	return url.String(), nil
-}
diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl
index 984e9f044e..935060816c 100644
--- a/templates/repo/header.tmpl
+++ b/templates/repo/header.tmpl
@@ -37,8 +37,7 @@
 					{{end}}
 				</div>
 				{{if $.PullMirror}}
-					{{$address := MirrorRemoteAddress $.Context . $.PullMirror.GetRemoteName false}}
-					<div class="fork-flag">{{$.locale.Tr "repo.mirror_from"}} <a target="_blank" rel="noopener noreferrer" href="{{$address.Address}}">{{$address.Address}}</a></div>
+					<div class="fork-flag">{{$.locale.Tr "repo.mirror_from"}} <a target="_blank" rel="noopener noreferrer" href="{{$.PullMirror.RemoteAddress}}">{{$.PullMirror.RemoteAddress}}</a></div>
 				{{end}}
 				{{if .IsFork}}<div class="fork-flag">{{$.locale.Tr "repo.forked_from"}} <a href="{{.BaseRepo.Link}}">{{.BaseRepo.FullName}}</a></div>{{end}}
 				{{if .IsGenerated}}<div class="fork-flag">{{$.locale.Tr "repo.generated_from"}} <a href="{{.TemplateRepo.Link}}">{{.TemplateRepo.FullName}}</a></div>{{end}}
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl
index 16944506f1..78700e05b7 100644
--- a/templates/repo/settings/options.tmpl
+++ b/templates/repo/settings/options.tmpl
@@ -123,7 +123,7 @@
 					{{else if $isWorkingPullMirror}}
 					<tbody>
 						<tr>
-							<td>{{(MirrorRemoteAddress $.Context .Repository .PullMirror.GetRemoteName false).Address}}</td>
+							<td>{{.PullMirror.RemoteAddress}}</td>
 							<td>{{$.locale.Tr "repo.settings.mirror_settings.direction.pull"}}</td>
 							<td>{{DateTime "full" .PullMirror.UpdatedUnix}}</td>
 							<td class="right aligned">
@@ -200,8 +200,7 @@
 					<tbody>
 						{{range .PushMirrors}}
 						<tr>
-							{{$address := MirrorRemoteAddress $.Context $.Repository .GetRemoteName true}}
-							<td class="gt-word-break">{{$address.Address}}</td>
+							<td class="gt-word-break">{{.RemoteAddress}}</td>
 							<td>{{$.locale.Tr "repo.settings.mirror_settings.direction.push"}}</td>
 							<td>{{if .LastUpdateUnix}}{{DateTime "full" .LastUpdateUnix}}{{else}}{{$.locale.Tr "never"}}{{end}} {{if .LastError}}<div class="ui red label" data-tooltip-content="{{.LastError}}">{{$.locale.Tr "error"}}</div>{{end}}</td>
 							<td class="right aligned">
@@ -211,7 +210,7 @@
 									data-tooltip-content="{{$.locale.Tr "repo.settings.mirror_settings.push_mirror.edit_sync_time"}}"
 									data-modal-push-mirror-edit-id="{{.ID}}"
 									data-modal-push-mirror-edit-interval="{{.Interval}}"
-									data-modal-push-mirror-edit-address="{{$address.Address}}"
+									data-modal-push-mirror-edit-address="{{.RemoteAddress}}"
 								>
 									{{svg "octicon-pencil" 14}}
 								</button>