diff --git a/modules/context/context.go b/modules/context/context.go
index 50c34edae2..5876e23cc4 100644
--- a/modules/context/context.go
+++ b/modules/context/context.go
@@ -16,8 +16,10 @@ import (
 	"net/http"
 	"net/url"
 	"path"
+	"regexp"
 	"strconv"
 	"strings"
+	texttemplate "text/template"
 	"time"
 
 	"code.gitea.io/gitea/models/db"
@@ -213,6 +215,8 @@ func (ctx *Context) RedirectToFirst(location ...string) {
 	ctx.Redirect(setting.AppSubURL + "/")
 }
 
+var templateExecutingErr = regexp.MustCompile(`^template: (.*):([1-9][0-9]*):([1-9][0-9]*): executing (?:"(.*)" at <(.*)>: )?`)
+
 // HTML calls Context.HTML and renders the template to HTTP response
 func (ctx *Context) HTML(status int, name base.TplName) {
 	log.Debug("Template: %s", name)
@@ -228,6 +232,34 @@ func (ctx *Context) HTML(status int, name base.TplName) {
 			ctx.PlainText(http.StatusInternalServerError, "Unable to find status/500 template")
 			return
 		}
+		if execErr, ok := err.(texttemplate.ExecError); ok {
+			if groups := templateExecutingErr.FindStringSubmatch(err.Error()); len(groups) > 0 {
+				errorTemplateName, lineStr, posStr := groups[1], groups[2], groups[3]
+				target := ""
+				if len(groups) == 6 {
+					target = groups[5]
+				}
+				line, _ := strconv.Atoi(lineStr) // Cannot error out as groups[2] is [1-9][0-9]*
+				pos, _ := strconv.Atoi(posStr)   // Cannot error out as groups[3] is [1-9][0-9]*
+				filename, filenameErr := templates.GetAssetFilename("templates/" + errorTemplateName + ".tmpl")
+				if filenameErr != nil {
+					filename = "(template) " + errorTemplateName
+				}
+				if errorTemplateName != string(name) {
+					filename += " (subtemplate of " + string(name) + ")"
+				}
+				err = fmt.Errorf("%w\nin template file %s:\n%s", err, filename, templates.GetLineFromTemplate(errorTemplateName, line, target, pos))
+			} else {
+				filename, filenameErr := templates.GetAssetFilename("templates/" + execErr.Name + ".tmpl")
+				if filenameErr != nil {
+					filename = "(template) " + execErr.Name
+				}
+				if execErr.Name != string(name) {
+					filename += " (subtemplate of " + string(name) + ")"
+				}
+				err = fmt.Errorf("%w\nin template file %s", err, filename)
+			}
+		}
 		ctx.ServerError("Render failed", err)
 	}
 }
diff --git a/modules/templates/htmlrenderer.go b/modules/templates/htmlrenderer.go
index 5a328043eb..96dc010796 100644
--- a/modules/templates/htmlrenderer.go
+++ b/modules/templates/htmlrenderer.go
@@ -118,7 +118,7 @@ func handleGenericTemplateError(err error) (string, []interface{}) {
 
 	lineNumber, _ := strconv.Atoi(lineNumberStr)
 
-	line := getLineFromAsset(templateName, lineNumber, "")
+	line := GetLineFromTemplate(templateName, lineNumber, "", -1)
 
 	return "PANIC: Unable to compile templates!\n%s in template file %s at line %d:\n\n%s\nStacktrace:\n\n%s", []interface{}{message, filename, lineNumber, log.NewColoredValue(line, log.Reset), log.Stack(2)}
 }
@@ -140,7 +140,7 @@ func handleNotDefinedPanicError(err error) (string, []interface{}) {
 
 	lineNumber, _ := strconv.Atoi(lineNumberStr)
 
-	line := getLineFromAsset(templateName, lineNumber, functionName)
+	line := GetLineFromTemplate(templateName, lineNumber, functionName, -1)
 
 	return "PANIC: Unable to compile templates!\nUndefined function %q in template file %s at line %d:\n\n%s", []interface{}{functionName, filename, lineNumber, log.NewColoredValue(line, log.Reset)}
 }
@@ -161,7 +161,7 @@ func handleUnexpected(err error) (string, []interface{}) {
 
 	lineNumber, _ := strconv.Atoi(lineNumberStr)
 
-	line := getLineFromAsset(templateName, lineNumber, unexpected)
+	line := GetLineFromTemplate(templateName, lineNumber, unexpected, -1)
 
 	return "PANIC: Unable to compile templates!\nUnexpected %q in template file %s at line %d:\n\n%s", []interface{}{unexpected, filename, lineNumber, log.NewColoredValue(line, log.Reset)}
 }
@@ -181,14 +181,15 @@ func handleExpectedEnd(err error) (string, []interface{}) {
 
 	lineNumber, _ := strconv.Atoi(lineNumberStr)
 
-	line := getLineFromAsset(templateName, lineNumber, unexpected)
+	line := GetLineFromTemplate(templateName, lineNumber, unexpected, -1)
 
 	return "PANIC: Unable to compile templates!\nMissing end with unexpected %q in template file %s at line %d:\n\n%s", []interface{}{unexpected, filename, lineNumber, log.NewColoredValue(line, log.Reset)}
 }
 
 const dashSeparator = "----------------------------------------------------------------------\n"
 
-func getLineFromAsset(templateName string, targetLineNum int, target string) string {
+// GetLineFromTemplate returns a line from a template with some context
+func GetLineFromTemplate(templateName string, targetLineNum int, target string, position int) string {
 	bs, err := GetAsset("templates/" + templateName + ".tmpl")
 	if err != nil {
 		return fmt.Sprintf("(unable to read template file: %v)", err)
@@ -229,24 +230,30 @@ func getLineFromAsset(templateName string, targetLineNum int, target string) str
 	// If there is a provided target to look for in the line add a pointer to it
 	// e.g.                                                        ^^^^^^^
 	if target != "" {
-		idx := bytes.Index(lineBs, []byte(target))
-
-		if idx >= 0 {
-			// take the current line and replace preceding text with whitespace (except for tab)
-			for i := range lineBs[:idx] {
-				if lineBs[i] != '\t' {
-					lineBs[i] = ' '
-				}
-			}
-
-			// write the preceding "space"
-			_, _ = sb.Write(lineBs[:idx])
-
-			// Now write the ^^ pointer
-			_, _ = sb.WriteString(strings.Repeat("^", len(target)))
-			_ = sb.WriteByte('\n')
+		targetPos := bytes.Index(lineBs, []byte(target))
+		if targetPos >= 0 {
+			position = targetPos
 		}
 	}
+	if position >= 0 {
+		// take the current line and replace preceding text with whitespace (except for tab)
+		for i := range lineBs[:position] {
+			if lineBs[i] != '\t' {
+				lineBs[i] = ' '
+			}
+		}
+
+		// write the preceding "space"
+		_, _ = sb.Write(lineBs[:position])
+
+		// Now write the ^^ pointer
+		targetLen := len(target)
+		if targetLen == 0 {
+			targetLen = 1
+		}
+		_, _ = sb.WriteString(strings.Repeat("^", targetLen))
+		_ = sb.WriteByte('\n')
+	}
 
 	// Finally write the footer
 	sb.WriteString(dashSeparator)