diff --git a/modules/markup/html.go b/modules/markup/html.go
index cef643bf1805..5ae0cc8755c0 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -591,17 +591,16 @@ func replaceContentList(node *html.Node, i, j int, newNodes []*html.Node) {
 
 func mentionProcessor(ctx *RenderContext, node *html.Node) {
 	start := 0
-	next := node.NextSibling
-	for node != nil && node != next && start < len(node.Data) {
-		// We replace only the first mention; other mentions will be addressed later
-		found, loc := references.FindFirstMentionBytes([]byte(node.Data[start:]))
+	for node != nil {
+		found, loc := references.FindFirstMentionBytes(util.UnsafeStringToBytes(node.Data[start:]))
 		if !found {
-			return
+			node = node.NextSibling
+			start = 0
+			continue
 		}
 		loc.Start += start
 		loc.End += start
 		mention := node.Data[loc.Start:loc.End]
-		var teams string
 		teams, ok := ctx.Metas["teams"]
 		// FIXME: util.URLJoin may not be necessary here:
 		// - setting.AppURL is defined to have a terminal '/' so unless mention[1:]
@@ -623,10 +622,10 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) {
 		if DefaultProcessorHelper.IsUsernameMentionable != nil && DefaultProcessorHelper.IsUsernameMentionable(ctx.Ctx, mentionedUsername) {
 			replaceContent(node, loc.Start, loc.End, createLink(util.URLJoin(ctx.Links.Prefix(), mentionedUsername), mention, "mention"))
 			node = node.NextSibling.NextSibling
+			start = 0
 		} else {
-			node = node.NextSibling
+			start = loc.End
 		}
-		start = 0
 	}
 }
 
diff --git a/modules/references/references.go b/modules/references/references.go
index 761d6ee3d184..1b656ed4cbf4 100644
--- a/modules/references/references.go
+++ b/modules/references/references.go
@@ -29,7 +29,7 @@ var (
 	// TODO: fix invalid linking issue
 
 	// mentionPattern matches all mentions in the form of "@user" or "@org/team"
-	mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_]+\/?[0-9a-zA-Z-_]+|@[0-9a-zA-Z-_][0-9a-zA-Z-_.]+\/?[0-9a-zA-Z-_.]+[0-9a-zA-Z-_])(?:\s|[:,;.?!]\s|[:,;.?!]?$|\)|\])`)
+	mentionPattern = regexp.MustCompile(`(?:\s|^|\(|\[)(@[-\w][-.\w]*?|@[-\w][-.\w]*?/[-\w][-.\w]*?)(?:\s|$|[:,;.?!](\s|$)|'|\)|\])`)
 	// issueNumericPattern matches string that references to a numeric issue, e.g. #1287
 	issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\")([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$)`)
 	// issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234
diff --git a/modules/references/references_test.go b/modules/references/references_test.go
index 0c3293361910..e5a0d60fe3bc 100644
--- a/modules/references/references_test.go
+++ b/modules/references/references_test.go
@@ -392,6 +392,7 @@ func TestRegExp_mentionPattern(t *testing.T) {
 		{"@gitea,", "@gitea"},
 		{"@gitea;", "@gitea"},
 		{"@gitea/team1;", "@gitea/team1"},
+		{"@user's idea", "@user"},
 	}
 	falseTestCases := []string{
 		"@ 0",
@@ -412,7 +413,6 @@ func TestRegExp_mentionPattern(t *testing.T) {
 
 	for _, testCase := range trueTestCases {
 		found := mentionPattern.FindStringSubmatch(testCase.pat)
-		assert.Len(t, found, 2)
 		assert.Equal(t, testCase.exp, found[1])
 	}
 	for _, testCase := range falseTestCases {
diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go
index 47c5da6485c3..f493b899e393 100644
--- a/modules/templates/util_render_test.go
+++ b/modules/templates/util_render_test.go
@@ -207,3 +207,8 @@ func TestRenderLabels(t *testing.T) {
 	expected = `/owner/repo/pulls?labels=123`
 	assert.Contains(t, RenderLabels(ctx, locale, []*issues.Label{label}, "/owner/repo", issue), expected)
 }
+
+func TestUserMention(t *testing.T) {
+	rendered := RenderMarkdownToHtml(context.Background(), "@no-such-user @mention-user @mention-user")
+	assert.EqualValues(t, `<p>@no-such-user <a href="/mention-user" rel="nofollow">@mention-user</a> <a href="/mention-user" rel="nofollow">@mention-user</a></p>`, strings.TrimSpace(string(rendered)))
+}