Filesystem & Uploads

This chapter describes how Stylus SFTP Server manages user home directories, controls access, handles the upload lifecycle, enforces disk quotas, and integrates with downstream systems.

Home Directories

Every user receives an isolated home directory. The root path is defined by the <home-root> element in filesystem-config.xml:

<home-root>${STYLUS_SFTPSERVER_HOME}/homes/${username}</home-root>

When a user connects, the server resolves the template and places them in the resulting directory. The default path places each user under homes/<username> within the installation root.

Path Macros

The following macros are resolved at runtime:

Macro Resolves To
${STYLUS_SFTPSERVER_HOME} Server installation root directory
${STYLUS_SFTPSERVER_DATA} Writable data directory (conf, db, homes, logs)
${username} Authenticated SFTP username for the current session
${env.VAR} Value of the environment variable VAR

Relative paths in <home-root> are resolved against STYLUS_SFTPSERVER_HOME.

Directory Creation

Home directories are auto-created on first login. If the directory does not exist when the user authenticates, the server creates it automatically. You can also pre-create directories via the admin command CREATE_USER_HOME.

Users with read-write access can create subdirectories freely within their home directory.

Virtual Folders

Beyond the per-user home directory, Stylus SFTP Server can surface virtual folders — logical paths (like /releases/) mapped to physical directories on disk and granted to groups. Users see the granted folders as siblings of their home, respecting per-grant permissions (READ / WRITE / READ_WRITE). Which channels surface them is controlled per-channel by mount-routing switches in sftp-server.xml and admin-console.xml.

See the Organizations & Groups chapter for the full identity-model narrative and the corresponding admin workflow. Two rules worth flagging at the filesystem level:

Access Control

The <default-access> element in filesystem-config.xml sets the baseline access level for all users:

<default-access>read-write</default-access>
Value Effect
read-write Users can upload, download, create subdirectories, and delete files
read-only Users can only download files; uploads are rejected

Per-User Overrides

The default access level can be overridden on a per-user basis through two mechanisms:

LDAP Access Control Groups

Configure group mappings in the <ldap> block of conf/sftp-server.xml:

Configuration Effect
<access-groups> User can connect; home directory is read-write
<read-only-groups> User can connect; home directory is read-only (download only)

If a user matches both access and read-only groups, read-only wins — the most restrictive policy takes precedence. If no configured groups match (or no groups are configured), the server falls back to the <default-access> setting. Group matching is case-insensitive on the CN value.

Read-Only Rejection

When a read-only user attempts to upload a file, the server rejects the operation with SSH_FX_PERMISSION_DENIED and records an UPLOAD_REJECTED audit event.

Upload Lifecycle

Every upload goes through a controlled staging process that guarantees downstream consumers never see incomplete files:

  1. Staging write — When a client begins an upload, the server writes the data to a staging file in a centralized .staging/ directory under the data root — never in the user's home directory. The staging file is named filename.UUID.tmp. The UUID ensures that two concurrent uploads of the same filename receive distinct staging files.
  2. Isolated from users — Because staging files are in a separate directory, they never appear in any protocol's directory listing (SFTP, FTP, WebDAV, or the Web Portal). User home directories contain only user files.
  3. Atomic rename — On successful channel close, the staging file is renamed to its final name using ATOMIC_MOVE. This is a single filesystem operation — the file either appears in its entirety or not at all.
  4. Duplicate handling — If a file with the target name already exists, the server applies the configured <on-duplicate> strategy (see below).
  5. Abort cleanup — If the upload is aborted (client disconnects, error occurs), the staging file is deleted.
Important The .staging/ directory and the final target directory should reside on the same filesystem volume for ATOMIC_MOVE to work. If they are on different volumes, the server falls back to a non-atomic move and logs a warning.

Staging Guarantees

The UUID-based staging file naming means that even if two clients upload a file with the same name at the same time, each upload gets its own independent staging file. There is no contention or data corruption. The atomic rename at completion ensures that partial or corrupted files never appear in the user's directory — only fully transferred files are visible.

Rename Pattern

The <rename-pattern> element controls the final filename after a successful upload:

<rename-pattern>${basename}</rename-pattern>

Available macros:

Macro Description Example
${username} Authenticated username jdoe
${basename} Original filename as sent by the client report.csv
${filename} Filename without extension report
${ext} Extension including the dot .csv

For example, to prefix every uploaded file with the username:

<rename-pattern>${username}_${basename}</rename-pattern>

Duplicate Handling

The <on-duplicate> element controls what happens when the target filename already exists in the user's home directory:

<on-duplicate>timestamp-suffix</on-duplicate>
Option Behavior
timestamp-suffix Appends _yyyyMMddHHmmss before the file extension. For example, report.csv becomes report_20260303172633.csv. If two files arrive within the same second, a nanosecond fallback ensures unique names.
overwrite Silently replaces the existing file with the new upload.
reject Closes the SFTP channel with an error. The upload is not committed.

Staging Cleanup

On server startup, the entire .staging/ directory is wiped. Everything in it is transient — if the server is starting, no transfers are in progress. The same cleanup runs on orderly shutdown.

There is no background cleanup worker or age-based scanning. If the server crashes, leftover staging files are cleaned up automatically on the next boot.

Volume Guard

The volume guard is a server-level disk protection mechanism that prevents the storage volume from filling up. Unlike per-user quotas, the volume guard monitors the actual free space on the filesystem volume and rejects uploads when free space falls below a configurable threshold.

Configure the volume guard in filesystem-config.xml:

<volume-guard>
    <min-free-bytes>1073741824</min-free-bytes>       <!-- 1 GiB -->
    <check-interval-bytes>1073741824</check-interval-bytes> <!-- 1 GiB -->
</volume-guard>
Element Default Description
min-free-bytes 1073741824 (1 GiB) Minimum free space to maintain on the volume. Uploads are rejected when usable space falls below this threshold. Set to 0 to disable.
check-interval-bytes 1073741824 (1 GiB) How often to re-check free space during large uploads, measured in bytes written. Minimum value: 536870912 (512 MB).

How It Works

The volume guard checks free space at two points during an upload:

  1. Upload start — if the volume is already below the threshold, the upload is rejected immediately.
  2. During write — for large uploads, free space is re-checked periodically (every check-interval-bytes bytes written) to catch uploads that consume the remaining space.

Each check is a single OS system call (statfs on Linux, GetDiskFreeSpaceEx on Windows) with negligible overhead. No filesystem walks, no shared state, and no locks are involved.

Note The volume guard is a server-level protection — it applies to all users equally and protects the underlying storage volume from being completely filled. It does not enforce per-user limits. For per-user storage limits, use the disk quota feature (see below).
Tip The volume guard settings are hot-reloadable. Changes to filesystem-config.xml are detected automatically and take effect within a few seconds, with no server restart required.

Disk Quotas

Disk quotas prevent any single user from consuming excessive storage. Configure quotas in filesystem-config.xml:

<quota>
    <default-limit-bytes>1073741824</default-limit-bytes>
    <enforcement>hard</enforcement>
</quota>

The default quota is 1 GiB per user.

Per-User Override

Individual users can have custom quota limits stored in the sftp_quotas database table. Use the admin CLI commands to manage per-user quotas:

Command Description
set-quota <username> <bytes> Set a custom quota limit for a user
get-quota <username> Display the effective quota for a user
clear-quota <username> Remove the per-user override (reverts to default)

Enforcement Modes

Mode Behavior
hard When the quota is exceeded, the write is blocked. The staging file is discarded (not committed as a truncated file). An UPLOAD_REJECTED audit event is recorded, and the client receives SSH_FX_FAILURE.
soft A warning is logged but the upload proceeds to completion.

How Usage Is Calculated

Disk usage is calculated by walking all files in the user's home directory tree. Lookups are cached on a per-user basis with a baseline TTL of 300 seconds to avoid excessive filesystem scans.

Partial Upload Handling

If a quota is exceeded mid-transfer (some data was written successfully before the limit was hit), the staging file is discarded. It is never committed as a truncated file. This prevents downstream systems from processing incomplete data.

Downstream Integration

Stylus SFTP Server acts as a file-exchange hub. External systems pick up transferred files directly from user home directories on the shared filesystem.

Tip Staging files are stored in a separate .staging/ directory and never appear in user home directories. Once a file appears in a user's directory, it is fully written and safe to process.

The atomic rename guarantee means that once a file appears with its final name, it is fully written and closed. There is no window where a downstream process could read a partially written file.

Configuration Reference

All filesystem settings are in conf/filesystem-config.xml. A complete example:

<filesystem xmlns="http://www.xmlpipelineserver.com/sftp/"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://www.xmlpipelineserver.com/sftp/
                                ../docs/xsd/filesystem-config.xsd">

    <home-root>${STYLUS_SFTPSERVER_DATA}/homes/${username}</home-root>
    <default-access>read-write</default-access>

    <rename-pattern>${basename}</rename-pattern>
    <on-duplicate>timestamp-suffix</on-duplicate>
    <orphan-max-age>PT2H</orphan-max-age>

    <quota>
        <default-limit-bytes>1073741824</default-limit-bytes>
        <enforcement>hard</enforcement>
    </quota>

    <volume-guard>
        <min-free-bytes>1073741824</min-free-bytes>
        <check-interval-bytes>1073741824</check-interval-bytes>
    </volume-guard>
</filesystem>