diff --git a/models/repo/release.go b/models/repo/release.go
index 1c2e4a48e3..7cf8760f31 100644
--- a/models/repo/release.go
+++ b/models/repo/release.go
@@ -298,6 +298,26 @@ func GetTagNamesByRepoID(ctx context.Context, repoID int64) ([]string, error) {
 	return tags, sess.Find(&tags)
 }
 
+// GetTagMappingsByRepoID returns a mapping from tag name to commit SHA by repo id
+func GetTagMappingsByRepoID(ctx context.Context, repoID int64) (map[string]string, error) {
+	mapping := make(map[string]string)
+	rels := make([]*Release, 0)
+	if err := db.GetEngine(ctx).
+		Desc("created_unix").
+		Find(&rels, Release{RepoID: repoID}); err != nil {
+		return mapping, err
+	}
+	for _, r := range rels {
+		mapping[r.TagName] = r.Sha1
+	}
+	return mapping, nil
+}
+
+// CountReleasesByRepoID returns a number of releases matching FindReleaseOptions and RepoID.
+func CountReleasesByRepoID(ctx context.Context, repoID int64, opts FindReleasesOptions) (int64, error) {
+	return db.GetEngine(ctx).Where(opts.ToConds()).Count(new(Release))
+}
+
 // GetLatestReleaseByRepoID returns the latest release for a repository
 func GetLatestReleaseByRepoID(ctx context.Context, repoID int64) (*Release, error) {
 	cond := builder.NewCond().
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 41f6c5055d..71c974085c 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2679,6 +2679,12 @@ release.add_tag_msg = Use the title and content of release as tag message.
 release.add_tag = Create Tag Only
 release.releases_for = Releases for %s
 release.tags_for = Tags for %s
+release.existing_tag_header = New release with an old tag
+release.existing_tag_draft_header = Draft release with an old tag
+release.existing_tag = You are about to create a new release with an <b>existing tag</b>. Make sure that the tag is pointing to the right commit.
+release.existing_draft_tag = You are about to create a draft release with an <b>existing tag</b>. Make sure that the tag is pointing to the right commit.
+release.create_confirmation = Do you want to continue with the release creation?
+release.create_draft_confirmation = Do you want to continue with the release draft creation?
 
 branch.name = Branch Name
 branch.already_exists = A branch named "%s" already exists.
diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go
index 284fd27abf..e84af21798 100644
--- a/routers/web/repo/release.go
+++ b/routers/web/repo/release.go
@@ -330,9 +330,9 @@ func newReleaseCommon(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("repo.release.new_release")
 	ctx.Data["PageIsReleaseList"] = true
 
-	tags, err := repo_model.GetTagNamesByRepoID(ctx, ctx.Repo.Repository.ID)
+	tags, err := repo_model.GetTagMappingsByRepoID(ctx, ctx.Repo.Repository.ID)
 	if err != nil {
-		ctx.ServerError("GetTagNamesByRepoID", err)
+		ctx.ServerError("GetTagMappingsByRepoID", err)
 		return
 	}
 	ctx.Data["Tags"] = tags
diff --git a/templates/repo/release/new.tmpl b/templates/repo/release/new.tmpl
index 8b6aa252af..6766e2a3a0 100644
--- a/templates/repo/release/new.tmpl
+++ b/templates/repo/release/new.tmpl
@@ -38,6 +38,9 @@
 								</div>
 							</div>
 						</div>
+						<div id="tag-warning" class="gt-dib gt-hidden" data-commit-url-stub="{{.RepoLink}}/commit">
+							{{svg "octicon-alert-fill" 16}} Existing tag referencing <span class="gt-px-3">{{svg "octicon-git-commit" 16}} <a target="_blank" rel="noopener noreferrer" class="tag-warning-detail"></a></span>
+						</div>
 						<div>
 							<span id="tag-helper" class="help tw-mt-2 tw-pb-0">{{ctx.Locale.Tr "repo.release.tag_helper"}}</span>
 						</div>
@@ -118,8 +121,8 @@
 							{{if .ShowCreateTagOnlyButton}}
 								<button class="ui small button" name="tag_only" value="1">{{ctx.Locale.Tr "repo.release.add_tag"}}</button>
 							{{end}}
-							<button class="ui small button" name="draft" value="1">{{ctx.Locale.Tr "repo.release.save_draft"}}</button>
-							<button class="ui small primary button">{{ctx.Locale.Tr "repo.release.publish"}}</button>
+							<button class="ui small button tag-confirm tag-draft" name="draft" value="1">{{ctx.Locale.Tr "repo.release.save_draft"}}</button>
+							<button class="ui small primary button tag-confirm">{{ctx.Locale.Tr "repo.release.publish"}}</button>
 						{{end}}
 					</div>
 				</div>
@@ -128,6 +131,29 @@
 	</div>
 </div>
 
+<div id="tag-confirm-modal" class="ui g-modal-confirm modal">
+	<div class="header">
+		{{ctx.Locale.Tr "repo.release.existing_tag_header"}}
+	</div>
+	<div class="content">
+		<p>{{svg "octicon-alert-fill" 16}} Existing tag referencing <span class="gt-px-3">{{svg "octicon-git-commit" 16}} <a target="_blank" rel="noopener noreferrer" class="tag-warning-detail"></a></span></p>
+		<p>{{ctx.Locale.Tr "repo.release.existing_tag"}}</p>
+		<p>{{ctx.Locale.Tr "repo.release.create_confirmation"}}</p>
+	</div>
+	{{template "base/modal_actions_confirm" .}}
+</div>
+<div id="tag-confirm-draft-modal" class="ui g-modal-confirm modal">
+	<div class="header">
+		{{ctx.Locale.Tr "repo.release.existing_tag_draft_header"}}
+	</div>
+	<div class="content">
+		<p>{{svg "octicon-alert-fill" 16}} Existing tag referencing <span class="gt-px-3">{{svg "octicon-git-commit" 16}} <a target="_blank" rel="noopener noreferrer" class="tag-warning-detail"></a></span></p>
+		<p>{{ctx.Locale.Tr "repo.release.existing_draft_tag"}}</p>
+		<p>{{ctx.Locale.Tr "repo.release.create_draft_confirmation"}}</p>
+	</div>
+	{{template "base/modal_actions_confirm" .}}
+</div>
+
 {{if .PageIsEditRelease}}
 	<div class="ui g-modal-confirm delete modal">
 		<div class="header">
diff --git a/web_src/js/features/repo-release.ts b/web_src/js/features/repo-release.ts
index dfff090ba9..f8a35e8f50 100644
--- a/web_src/js/features/repo-release.ts
+++ b/web_src/js/features/repo-release.ts
@@ -1,4 +1,5 @@
 import {hideElem, showElem, type DOMEvent} from '../utils/dom.ts';
+import {fomanticQuery} from "../modules/fomantic/base";
 
 export function initRepoRelease() {
   document.addEventListener('click', (e: DOMEvent<MouseEvent>) => {
@@ -21,24 +22,65 @@ function initTagNameEditor() {
   const el = document.querySelector('#tag-name-editor');
   if (!el) return;
 
+  const tagWarning = document.querySelector('#tag-warning');
+  const tagWarningDetailLinks = Array.from(document.getElementsByClassName('tag-warning-detail'));
   const existingTags = JSON.parse(el.getAttribute('data-existing-tags'));
   if (!Array.isArray(existingTags)) return;
 
   const defaultTagHelperText = el.getAttribute('data-tag-helper');
   const newTagHelperText = el.getAttribute('data-tag-helper-new');
   const existingTagHelperText = el.getAttribute('data-tag-helper-existing');
+  const tagURLStub = tagWarning.getAttribute('data-commit-url-stub');
+  const tagConfirmDraftModal = document.querySelector('#tag-confirm-draft-modal');
+  const tagConfirmModal = document.querySelector('#tag-confirm-modal');
+
+  // show the confirmation modal if release is using an existing tag
+  let requiresConfirmation = false;
+  $('.tag-confirm').on('click', (event) => {
+    if (requiresConfirmation) {
+      event.preventDefault();
+      if ($(event.target).hasClass('tag-draft')) {
+        fomanticQuery(tagConfirmDraftModal).modal({
+          onApprove() {
+            // need to add hidden input with draft form value
+            // (triggering form submission doesn't include the button data)
+            $('<input>').attr({
+              type: 'hidden',
+              name: 'draft',
+              value: '1'
+            }).appendTo(event.target.form);
+            $(event.target.form).trigger('submit');
+          },
+        }).modal('show');
+      } else {
+        fomanticQuery(tagConfirmModal).modal({
+          onApprove() {
+            $(event.target.form).trigger('submit');
+          },
+        }).modal('show');
+      }
+    }
+  });
 
   const tagNameInput = document.querySelector<HTMLInputElement>('#tag-name');
   const hideTargetInput = function(tagNameInput: HTMLInputElement) {
     const value = tagNameInput.value;
     const tagHelper = document.querySelector('#tag-helper');
-    if (existingTags.includes(value)) {
+    if (value in existingTags) {
       // If the tag already exists, hide the target branch selector.
       hideElem('#tag-target-selector');
       tagHelper.textContent = existingTagHelperText;
+      showElem('#tag-warning');
+      for (const detail of tagWarningDetailLinks) {
+        detail.href = `${tagURLStub}/${existingTags[value]}`;
+        detail.textContent = existingTags[value].substring(0, 10);
+      }
+      requiresConfirmation = true;
     } else {
       showElem('#tag-target-selector');
       tagHelper.textContent = value ? newTagHelperText : defaultTagHelperText;
+      hideElem('#tag-warning');
+      requiresConfirmation = false;
     }
   };
   hideTargetInput(tagNameInput); // update on page load because the input may have a value