diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go
index 3dc5530e00..b650854971 100644
--- a/modules/markup/markdown/goldmark.go
+++ b/modules/markup/markdown/goldmark.go
@@ -7,6 +7,7 @@ import (
 	"bytes"
 	"fmt"
 	"regexp"
+	"slices"
 	"strings"
 
 	"code.gitea.io/gitea/modules/container"
@@ -131,11 +132,17 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 		case *ast.Link:
 			// Links need their href to munged to be a real value
 			link := v.Destination
-			if len(link) > 0 && !markup.IsLink(link) &&
-				link[0] != '#' && !bytes.HasPrefix(link, byteMailto) {
-				// special case: this is not a link, a hash link or a mailto:, so it's a
-				// relative URL
 
+			// Do not process the link if it's not a link, starts with an hashtag
+			// (indicating it's an anchor link), starts with `mailto:` or any of the
+			// custom markdown URLs.
+			processLink := len(link) > 0 && !markup.IsLink(link) &&
+				link[0] != '#' && !bytes.HasPrefix(link, byteMailto) &&
+				!slices.ContainsFunc(setting.Markdown.CustomURLSchemes, func(s string) bool {
+					return bytes.HasPrefix(link, []byte(s+":"))
+				})
+
+			if processLink {
 				var base string
 				if ctx.IsWiki {
 					base = ctx.Links.WikiLink()
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index ee3ec0fda5..3cb11c9482 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -15,6 +15,7 @@ import (
 	"code.gitea.io/gitea/modules/markup"
 	. "code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/test"
 	"code.gitea.io/gitea/modules/util"
 
 	"github.com/stretchr/testify/assert"
@@ -960,3 +961,29 @@ space</p>
 		assert.Equal(t, c.Expected, result, "Unexpected result in testcase %v", i)
 	}
 }
+
+func TestCustomMarkdownURL(t *testing.T) {
+	defer test.MockVariableValue(&setting.Markdown.CustomURLSchemes, []string{"abp"})()
+
+	setting.AppURL = AppURL
+	setting.AppSubURL = AppSubURL
+
+	test := func(input, expected string) {
+		buffer, err := RenderString(&markup.RenderContext{
+			Ctx: git.DefaultContext,
+			Links: markup.Links{
+				Base:       setting.AppSubURL,
+				BranchPath: "branch/main",
+			},
+		}, input)
+		assert.NoError(t, err)
+		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
+	}
+
+	test("[test](abp:subscribe?location=https://codeberg.org/filters.txt&amp;title=joy)",
+		`<p><a href="abp:subscribe?location=https://codeberg.org/filters.txt&amp;title=joy" rel="nofollow">test</a></p>`)
+
+	// Ensure that the schema itself without `:` is still made absolute.
+	test("[test](abp)",
+		`<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/abp" rel="nofollow">test</a></p>`)
+}