diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index b50c5b6738..d2c99dff20 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -801,6 +801,11 @@ LEVEL = Info
 ;; Every new user will have restricted permissions depending on this setting
 ;DEFAULT_USER_IS_RESTRICTED = false
 ;;
+;; Users will be able to use dots when choosing their username. Disabling this is
+;; helpful if your usersare having issues with e.g. RSS feeds or advanced third-party
+;; extensions that use strange regex patterns.
+; ALLOW_DOTS_IN_USERNAMES = true
+;;
 ;; Either "public", "limited" or "private", default is "public"
 ;; Limited is for users visible only to signed users
 ;; Private is for users visible only to members of their organizations
diff --git a/modules/setting/service.go b/modules/setting/service.go
index 595ea6528f..446ea8471a 100644
--- a/modules/setting/service.go
+++ b/modules/setting/service.go
@@ -67,6 +67,7 @@ var Service = struct {
 	DefaultKeepEmailPrivate                 bool
 	DefaultAllowCreateOrganization          bool
 	DefaultUserIsRestricted                 bool
+	AllowDotsInUsernames                    bool
 	EnableTimetracking                      bool
 	DefaultEnableTimetracking               bool
 	DefaultEnableDependencies               bool
@@ -178,6 +179,7 @@ func loadServiceFrom(rootCfg ConfigProvider) {
 	Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()
 	Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true)
 	Service.DefaultUserIsRestricted = sec.Key("DEFAULT_USER_IS_RESTRICTED").MustBool(false)
+	Service.AllowDotsInUsernames = sec.Key("ALLOW_DOTS_IN_USERNAMES").MustBool(true)
 	Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true)
 	if Service.EnableTimetracking {
 		Service.DefaultEnableTimetracking = sec.Key("DEFAULT_ENABLE_TIMETRACKING").MustBool(true)
diff --git a/modules/validation/helpers.go b/modules/validation/helpers.go
index 3381846b86..2f88fcbc60 100644
--- a/modules/validation/helpers.go
+++ b/modules/validation/helpers.go
@@ -92,13 +92,20 @@ func IsValidExternalTrackerURLFormat(uri string) bool {
 }
 
 var (
-	validUsernamePattern   = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`)
-	invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`) // No consecutive or trailing non-alphanumeric chars
+	validUsernamePatternWithDots    = regexp.MustCompile(`^[\da-zA-Z][-.\w]*$`)
+	validUsernamePatternWithoutDots = regexp.MustCompile(`^[\da-zA-Z][-\w]*$`)
+
+	// No consecutive or trailing non-alphanumeric chars, catches both cases
+	invalidUsernamePattern = regexp.MustCompile(`[-._]{2,}|[-._]$`)
 )
 
 // IsValidUsername checks if username is valid
 func IsValidUsername(name string) bool {
 	// It is difficult to find a single pattern that is both readable and effective,
 	// but it's easier to use positive and negative checks.
-	return validUsernamePattern.MatchString(name) && !invalidUsernamePattern.MatchString(name)
+	if setting.Service.AllowDotsInUsernames {
+		return validUsernamePatternWithDots.MatchString(name) && !invalidUsernamePattern.MatchString(name)
+	}
+
+	return validUsernamePatternWithoutDots.MatchString(name) && !invalidUsernamePattern.MatchString(name)
 }
diff --git a/modules/validation/helpers_test.go b/modules/validation/helpers_test.go
index 52f383f698..a1bdf2a29c 100644
--- a/modules/validation/helpers_test.go
+++ b/modules/validation/helpers_test.go
@@ -155,7 +155,8 @@ func Test_IsValidExternalTrackerURLFormat(t *testing.T) {
 	}
 }
 
-func TestIsValidUsername(t *testing.T) {
+func TestIsValidUsernameAllowDots(t *testing.T) {
+	setting.Service.AllowDotsInUsernames = true
 	tests := []struct {
 		arg  string
 		want bool
@@ -185,3 +186,31 @@ func TestIsValidUsername(t *testing.T) {
 		})
 	}
 }
+
+func TestIsValidUsernameBanDots(t *testing.T) {
+	setting.Service.AllowDotsInUsernames = false
+	defer func() {
+		setting.Service.AllowDotsInUsernames = true
+	}()
+
+	tests := []struct {
+		arg  string
+		want bool
+	}{
+		{arg: "a", want: true},
+		{arg: "abc", want: true},
+		{arg: "0.b-c", want: false},
+		{arg: "a.b-c_d", want: false},
+		{arg: ".abc", want: false},
+		{arg: "abc.", want: false},
+		{arg: "a..bc", want: false},
+		{arg: "a...bc", want: false},
+		{arg: "a.-bc", want: false},
+		{arg: "a._bc", want: false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.arg, func(t *testing.T) {
+			assert.Equalf(t, tt.want, IsValidUsername(tt.arg), "IsValidUsername[AllowDotsInUsernames=false](%v)", tt.arg)
+		})
+	}
+}
diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go
index d9bcdf3b2a..4e7fca80e2 100644
--- a/modules/web/middleware/binding.go
+++ b/modules/web/middleware/binding.go
@@ -8,6 +8,7 @@ import (
 	"reflect"
 	"strings"
 
+	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/translation"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/validation"
@@ -135,7 +136,11 @@ func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Lo
 			case validation.ErrRegexPattern:
 				data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message)
 			case validation.ErrUsername:
-				data["ErrorMsg"] = trName + l.Tr("form.username_error")
+				if setting.Service.AllowDotsInUsernames {
+					data["ErrorMsg"] = trName + l.Tr("form.username_error")
+				} else {
+					data["ErrorMsg"] = trName + l.Tr("form.username_error_no_dots")
+				}
 			case validation.ErrInvalidGroupTeamMap:
 				data["ErrorMsg"] = trName + l.Tr("form.invalid_group_team_map_error", errs[0].Message)
 			default:
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 3256d2ba91..e79a99086d 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -289,6 +289,7 @@ default_allow_create_organization = Allow Creation of Organizations by Default
 default_allow_create_organization_popup = Allow new user accounts to create organizations by default.
 default_enable_timetracking = Enable Time Tracking by Default
 default_enable_timetracking_popup = Enable time tracking for new repositories by default.
+allow_dots_in_usernames = Allow users to use dots in their usernames. Doesn't affect existing accounts.
 no_reply_address = Hidden Email Domain
 no_reply_address_helper = Domain name for users with a hidden email address. For example, the username 'joe' will be logged in Git as 'joe@noreply.example.org' if the hidden email domain is set to 'noreply.example.org'.
 password_algorithm = Password Hash Algorithm
@@ -527,6 +528,7 @@ include_error = ` must contain substring "%s".`
 glob_pattern_error = ` glob pattern is invalid: %s.`
 regex_pattern_error = ` regex pattern is invalid: %s.`
 username_error = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-'), underscore ('_') and dot ('.'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.`
+username_error_no_dots = ` can only contain alphanumeric chars ('0-9','a-z','A-Z'), dash ('-') and underscore ('_'). It cannot begin or end with non-alphanumeric chars, and consecutive non-alphanumeric chars are also forbidden.`
 invalid_group_team_map_error = ` mapping is invalid: %s`
 unknown_error = Unknown error:
 captcha_incorrect = The CAPTCHA code is incorrect.
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl
index 36d9bcb8a5..6947ecfe8e 100644
--- a/templates/admin/config.tmpl
+++ b/templates/admin/config.tmpl
@@ -159,6 +159,8 @@
 				<dd>{{if .Service.DefaultKeepEmailPrivate}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
 				<dt>{{.locale.Tr "admin.config.default_allow_create_organization"}}</dt>
 				<dd>{{if .Service.DefaultAllowCreateOrganization}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
+				<dt>{{.locale.Tr "admin.config.allow_dots_in_usernames"}}</dt>
+				<dd>{{if .Service.AllowDotsInUsernames}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
 				<dt>{{.locale.Tr "admin.config.enable_timetracking"}}</dt>
 				<dd>{{if .Service.EnableTimetracking}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
 				{{if .Service.EnableTimetracking}}