diff --git a/internal/common/common.go b/internal/common/common.go
index 39bb2ce5..a8bff346 100644
--- a/internal/common/common.go
+++ b/internal/common/common.go
@@ -170,6 +170,7 @@ func Initialize(c Configuration, isShared int) error {
 	isShuttingDown.Store(false)
 	util.SetUmask(c.Umask)
 	version.SetConfig(c.ServerVersion)
+	dataprovider.SetTZ(c.TZ)
 	Config = c
 	Config.Actions.ExecuteOn = util.RemoveDuplicates(Config.Actions.ExecuteOn, true)
 	Config.Actions.ExecuteSync = util.RemoveDuplicates(Config.Actions.ExecuteSync, true)
@@ -588,6 +589,10 @@ type Configuration struct {
 	Umask string `json:"umask" mapstructure:"umask"`
 	// Defines the server version
 	ServerVersion string `json:"server_version" mapstructure:"server_version"`
+	// TZ defines the time zone to use for the EventManager scheduler and to
+	// control time-based access restrictions. Set to "local" to use the
+	// server's local time, otherwise UTC will be used.
+	TZ string `json:"tz" mapstructure:"tz"`
 	// Metadata configuration
 	Metadata              MetadataConfig `json:"metadata" mapstructure:"metadata"`
 	idleTimeoutAsDuration time.Duration
diff --git a/internal/common/eventscheduler.go b/internal/common/eventscheduler.go
index 71315e81..762880f7 100644
--- a/internal/common/eventscheduler.go
+++ b/internal/common/eventscheduler.go
@@ -19,6 +19,8 @@ import (
 
 	"github.com/robfig/cron/v3"
 
+	"github.com/drakkan/sftpgo/v2/internal/dataprovider"
+	"github.com/drakkan/sftpgo/v2/internal/logger"
 	"github.com/drakkan/sftpgo/v2/internal/util"
 )
 
@@ -36,7 +38,15 @@ func stopEventScheduler() {
 func startEventScheduler() {
 	stopEventScheduler()
 
-	eventScheduler = cron.New(cron.WithLocation(time.UTC), cron.WithLogger(cron.DiscardLogger))
+	options := []cron.Option{
+		cron.WithLogger(cron.DiscardLogger),
+	}
+	if !dataprovider.UseLocalTime() {
+		eventManagerLog(logger.LevelDebug, "use UTC time for the scheduler")
+		options = append(options, cron.WithLocation(time.UTC))
+	}
+
+	eventScheduler = cron.New(options...)
 	eventManager.loadRules()
 	_, err := eventScheduler.AddFunc("@every 10m", eventManager.loadRules)
 	util.PanicOnError(err)
diff --git a/internal/config/config.go b/internal/config/config.go
index 431ffd08..a85c8777 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -234,6 +234,7 @@ func Init() {
 			RateLimitersConfig: []common.RateLimiterConfig{defaultRateLimiter},
 			Umask:              "",
 			ServerVersion:      "",
+			TZ:                 "",
 			Metadata: common.MetadataConfig{
 				Read: 0,
 			},
@@ -2007,6 +2008,7 @@ func setViperDefaults() {
 	viper.SetDefault("common.defender.login_delay.password_failed", globalConf.Common.DefenderConfig.LoginDelay.PasswordFailed)
 	viper.SetDefault("common.umask", globalConf.Common.Umask)
 	viper.SetDefault("common.server_version", globalConf.Common.ServerVersion)
+	viper.SetDefault("common.tz", globalConf.Common.TZ)
 	viper.SetDefault("common.metadata.read", globalConf.Common.Metadata.Read)
 	viper.SetDefault("acme.email", globalConf.ACME.Email)
 	viper.SetDefault("acme.key_type", globalConf.ACME.KeyType)
diff --git a/internal/dataprovider/dataprovider.go b/internal/dataprovider/dataprovider.go
index 6b420c82..677ee0bb 100644
--- a/internal/dataprovider/dataprovider.go
+++ b/internal/dataprovider/dataprovider.go
@@ -187,6 +187,7 @@ var (
 	ErrDuplicatedKey = errors.New("duplicated key not allowed")
 	// ErrForeignKeyViolated occurs when there is a foreign key constraint violation
 	ErrForeignKeyViolated   = errors.New("violates foreign key constraint")
+	tz                      = ""
 	isAdminCreated          atomic.Bool
 	validTLSUsernames       = []string{string(sdk.TLSUsernameNone), string(sdk.TLSUsernameCN)}
 	config                  Config
@@ -590,6 +591,16 @@ func (c *Config) doBackup() (string, error) {
 	return outputFile, nil
 }
 
+// SetTZ sets the configured timezone.
+func SetTZ(val string) {
+	tz = val
+}
+
+// UseLocalTime returns true if local time should be used instead of UTC.
+func UseLocalTime() bool {
+	return tz == "local"
+}
+
 // ExecuteBackup executes a backup
 func ExecuteBackup() (string, error) {
 	return config.doBackup()
diff --git a/internal/dataprovider/user.go b/internal/dataprovider/user.go
index 80970c26..a6a2f1ed 100644
--- a/internal/dataprovider/user.go
+++ b/internal/dataprovider/user.go
@@ -342,7 +342,11 @@ func (u *User) isTimeBasedAccessAllowed(when time.Time) bool {
 	if when.IsZero() {
 		when = time.Now()
 	}
-	when = when.UTC()
+	if UseLocalTime() {
+		when = when.Local()
+	} else {
+		when = when.UTC()
+	}
 	weekDay := when.Weekday()
 	hhMM := when.Format("15:04")
 	for _, p := range u.Filters.AccessTime {
diff --git a/sftpgo.json b/sftpgo.json
index e634fedf..d66d8951 100644
--- a/sftpgo.json
+++ b/sftpgo.json
@@ -24,6 +24,7 @@
     "allow_self_connections": 0,
     "umask": "",
     "server_version": "",
+    "tz": "",
     "metadata": {
       "read": 0
     },
diff --git a/static/locales/en/translation.json b/static/locales/en/translation.json
index 0cf02af2..25254bc8 100644
--- a/static/locales/en/translation.json
+++ b/static/locales/en/translation.json
@@ -730,7 +730,7 @@
         "external_auth_cache_time": "External auth cache time",
         "external_auth_cache_time_help": "Cache time, in seconds, for users authenticated using an external auth hook. 0 means no cache",
         "access_time": "Access time restrictions",
-        "access_time_help": "No restrictions means access is always allowed, the time must be set in the format HH:MM. Use UTC time"
+        "access_time_help": "No restrictions means access is always allowed, the time must be set in the format HH:MM"
     },
     "admin": {
         "role_permissions": "A role admin cannot have the following permissions: {{val}}",
@@ -1071,7 +1071,7 @@
         "sync_unsupported_fs_event": "Synchronous execution is only supported for upload and pre-* filesystem events",
         "only_failure_actions": "At least a non-failure action is required",
         "sync_action_required": "Event \"{{val}}\" requires at least a synchronous action",
-        "scheduler_help": "The scheduler uses UTC time. Hours: 0-23. Day of week: 0-6 (Sun-Sat). Day of month: 1-31. Month: 1-12. Asterisk (*) indicates a match for all the values of the field. e.g. every day of week, every day of month and so on",
+        "scheduler_help": "Hours: 0-23. Day of week: 0-6 (Sun-Sat). Day of month: 1-31. Month: 1-12. Asterisk (*) indicates a match for all the values of the field. e.g. every day of week, every day of month and so on",
         "concurrent_run": "Allow concurrent execution from multiple instances",
         "protocol_filters": "Protocol filters",
         "object_filters": "Object filters",
diff --git a/static/locales/it/translation.json b/static/locales/it/translation.json
index ff108b41..de6bd488 100644
--- a/static/locales/it/translation.json
+++ b/static/locales/it/translation.json
@@ -730,7 +730,7 @@
         "external_auth_cache_time": "Cache per autenticazione esterna",
         "external_auth_cache_time_help": "Tempo di memorizzazione nella cache, in secondi, per gli utenti autenticati utilizzando un hook di autenticazione esterno. 0 significa nessuna cache",
         "access_time": "Limitazioni temporali all'accesso",
-        "access_time_help": "Nessuna restrizione significa che l'accesso è sempre consentito, l'ora deve essere impostata nel formato HH:MM. Utilizzare l'ora UTC"
+        "access_time_help": "Nessuna restrizione significa che l'accesso è sempre consentito, l'ora deve essere impostata nel formato HH:MM"
     },
     "admin": {
         "role_permissions": "Un amministratore di ruolo non può avere le seguenti autorizzazioni: {{val}}",
@@ -1071,7 +1071,7 @@
         "sync_unsupported_fs_event": "L'esecuzione sincrona è supporta solo per gli eventi \"upload\" e \"pre-*\"",
         "only_failure_actions": "E' richiesta almeno un'azione che non venga eseguita su errore",
         "sync_action_required": "L'evento \"{{val}}\" richiede almeno un'azione da eseguire sincronamente",
-        "scheduler_help": "Lo scheduler utilizza l'ora UTC. Orari: 0-23. Giorno della settimana: 0-6 (dom-sab). Giorno del mese: 1-31. Mese: 1-12. L'asterisco (*) indica una corrispondenza per tutti i valori del campo. per esempio. ogni giorno della settimana, ogni giorno del mese e così via",
+        "scheduler_help": "Orari: 0-23. Giorno della settimana: 0-6 (dom-sab). Giorno del mese: 1-31. Mese: 1-12. L'asterisco (*) indica una corrispondenza per tutti i valori del campo. per esempio. ogni giorno della settimana, ogni giorno del mese e così via",
         "concurrent_run": "Consentire l'esecuzione simultanea da più istanze",
         "protocol_filters": "Filtro su protocolli",
         "object_filters": "Filtro su oggetti",