diff --git a/docs/full-configuration.md b/docs/full-configuration.md
index e8b55ae1..2efbad5c 100644
--- a/docs/full-configuration.md
+++ b/docs/full-configuration.md
@@ -86,6 +86,7 @@ The configuration file contains the following sections:
- `max_per_host_connections`, integer. Maximum number of concurrent client connections from the same host (IP). If the defender is enabled, exceeding this limit will generate `score_limit_exceeded` events and thus hosts that repeatedly exceed the max allowed connections can be automatically blocked. 0 means unlimited. Default: `20`.
- `allowlist_status`, integer. Set to `1` to enable the allow list. The allow list can be populated using the WebAdmin or the REST API. If enabled, only the listed IPs/networks can access the configured services, all other client connections will be dropped before they even try to authenticate. Ensure to populate your allow list before enabling this setting. In multi-nodes setups, the list entries propagation between nodes may take some minutes. Default: `0`.
- `allow_self_connections`, integer. Allow users on this instance to use other users/virtual folders on this instance as storage backend. Enable this setting if you know what you are doing. Set to `1` to enable. Default: `0`.
+ - `umask`, string. Set the file mode creation mask, for example `002`. Leave blank to use the system umask. Supported on *NIX platforms. Default: blank.
- `defender`, struct containing the defender configuration. See [Defender](./defender.md) for more details.
- `enabled`, boolean. Default `false`.
- `driver`, string. Supported drivers are `memory` and `provider`. The `provider` driver will use the configured data provider to store defender events and it is supported for `MySQL`, `PostgreSQL` and `CockroachDB` data providers. Using the `provider` driver you can share the defender events among multiple SFTPGO instances. For a single instance the `memory` driver will be much faster. Default: `memory`.
diff --git a/internal/common/common.go b/internal/common/common.go
index 22f85a33..6100afab 100644
--- a/internal/common/common.go
+++ b/internal/common/common.go
@@ -166,6 +166,7 @@ var (
// Initialize sets the common configuration
func Initialize(c Configuration, isShared int) error {
isShuttingDown.Store(false)
+ util.SetUmask(c.Umask)
Config = c
Config.Actions.ExecuteOn = util.RemoveDuplicates(Config.Actions.ExecuteOn, true)
Config.Actions.ExecuteSync = util.RemoveDuplicates(Config.Actions.ExecuteSync, true)
@@ -569,7 +570,9 @@ type Configuration struct {
// Defender configuration
DefenderConfig DefenderConfig `json:"defender" mapstructure:"defender"`
// Rate limiter configurations
- RateLimitersConfig []RateLimiterConfig `json:"rate_limiters" mapstructure:"rate_limiters"`
+ RateLimitersConfig []RateLimiterConfig `json:"rate_limiters" mapstructure:"rate_limiters"`
+ // Umask for new uploads. Leave blank to use the system default.
+ Umask string `json:"umask" mapstructure:"umask"`
idleTimeoutAsDuration time.Duration
idleLoginTimeout time.Duration
defender Defender
diff --git a/internal/util/util_fallback.go b/internal/util/util_fallback.go
new file mode 100644
index 00000000..17b4e74d
--- /dev/null
+++ b/internal/util/util_fallback.go
@@ -0,0 +1,31 @@
+// Copyright (C) 2019-2023 Nicola Murino
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+//go:build !unix
+
+package util
+
+import (
+ "runtime"
+
+ "github.com/drakkan/sftpgo/v2/internal/logger"
+)
+
+// SetUmask sets the specified umask
+func SetUmask(val string) {
+ if val == "" {
+ return
+ }
+ logger.Debug(logSender, "", "umask not supported on OS %q", runtime.GOOS)
+}
diff --git a/internal/util/util_unix.go b/internal/util/util_unix.go
new file mode 100644
index 00000000..bebad4d4
--- /dev/null
+++ b/internal/util/util_unix.go
@@ -0,0 +1,38 @@
+// Copyright (C) 2019-2023 Nicola Murino
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published
+// by the Free Software Foundation, version 3.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+//go:build unix
+
+package util
+
+import (
+ "strconv"
+ "syscall"
+
+ "github.com/drakkan/sftpgo/v2/internal/logger"
+)
+
+// SetUmask sets the specified umask
+func SetUmask(val string) {
+ if val == "" {
+ return
+ }
+ umask, err := strconv.ParseUint(val, 8, 31)
+ if err != nil {
+ logger.Error(logSender, "", "invalid umask %q: %v", val, err)
+ return
+ }
+ logger.Debug(logSender, "", "set umask to: %d, configured value: %q", umask, val)
+ syscall.Umask(int(umask))
+}
diff --git a/sftpgo.json b/sftpgo.json
index 84cca696..064b7fe5 100644
--- a/sftpgo.json
+++ b/sftpgo.json
@@ -21,6 +21,7 @@
"max_per_host_connections": 20,
"allowlist_status": 0,
"allow_self_connections": 0,
+ "umask": "",
"defender": {
"enabled": false,
"driver": "memory",