diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 2ade90799..1d19a3438 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -1587,6 +1587,10 @@ PATH =
 ;AVATAR_MAX_WIDTH = 4096
 ;AVATAR_MAX_HEIGHT = 3072
 ;;
+;; The multiplication factor for rendered avatar images.
+;; Larger values result in finer rendering on HiDPI devices.
+;AVATAR_RENDERED_SIZE_FACTOR = 3
+;;
 ;; Maximum allowed file size for uploaded avatars.
 ;; This is to limit the amount of RAM used when resizing the image.
 ;AVATAR_MAX_FILE_SIZE = 1048576
diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
index e2a2687fa..07655a181 100644
--- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md
+++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
@@ -710,6 +710,7 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type
 - `AVATAR_MAX_WIDTH`: **4096**: Maximum avatar image width in pixels.
 - `AVATAR_MAX_HEIGHT`: **3072**: Maximum avatar image height in pixels.
 - `AVATAR_MAX_FILE_SIZE`: **1048576** (1Mb): Maximum avatar image file size in bytes.
+- `AVATAR_RENDERED_SIZE_FACTOR`: **3**: The multiplication factor for rendered avatar images. Larger values result in finer rendering on HiDPI devices.
 
 - `REPOSITORY_AVATAR_STORAGE_TYPE`: **default**: Storage type defined in `[storage.xxx]`. Default is `default` which will read `[storage]` if no section `[storage]` will be a type `local`.
 - `REPOSITORY_AVATAR_UPLOAD_PATH`: **data/repo-avatars**: Path to store repository avatar image files.
diff --git a/models/avatars/avatar.go b/models/avatars/avatar.go
index da63dfd10..7206a8ae9 100644
--- a/models/avatars/avatar.go
+++ b/models/avatars/avatar.go
@@ -21,9 +21,6 @@ import (
 // DefaultAvatarPixelSize is the default size in pixels of a rendered avatar
 const DefaultAvatarPixelSize = 28
 
-// AvatarRenderedSizeFactor is the factor by which the default size is increased for finer rendering
-const AvatarRenderedSizeFactor = 4
-
 // EmailHash represents a pre-generated hash map (mainly used by LibravatarURL, it queries email server's DNS records)
 type EmailHash struct {
 	Hash  string `xorm:"pk varchar(32)"`
diff --git a/modules/repository/commits.go b/modules/repository/commits.go
index 8e727c95d..9ff9a9531 100644
--- a/modules/repository/commits.go
+++ b/modules/repository/commits.go
@@ -13,6 +13,7 @@ import (
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
 )
 
@@ -141,7 +142,7 @@ func (pc *PushCommits) AvatarLink(email string) string {
 		return avatar
 	}
 
-	size := avatars.DefaultAvatarPixelSize * avatars.AvatarRenderedSizeFactor
+	size := avatars.DefaultAvatarPixelSize * setting.Avatar.RenderedSizeFactor
 
 	u, ok := pc.emailUsers[email]
 	if !ok {
diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go
index 30edf3362..d01388fe1 100644
--- a/modules/repository/commits_test.go
+++ b/modules/repository/commits_test.go
@@ -124,13 +124,13 @@ func TestPushCommits_AvatarLink(t *testing.T) {
 	}
 
 	assert.Equal(t,
-		"https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon&s=112",
+		"https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon&s=84",
 		pushCommits.AvatarLink("user2@example.com"))
 
 	assert.Equal(t,
 		"https://secure.gravatar.com/avatar/"+
 			fmt.Sprintf("%x", md5.Sum([]byte("nonexistent@example.com")))+
-			"?d=identicon&s=112",
+			"?d=identicon&s=84",
 		pushCommits.AvatarLink("nonexistent@example.com"))
 }
 
diff --git a/modules/setting/picture.go b/modules/setting/picture.go
index 415552d7c..a6d3447dc 100644
--- a/modules/setting/picture.go
+++ b/modules/setting/picture.go
@@ -18,13 +18,15 @@ var (
 	Avatar = struct {
 		Storage
 
-		MaxWidth    int
-		MaxHeight   int
-		MaxFileSize int64
+		MaxWidth           int
+		MaxHeight          int
+		MaxFileSize        int64
+		RenderedSizeFactor int
 	}{
-		MaxWidth:    4096,
-		MaxHeight:   3072,
-		MaxFileSize: 1048576,
+		MaxWidth:           4096,
+		MaxHeight:          3072,
+		MaxFileSize:        1048576,
+		RenderedSizeFactor: 3,
 	}
 
 	GravatarSource        string
@@ -55,6 +57,7 @@ func newPictureService() {
 	Avatar.MaxWidth = sec.Key("AVATAR_MAX_WIDTH").MustInt(4096)
 	Avatar.MaxHeight = sec.Key("AVATAR_MAX_HEIGHT").MustInt(3072)
 	Avatar.MaxFileSize = sec.Key("AVATAR_MAX_FILE_SIZE").MustInt64(1048576)
+	Avatar.RenderedSizeFactor = sec.Key("AVATAR_RENDERED_SIZE_FACTOR").MustInt(3)
 
 	switch source := sec.Key("GRAVATAR_SOURCE").MustString("gravatar"); source {
 	case "duoshuo":
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 1841ad273..c1c6b4034 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -557,17 +557,17 @@ func Avatar(item interface{}, others ...interface{}) template.HTML {
 
 	switch t := item.(type) {
 	case *user_model.User:
-		src := t.AvatarLinkWithSize(size * avatars.AvatarRenderedSizeFactor)
+		src := t.AvatarLinkWithSize(size * setting.Avatar.RenderedSizeFactor)
 		if src != "" {
 			return AvatarHTML(src, size, class, t.DisplayName())
 		}
 	case *models.Collaborator:
-		src := t.AvatarLinkWithSize(size * avatars.AvatarRenderedSizeFactor)
+		src := t.AvatarLinkWithSize(size * setting.Avatar.RenderedSizeFactor)
 		if src != "" {
 			return AvatarHTML(src, size, class, t.DisplayName())
 		}
 	case *models.Organization:
-		src := t.AsUser().AvatarLinkWithSize(size * avatars.AvatarRenderedSizeFactor)
+		src := t.AsUser().AvatarLinkWithSize(size * setting.Avatar.RenderedSizeFactor)
 		if src != "" {
 			return AvatarHTML(src, size, class, t.AsUser().DisplayName())
 		}
@@ -596,7 +596,7 @@ func RepoAvatar(repo *repo_model.Repository, others ...interface{}) template.HTM
 // AvatarByEmail renders avatars by email address. args: email, name, size (int), class (string)
 func AvatarByEmail(email string, name string, others ...interface{}) template.HTML {
 	size, class := parseOthers(avatars.DefaultAvatarPixelSize, "ui avatar image", others...)
-	src := avatars.GenerateEmailAvatarFastLink(email, size*avatars.AvatarRenderedSizeFactor)
+	src := avatars.GenerateEmailAvatarFastLink(email, size*setting.Avatar.RenderedSizeFactor)
 
 	if src != "" {
 		return AvatarHTML(src, size, class, name)