diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 754eab452f..cec5e8cf03 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -957,6 +957,9 @@ ROUTER = console
 ;; Don't allow download source archive files from UI
 ;DISABLE_DOWNLOAD_SOURCE_ARCHIVES = false
 
+;; Allow fork repositories without maximum number limit
+;ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT = true
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;[repository.editor]
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 9d7a375ad3..3ccef3130c 100644
--- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md
+++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
@@ -112,6 +112,7 @@ In addition there is _`StaticRootPath`_ which can be set as a built-in at build
 - `ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to adopt unadopted repositories
 - `ALLOW_DELETION_OF_UNADOPTED_REPOSITORIES`: **false**: Allow non-admin users to delete unadopted repositories
 - `DISABLE_DOWNLOAD_SOURCE_ARCHIVES`: **false**: Don't allow download source archive files from UI
+- `ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT`: **true**: Allow fork repositories without maximum number limit
 
 ### Repository - Editor (`repository.editor`)
 
diff --git a/models/user/user.go b/models/user/user.go
index 71b036b06f..825223201b 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -275,6 +275,15 @@ func (u *User) CanEditGitHook() bool {
 	return !setting.DisableGitHooks && (u.IsAdmin || u.AllowGitHook)
 }
 
+// CanForkRepo returns if user login can fork a repository
+// It checks especially that the user can create repos, and potentially more
+func (u *User) CanForkRepo() bool {
+	if setting.Repository.AllowForkWithoutMaximumLimit {
+		return true
+	}
+	return u.CanCreateRepo()
+}
+
 // CanImportLocal returns true if user can migrate repository by local path.
 func (u *User) CanImportLocal() bool {
 	if !setting.ImportLocalPaths || u == nil {
diff --git a/modules/setting/repository.go b/modules/setting/repository.go
index ea288d2ed2..d78b63a1f3 100644
--- a/modules/setting/repository.go
+++ b/modules/setting/repository.go
@@ -48,6 +48,7 @@ var (
 		AllowAdoptionOfUnadoptedRepositories    bool
 		AllowDeleteOfUnadoptedRepositories      bool
 		DisableDownloadSourceArchives           bool
+		AllowForkWithoutMaximumLimit            bool
 
 		// Repository editor settings
 		Editor struct {
@@ -160,6 +161,7 @@ var (
 		DisableMigrations:                       false,
 		DisableStars:                            false,
 		DefaultBranch:                           "main",
+		AllowForkWithoutMaximumLimit:            true,
 
 		// Repository editor settings
 		Editor: struct {
diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go
index f2cd10e711..97c1dc7ba7 100644
--- a/routers/api/v1/repo/fork.go
+++ b/routers/api/v1/repo/fork.go
@@ -141,7 +141,7 @@ func CreateFork(ctx *context.APIContext) {
 		Description: repo.Description,
 	})
 	if err != nil {
-		if repo_model.IsErrRepoAlreadyExist(err) {
+		if repo_model.IsErrReachLimitOfRepo(err) || repo_model.IsErrRepoAlreadyExist(err) {
 			ctx.Error(http.StatusConflict, "ForkRepository", err)
 		} else {
 			ctx.Error(http.StatusInternalServerError, "ForkRepository", err)
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index bea6bfe433..8929a183ee 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -182,6 +182,15 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository {
 func Fork(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("new_fork")
 
+	if ctx.Doer.CanForkRepo() {
+		ctx.Data["CanForkRepo"] = true
+	} else {
+		maxCreationLimit := ctx.Doer.MaxCreationLimit()
+		msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
+		ctx.Data["Flash"] = ctx.Flash
+		ctx.Flash.Error(msg)
+	}
+
 	getForkRepository(ctx)
 	if ctx.Written() {
 		return
@@ -254,6 +263,10 @@ func ForkPost(ctx *context.Context) {
 	if err != nil {
 		ctx.Data["Err_RepoName"] = true
 		switch {
+		case repo_model.IsErrReachLimitOfRepo(err):
+			maxCreationLimit := ctxUser.MaxCreationLimit()
+			msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit)
+			ctx.RenderWithErr(msg, tplFork, &form)
 		case repo_model.IsErrRepoAlreadyExist(err):
 			ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplFork, &form)
 		case db.IsErrNameReserved(err):
diff --git a/services/repository/fork.go b/services/repository/fork.go
index 3ed0f4ffa5..ad534be887 100644
--- a/services/repository/fork.go
+++ b/services/repository/fork.go
@@ -51,6 +51,13 @@ type ForkRepoOptions struct {
 
 // ForkRepository forks a repository
 func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts ForkRepoOptions) (*repo_model.Repository, error) {
+	// Fork is prohibited, if user has reached maximum limit of repositories
+	if !owner.CanForkRepo() {
+		return nil, repo_model.ErrReachLimitOfRepo{
+			Limit: owner.MaxRepoCreation,
+		}
+	}
+
 	forkedRepo, err := repo_model.GetUserFork(ctx, opts.BaseRepo.ID, owner.ID)
 	if err != nil {
 		return nil, err
diff --git a/services/repository/fork_test.go b/services/repository/fork_test.go
index c809ed4529..452798b25b 100644
--- a/services/repository/fork_test.go
+++ b/services/repository/fork_test.go
@@ -10,6 +10,7 @@ import (
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/setting"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -29,4 +30,19 @@ func TestForkRepository(t *testing.T) {
 	assert.Nil(t, fork)
 	assert.Error(t, err)
 	assert.True(t, IsErrForkAlreadyExist(err))
+
+	// user not reached maximum limit of repositories
+	assert.False(t, repo_model.IsErrReachLimitOfRepo(err))
+
+	// change AllowForkWithoutMaximumLimit to false for the test
+	setting.Repository.AllowForkWithoutMaximumLimit = false
+	// user has reached maximum limit of repositories
+	user.MaxRepoCreation = 0
+	fork2, err := ForkRepository(git.DefaultContext, user, user, ForkRepoOptions{
+		BaseRepo:    repo,
+		Name:        "test",
+		Description: "test",
+	})
+	assert.Nil(t, fork2)
+	assert.True(t, repo_model.IsErrReachLimitOfRepo(err))
 }
diff --git a/templates/repo/pulls/fork.tmpl b/templates/repo/pulls/fork.tmpl
index 28ba8e2e25..4e20642cf6 100644
--- a/templates/repo/pulls/fork.tmpl
+++ b/templates/repo/pulls/fork.tmpl
@@ -58,7 +58,7 @@
 
 					<div class="inline field">
 						<label></label>
-						<button class="ui green button">
+						<button class="ui green button{{if not .CanForkRepo}} disabled{{end}}">
 							{{.locale.Tr "repo.fork_repo"}}
 						</button>
 					</div>