diff --git a/cmd/main.go b/cmd/main.go
index 257b724..80fb666 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -82,7 +82,10 @@ func Serve(ctx *cli.Context) error {
 	// TODO: make this an MRU cache with a size limit
 	fileResponseCache := cache.NewKeyValueCache()
 
-	giteaClient := gitea.NewClient(giteaRoot, giteaAPIToken)
+	giteaClient, err := gitea.NewClient(giteaRoot, giteaAPIToken)
+	if err != nil {
+		return fmt.Errorf("could not create new gitea client: %v", err)
+	}
 
 	// Create handler based on settings
 	handler := server.Handler(mainDomainSuffix, []byte(rawDomain),
diff --git a/server/gitea/client.go b/server/gitea/client.go
index d4eb980..5410413 100644
--- a/server/gitea/client.go
+++ b/server/gitea/client.go
@@ -4,7 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"net/url"
-	"path"
+	"strings"
 	"time"
 
 	"github.com/valyala/fasthttp"
@@ -29,18 +29,33 @@ type FileResponse struct {
 	Body     []byte
 }
 
-func joinURL(giteaRoot string, paths ...string) string { return giteaRoot + path.Join(paths...) }
+// TODO: once golang v1.19 is min requirement, we can switch to 'JoinPath()' of 'net/url' package
+func joinURL(baseURL string, paths ...string) string {
+	p := make([]string, 0, len(paths))
+	for i := range paths {
+		path := strings.TrimSpace(paths[i])
+		path = strings.Trim(path, "/")
+		if len(path) != 0 {
+			p = append(p, path)
+		}
+	}
+
+	return baseURL + "/" + strings.Join(p, "/")
+}
 
 func (f FileResponse) IsEmpty() bool { return len(f.Body) != 0 }
 
-func NewClient(giteaRoot, giteaAPIToken string) *Client {
+func NewClient(giteaRoot, giteaAPIToken string) (*Client, error) {
+	rootURL, err := url.Parse(giteaRoot)
+	giteaRoot = strings.Trim(rootURL.String(), "/")
+
 	return &Client{
 		giteaRoot:      giteaRoot,
 		giteaAPIToken:  giteaAPIToken,
 		infoTimeout:    5 * time.Second,
 		contentTimeout: 10 * time.Second,
 		fastClient:     getFastHTTPClient(),
-	}
+	}, err
 }
 
 func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource string) ([]byte, error) {
diff --git a/server/gitea/client_test.go b/server/gitea/client_test.go
index bae9d4e..7dbad68 100644
--- a/server/gitea/client_test.go
+++ b/server/gitea/client_test.go
@@ -1,23 +1,23 @@
 package gitea
 
 import (
+	"net/url"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
 func TestJoinURL(t *testing.T) {
-	url := joinURL("")
-	assert.EqualValues(t, "", url)
+	baseURL := ""
+	assert.EqualValues(t, "/", joinURL(baseURL))
+	assert.EqualValues(t, "/", joinURL(baseURL, "", ""))
 
-	url = joinURL("", "", "")
-	assert.EqualValues(t, "", url)
+	baseURL = "http://wwow.url.com"
+	assert.EqualValues(t, "http://wwow.url.com/a/b/c/d", joinURL(baseURL, "a", "b/c/", "d"))
 
-	url = joinURL("http://wwow.url.com", "a", "b/c/", "d")
-	// assert.EqualValues(t, "http://wwow.url.com/a/b/c/d", url)
-	assert.EqualValues(t, "http://wwow.url.coma/b/c/d", url)
-
-	url = joinURL("h:://wrong", "acdc")
-	// assert.EqualValues(t, "h:://wrong/acdc", url)
-	assert.EqualValues(t, "h:://wrongacdc", url)
+	baseURL = "http://wow.url.com/subpath/2"
+	assert.EqualValues(t, "http://wow.url.com/subpath/2/content.pdf", joinURL(baseURL, "/content.pdf"))
+	assert.EqualValues(t, "http://wow.url.com/subpath/2/wonderful.jpg", joinURL(baseURL, "wonderful.jpg"))
+	assert.EqualValues(t, "http://wow.url.com/subpath/2/raw/wonderful.jpg?ref=main", joinURL(baseURL, "raw", "wonderful.jpg"+"?ref="+url.QueryEscape("main")))
+	assert.EqualValues(t, "http://wow.url.com/subpath/2/raw/wonderful.jpg%3Fref=main", joinURL(baseURL, "raw", "wonderful.jpg%3Fref=main"))
 }
diff --git a/server/handler_test.go b/server/handler_test.go
index 73002a2..23d9af5 100644
--- a/server/handler_test.go
+++ b/server/handler_test.go
@@ -13,7 +13,7 @@ import (
 
 func TestHandlerPerformance(t *testing.T) {
 	giteaRoot := "https://codeberg.org"
-	giteaClient := gitea.NewClient(giteaRoot, "")
+	giteaClient, _ := gitea.NewClient(giteaRoot, "")
 	testHandler := Handler(
 		[]byte("codeberg.page"), []byte("raw.codeberg.org"),
 		giteaClient,