diff --git a/server/upstream/upstream.go b/server/upstream/upstream.go
index 33c80e9..d7eedae 100644
--- a/server/upstream/upstream.go
+++ b/server/upstream/upstream.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/rs/zerolog/log"
 	"github.com/valyala/fasthttp"
+	"github.com/valyala/fastjson"
 
 	"codeberg.org/codeberg/pages/html"
 	"codeberg.org/codeberg/pages/server/cache"
@@ -80,6 +81,26 @@ func (o *Options) Upstream(ctx *fasthttp.RequestCtx, giteaRoot, giteaAPIToken st
 		}
 	}
 	log.Debug().Msg("preparations")
+	
+	// We try to find out if the file is a symlink
+	uriContent := path.Join(o.TargetOwner, o.TargetRepo, "contents", o.TargetPath) + "?ref=" + o.TargetBranch
+	var reqContent *fasthttp.Request
+	var resContent *fasthttp.Response
+	reqContent = fasthttp.AcquireRequest()
+	reqContent.SetRequestURI(path.Join(giteaRoot, giteaAPIRepos, uriContent))
+	reqContent.Header.Set(fasthttp.HeaderAuthorization, giteaAPIToken)
+	resContent = fasthttp.AcquireResponse()
+	resContent.SetBodyStream(&strings.Reader{}, -1)
+	errContent := getFastHTTPClient(10*time.Second).Do(reqContent, resContent)
+	if errContent == nil {
+		contentBody := resContent.Body()
+		if fastjson.GetString(contentBody, "type") == "symlink" {
+			symlinkTarget := fastjson.GetString(contentBody, "target")
+			log.Debug().Msg(o.TargetPath + " is a symlink to " + symlinkTarget)
+			ctx.Redirect(symlinkTarget, fasthttp.StatusTemporaryRedirect)
+			return true
+		}
+	}
 
 	// Make a GET request to the upstream URL
 	uri := path.Join(o.TargetOwner, o.TargetRepo, "raw", o.TargetBranch, o.TargetPath)