diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 27e56542ee..3ee2270d33 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -700,11 +700,11 @@ LEVEL = Info
 ;; Whether a new user needs to be confirmed manually after registration. (Requires `REGISTER_EMAIL_CONFIRM` to be disabled.)
 ;REGISTER_MANUAL_CONFIRM = false
 ;;
-;; List of domain names that are allowed to be used to register on a Gitea instance
-;; gitea.io,example.com
-;EMAIL_DOMAIN_WHITELIST =
+;; List of domain names that are allowed to be used to register on a Gitea instance, wildcard is supported
+;; eg: gitea.io,example.com,*.mydomain.com
+;EMAIL_DOMAIN_ALLOWLIST =
 ;;
-;; Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance
+;; Comma-separated list of domain names that are not allowed to be used to register on a Gitea instance, wildcard is supported
 ;EMAIL_DOMAIN_BLOCKLIST =
 ;;
 ;; Disallow registration, only allow admins to create accounts.
diff --git a/docs/content/doc/administration/config-cheat-sheet.en-us.md b/docs/content/doc/administration/config-cheat-sheet.en-us.md
index 5fa4c5624c..cb75fc588a 100644
--- a/docs/content/doc/administration/config-cheat-sheet.en-us.md
+++ b/docs/content/doc/administration/config-cheat-sheet.en-us.md
@@ -651,9 +651,8 @@ And the following unique queues:
 - `ENABLE_TIMETRACKING`: **true**: Enable Timetracking feature.
 - `DEFAULT_ENABLE_TIMETRACKING`: **true**: Allow repositories to use timetracking by default.
 - `DEFAULT_ALLOW_ONLY_CONTRIBUTORS_TO_TRACK_TIME`: **true**: Only allow users with write permissions to track time.
-- `EMAIL_DOMAIN_WHITELIST`: **\<empty\>**: If non-empty, list of domain names that can only be used to register
-  on this instance.
-- `EMAIL_DOMAIN_BLOCKLIST`: **\<empty\>**: If non-empty, list of domain names that cannot be used to register on this instance
+- `EMAIL_DOMAIN_ALLOWLIST`: **\<empty\>**: If non-empty, comma separated list of domain names that can only be used to register on this instance, wildcard is supported.
+- `EMAIL_DOMAIN_BLOCKLIST`: **\<empty\>**: If non-empty, comma separated list of domain names that cannot be used to register on this instance, wildcard is supported.
 - `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button
 - `SHOW_MILESTONES_DASHBOARD_PAGE`: **true** Enable this to show the milestones dashboard page - a view of all the user's milestones
 - `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created
diff --git a/modules/setting/service.go b/modules/setting/service.go
index d4a31ba5d4..03225f566b 100644
--- a/modules/setting/service.go
+++ b/modules/setting/service.go
@@ -10,6 +10,8 @@ import (
 
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/structs"
+
+	"github.com/gobwas/glob"
 )
 
 // enumerates all the types of captchas
@@ -33,8 +35,8 @@ var Service = struct {
 	ResetPwdCodeLives                       int
 	RegisterEmailConfirm                    bool
 	RegisterManualConfirm                   bool
-	EmailDomainWhitelist                    []string
-	EmailDomainBlocklist                    []string
+	EmailDomainAllowList                    []glob.Glob
+	EmailDomainBlockList                    []glob.Glob
 	DisableRegistration                     bool
 	AllowOnlyInternalRegistration           bool
 	AllowOnlyExternalRegistration           bool
@@ -114,6 +116,20 @@ func (a AllowedVisibility) ToVisibleTypeSlice() (result []structs.VisibleType) {
 	return result
 }
 
+func CompileEmailGlobList(sec ConfigSection, keys ...string) (globs []glob.Glob) {
+	for _, key := range keys {
+		list := sec.Key(key).Strings(",")
+		for _, s := range list {
+			if g, err := glob.Compile(s); err == nil {
+				globs = append(globs, g)
+			} else {
+				log.Error("Skip invalid email allow/block list expression %q: %v", s, err)
+			}
+		}
+	}
+	return globs
+}
+
 func loadServiceFrom(rootCfg ConfigProvider) {
 	sec := rootCfg.Section("service")
 	Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180)
@@ -130,8 +146,11 @@ func loadServiceFrom(rootCfg ConfigProvider) {
 	} else {
 		Service.RegisterManualConfirm = false
 	}
-	Service.EmailDomainWhitelist = sec.Key("EMAIL_DOMAIN_WHITELIST").Strings(",")
-	Service.EmailDomainBlocklist = sec.Key("EMAIL_DOMAIN_BLOCKLIST").Strings(",")
+	if sec.HasKey("EMAIL_DOMAIN_WHITELIST") {
+		deprecatedSetting(rootCfg, "service", "EMAIL_DOMAIN_WHITELIST", "service", "EMAIL_DOMAIN_ALLOWLIST", "1.21")
+	}
+	Service.EmailDomainAllowList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_WHITELIST", "EMAIL_DOMAIN_ALLOWLIST")
+	Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST")
 	Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration))
 	Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true)
 	Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
diff --git a/modules/setting/service_test.go b/modules/setting/service_test.go
new file mode 100644
index 0000000000..656e759f42
--- /dev/null
+++ b/modules/setting/service_test.go
@@ -0,0 +1,46 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package setting
+
+import (
+	"testing"
+
+	"github.com/gobwas/glob"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestLoadServices(t *testing.T) {
+	oldService := Service
+	defer func() {
+		Service = oldService
+	}()
+
+	cfg, err := NewConfigProviderFromData(`
+[service]
+EMAIL_DOMAIN_WHITELIST = d1, *.w
+EMAIL_DOMAIN_ALLOWLIST = d2, *.a
+EMAIL_DOMAIN_BLOCKLIST = d3, *.b
+`)
+	assert.NoError(t, err)
+	loadServiceFrom(cfg)
+
+	match := func(globs []glob.Glob, s string) bool {
+		for _, g := range globs {
+			if g.Match(s) {
+				return true
+			}
+		}
+		return false
+	}
+
+	assert.True(t, match(Service.EmailDomainAllowList, "d1"))
+	assert.True(t, match(Service.EmailDomainAllowList, "foo.w"))
+	assert.True(t, match(Service.EmailDomainAllowList, "d2"))
+	assert.True(t, match(Service.EmailDomainAllowList, "foo.a"))
+	assert.False(t, match(Service.EmailDomainAllowList, "d3"))
+
+	assert.True(t, match(Service.EmailDomainBlockList, "d3"))
+	assert.True(t, match(Service.EmailDomainBlockList, "foo.b"))
+	assert.False(t, match(Service.EmailDomainBlockList, "d1"))
+}
diff --git a/services/forms/user_form.go b/services/forms/user_form.go
index fa8129bf85..1e04f85319 100644
--- a/services/forms/user_form.go
+++ b/services/forms/user_form.go
@@ -16,6 +16,7 @@ import (
 	"code.gitea.io/gitea/modules/web/middleware"
 
 	"gitea.com/go-chi/binding"
+	"github.com/gobwas/glob"
 )
 
 // InstallForm form for installation page
@@ -105,8 +106,8 @@ func (f *RegisterForm) Validate(req *http.Request, errs binding.Errors) binding.
 
 // IsEmailDomainListed checks whether the domain of an email address
 // matches a list of domains
-func IsEmailDomainListed(list []string, email string) bool {
-	if len(list) == 0 {
+func IsEmailDomainListed(globs []glob.Glob, email string) bool {
+	if len(globs) == 0 {
 		return false
 	}
 
@@ -117,8 +118,8 @@ func IsEmailDomainListed(list []string, email string) bool {
 
 	domain := strings.ToLower(email[n+1:])
 
-	for _, v := range list {
-		if strings.ToLower(v) == domain {
+	for _, g := range globs {
+		if g.Match(domain) {
 			return true
 		}
 	}
@@ -131,12 +132,12 @@ func IsEmailDomainListed(list []string, email string) bool {
 // The email is marked as allowed if it matches any of the
 // domains in the whitelist or if it doesn't match any of
 // domains in the blocklist, if any such list is not empty.
-func (f RegisterForm) IsEmailDomainAllowed() bool {
-	if len(setting.Service.EmailDomainWhitelist) == 0 {
-		return !IsEmailDomainListed(setting.Service.EmailDomainBlocklist, f.Email)
+func (f *RegisterForm) IsEmailDomainAllowed() bool {
+	if len(setting.Service.EmailDomainAllowList) == 0 {
+		return !IsEmailDomainListed(setting.Service.EmailDomainBlockList, f.Email)
 	}
 
-	return IsEmailDomainListed(setting.Service.EmailDomainWhitelist, f.Email)
+	return IsEmailDomainListed(setting.Service.EmailDomainAllowList, f.Email)
 }
 
 // MustChangePasswordForm form for updating your password after account creation
diff --git a/services/forms/user_form_test.go b/services/forms/user_form_test.go
index 225686f0fe..84efa25d53 100644
--- a/services/forms/user_form_test.go
+++ b/services/forms/user_form_test.go
@@ -10,13 +10,17 @@ import (
 	auth_model "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/modules/setting"
 
+	"github.com/gobwas/glob"
 	"github.com/stretchr/testify/assert"
 )
 
 func TestRegisterForm_IsDomainAllowed_Empty(t *testing.T) {
-	_ = setting.Service
+	oldService := setting.Service
+	defer func() {
+		setting.Service = oldService
+	}()
 
-	setting.Service.EmailDomainWhitelist = []string{}
+	setting.Service.EmailDomainAllowList = nil
 
 	form := RegisterForm{}
 
@@ -24,15 +28,18 @@ func TestRegisterForm_IsDomainAllowed_Empty(t *testing.T) {
 }
 
 func TestRegisterForm_IsDomainAllowed_InvalidEmail(t *testing.T) {
-	_ = setting.Service
+	oldService := setting.Service
+	defer func() {
+		setting.Service = oldService
+	}()
 
-	setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
+	setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("gitea.io")}
 
 	tt := []struct {
 		email string
 	}{
-		{"securitygieqqq"},
-		{"hdudhdd"},
+		{"invalid-email"},
+		{"gitea.io"},
 	}
 
 	for _, v := range tt {
@@ -42,10 +49,13 @@ func TestRegisterForm_IsDomainAllowed_InvalidEmail(t *testing.T) {
 	}
 }
 
-func TestRegisterForm_IsDomainAllowed_WhitelistedEmail(t *testing.T) {
-	_ = setting.Service
+func TestRegisterForm_IsDomainAllowed_AllowedEmail(t *testing.T) {
+	oldService := setting.Service
+	defer func() {
+		setting.Service = oldService
+	}()
 
-	setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
+	setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("gitea.io"), glob.MustCompile("*.allow")}
 
 	tt := []struct {
 		email string
@@ -53,8 +63,11 @@ func TestRegisterForm_IsDomainAllowed_WhitelistedEmail(t *testing.T) {
 	}{
 		{"security@gitea.io", true},
 		{"security@gITea.io", true},
-		{"hdudhdd", false},
+		{"invalid", false},
 		{"seee@example.com", false},
+
+		{"user@my.allow", true},
+		{"user@my.allow1", false},
 	}
 
 	for _, v := range tt {
@@ -64,11 +77,14 @@ func TestRegisterForm_IsDomainAllowed_WhitelistedEmail(t *testing.T) {
 	}
 }
 
-func TestRegisterForm_IsDomainAllowed_BlocklistedEmail(t *testing.T) {
-	_ = setting.Service
+func TestRegisterForm_IsDomainAllowed_BlockedEmail(t *testing.T) {
+	oldService := setting.Service
+	defer func() {
+		setting.Service = oldService
+	}()
 
-	setting.Service.EmailDomainWhitelist = []string{}
-	setting.Service.EmailDomainBlocklist = []string{"gitea.io"}
+	setting.Service.EmailDomainAllowList = nil
+	setting.Service.EmailDomainBlockList = []glob.Glob{glob.MustCompile("gitea.io"), glob.MustCompile("*.block")}
 
 	tt := []struct {
 		email string
@@ -76,7 +92,10 @@ func TestRegisterForm_IsDomainAllowed_BlocklistedEmail(t *testing.T) {
 	}{
 		{"security@gitea.io", false},
 		{"security@gitea.example", true},
-		{"hdudhdd", true},
+		{"invalid", true},
+
+		{"user@my.block", false},
+		{"user@my.block1", true},
 	}
 
 	for _, v := range tt {