Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

For an SSH file-transfer account, the dependable pattern is an OpenSSH chroot: set ChrootDirectory in a user- or group-specific Match block, force the in-process SFTP server with ForceCommand internal-sftp, and keep the jail and every parent directory owned by root and unwritable by group or other users. Give the account write permission only in a child directory such as data. This changes the filesystem the SFTP session can see; it does not by itself create a complete kernel security boundary or replace SELinux, AppArmor, permissions, or service-level sandboxing.

Choose the right kind of restriction

“Restrict a user to one directory” can mean several different things. Decide whether the account needs file transfer, an interactive shell, ordinary access to the host with a few paths denied, or a daemon process confined to its own filesystem view. The mechanisms are not interchangeable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Approach Filesystem view Shell or process type Best fit Main operational cost
SFTP chroot Session root is a dedicated directory SFTP only when paired with ForceCommand internal-sftp Upload and download accounts Correct ownership of the jail hierarchy and SSH configuration testing
Interactive SSH chroot Session root is a dedicated directory Normal shell inside a constructed mini-root Legacy applications that genuinely require a shell Copying and maintaining binaries, libraries, devices, user data, and logging support
Permissions or ACLs Normal host filesystem remains visible Normal login and shell Users who need selected paths rather than a changed root Every sensitive path and inherited permission must be audited
systemd service sandbox Filesystem view is set for a service process Daemon or unit, not a general login Confining an application launched by systemd Unit-level testing and compatibility with the service

For a person who should exchange files but must not receive a normal shell, use the first option. The OpenSSH manual describes ChrootDirectory as the post-authentication root and says its default is none, meaning no chroot: sshd_config(5).

How an OpenSSH chroot works

After authentication, sshd changes the session’s apparent root to the configured path. A token such as %u is expanded to the authenticated username, so /srv/sftp/%u becomes /srv/sftp/alice for Alice. The session then starts in the account’s home directory as that path is resolved inside the jail; set the account’s home to a directory that actually exists below the chroot if you want a predictable starting location.

At session startup, OpenSSH checks every component of the chroot pathname. The components must be root-owned directories and must not be writable by group or others. The manual also warns that the hierarchy must be protected from modification by other processes, because sshd cannot detect every unsafe arrangement: Linux man-pages, sshd_config(5).

A chroot is a filesystem view, not a promise that every other subsystem is harmless. It does not automatically disable TCP forwarding, X11 forwarding, tunnelling, a separately configured SSH subsystem, a vulnerable kernel, or an incorrectly applied mandatory-access-control policy. Configure only the capabilities the account needs and verify the effective configuration on the target distribution.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build an SFTP-only account step by step

1. Plan the directory layout

Keep the chroot directory itself administrative and put user-writable material underneath it. A minimal layout is:

/srv/sftp/                    root:root  755
/srv/sftp/alice/              root:root  755   # chroot root
/srv/sftp/alice/data/         alice:alice 700  # writable area

The exact mode on a child directory can be broader when a shared workflow requires it, but the chroot root and all of its parents must continue to satisfy OpenSSH’s ownership and write checks. Do not make /srv/sftp/alice owned by Alice merely because Alice needs to upload files.

2. Create a dedicated account or group

Use a dedicated group when several accounts should receive the same SSH policy. The following example creates a login account intended for SFTP; adapt the shell path and account-management commands to your distribution.

sudo groupadd sftp-only
sudo useradd -M -d /data -s /usr/sbin/nologin -G sftp-only alice
sudo passwd alice

-M avoids creating a host-side home directory that is not used by the jail, -d /data makes the post-login location meaningful inside the chroot, and /usr/sbin/nologin prevents an ordinary shell if the account is ever tested outside the forced SFTP rule. On systems that use a different nologin path or account tools, use the local equivalent. The account still needs a usable authentication method, such as a password or an authorized key, according to your SSH policy.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

3. Create and secure the hierarchy

sudo mkdir -p /srv/sftp/alice/data
sudo chown root:root /srv /srv/sftp /srv/sftp/alice
sudo chmod 755 /srv /srv/sftp /srv/sftp/alice
sudo chown alice:alice /srv/sftp/alice/data
sudo chmod 700 /srv/sftp/alice/data

Check every component, including directories created earlier by another administrator:

namei -l /srv/sftp/alice

Look for an unexpected group- or world-writable bit, a symbolic link in the path, or ownership by a non-root account. The safety requirement applies to the complete path, not only the final directory. Also ensure that unrelated processes cannot rename, replace, or alter directories in the hierarchy; protecting the path from outside modification is part of the security model.

4. Add a scoped SSH configuration block

Edit the server’s sshd_config and place a rule for the account or group. A user-specific example is:

Match User alice
    ChrootDirectory /srv/sftp/%u
    ForceCommand internal-sftp
    X11Forwarding no
    AllowTcpForwarding no
    PermitTunnel no

For a group policy, replace Match User alice with Match Group sftp-only. Keep the block’s ordering in mind: Match changes how following directives are interpreted until another conditional block or the end of the file. Review any later rules that could change the effective result.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

ForceCommand internal-sftp is the important difference between an SFTP-only account and a user who merely happens to have a chroot configured. It forces every SSH session for the match to invoke OpenSSH’s in-process SFTP server instead of the user’s shell or a requested command. The forwarding directives remove channels that a file-transfer account normally does not need.

5. Validate before reloading

Always test syntax before touching the running daemon:

sudo sshd -t

Where supported, inspect the effective result for the exact connection context:

sudo sshd -T -C user=alice,addr=127.0.0.1,host=localhost

Confirm that the output contains the intended chrootdirectory, forced command, and forwarding settings. The -C values matter because conditional rules can depend on user, source address, and host. If validation reports an error, fix it before reloading; keep an existing administrative session open so a configuration mistake does not remove your only access.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

6. Reload and make a fresh test connection

Reload with the service manager used by your distribution. The unit may be named sshd or ssh; use the name installed on the host:

sudo systemctl reload sshd

If that unit name does not exist, use the distribution’s documented SSH service name rather than guessing. A reload normally preserves existing sessions, so test with a new connection after it completes.

From a client, open SFTP and verify the view and the allowed operation:

sftp alice@server.example
sftp> pwd
sftp> ls
sftp> put test.txt
sftp> ls data
sftp> cd ..
sftp> pwd

The SFTP client should see the chroot as /, be able to create or upload files in data, and be unable to escape above that apparent root. A separate shell test should not provide a normal prompt:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
ssh alice@server.example

With the forced internal server, the SSH client normally reports that the requested shell or command is unavailable rather than starting an interactive shell. Test both password/key authentication and any automation account path you intend to support.

7. Verify the failure cases deliberately

  • Download a file the account is meant to read and upload a new file into data.
  • Attempt to create a file directly in the chroot root; it should fail because that directory is root-owned and not writable by Alice.
  • Attempt an SSH port forward and confirm that the server refuses it.
  • Check that an account outside the Match rule still receives its intended normal SSH behavior.
  • Review authentication and SFTP logs using the host’s normal logging configuration.

Why the jail root must be root-owned

The ownership rule applies to every parent

OpenSSH checks /srv, /srv/sftp, and /srv/sftp/alice, not just the last component. If any component is group-writable, world-writable, or owned by the confined account, the session can be rejected with a chroot ownership or permissions error. This is intentional: allowing the account to modify the directory that becomes its root would let it replace the environment that the daemon trusts.

Put write access below the root

The practical pattern is a root-owned container with one or more child directories owned by the transfer account. You can create separate children for incoming files, outgoing files, or shared exchange, each with its own ownership and mode. Keep the root directory searchable so the SFTP server can enter it, but do not grant write permission there. If a workflow needs Alice to rename or delete files, grant those operations in the child directory, not on the chroot root.

Understand the %u token

Using /srv/sftp/%u lets one configuration serve multiple users, provided each username has a matching directory with the required ownership. A typo or missing directory causes login failure, so create and test each account before handing it to a user. If you use a fixed path instead, ensure that the path cannot be selected or modified by an untrusted account.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When an interactive shell inside the chroot is unavoidable

An interactive SSH chroot is substantially more work than an SFTP jail. The in-process SFTP server needs no copied shell, libraries, or runtime files; OpenSSH documents that no additional environment configuration is necessary for this mode: OpenSSH portable sshd_config documentation.

A shell session, by contrast, needs a usable mini-root. Depending on the commands allowed, that commonly includes:

  • The user’s shell binary and every shared library it loads.
  • Utilities such as directory listing, file manipulation, and authentication helpers, along with their libraries.
  • Consistent /etc data for users, groups, name resolution, and certificates required by the applications.
  • Device nodes for standard input, output, error, /dev/null, and /dev/zero; an interactive terminal also needs a tty.
  • A logging endpoint such as /dev/log when the chosen logging path requires it.

Those files must be maintained as packages and libraries change. A shell inside a chroot can still contain a vulnerable program, and a privileged process can undermine the intended isolation. If the requirement is simply to upload and download files, do not pay this maintenance cost: use internal-sftp and deny the shell instead. The OpenSSH documentation describes the additional environment requirements at openssh-portable sshd_config(5).

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When permissions or ACLs are a better answer

If the user needs a normal shell and tools but should not modify selected data, a chroot may be the wrong abstraction. Unix ownership, mode bits, and POSIX ACLs can grant or deny access to specific paths while leaving the normal filesystem visible. This is access control, not a changed filesystem root: the user may still discover other readable paths, use allowed programs, and interact with services available to ordinary accounts.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use this approach when the policy is “read these directories, write that directory, and deny this subtree,” rather than “make the rest of the host invisible.” Audit directory traversal permissions on every parent, inherited ACL entries, group membership, set-user-ID programs, and application-specific access. If a user can run arbitrary programs, filesystem permissions alone are not a general process sandbox.

When the subject is a daemon, use a service boundary

If “user” actually means a process launched by systemd, configure the unit instead of trying to turn an interactive login chroot into a service sandbox. systemd’s RootDirectory= changes the service’s filesystem root and WorkingDirectory= sets its starting directory; additional sandboxing directives can restrict filesystem exposure and capabilities. These are unit-level controls, not a replacement for an SSH account policy. See systemd.exec(5) for the directives and their interactions.

Test the service with its real dependencies: a daemon may need sockets, state directories, temporary storage, certificates, or device access that are absent from a reduced root. Keep the unit’s service account separate from human SFTP accounts so a change to one boundary does not silently alter the other.

Hardening beyond the chroot

Disable unused SSH channels

For a transfer-only account, disable X11 forwarding, TCP forwarding, and tunnelling as shown in the configuration example. Do not grant agent forwarding or a pseudo-terminal through other global or conditional settings unless a documented workflow requires them. Recheck the effective configuration with sshd -T -C after changing any related rule.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Consider mount options for a dedicated filesystem

ANSSI’s OpenSSH guidance describes a dedicated file hierarchy and discusses nodev, noexec, and nosuid mount options: ANSSI OpenSSH security technical guidance. These flags are hardening choices to assess for the workload, not universal defaults. noexec can break legitimate file-processing workflows, and other applications may require device nodes or set-user-ID behavior. Apply a flag only after confirming what the account and the filesystem actually need.

Account for mandatory access control

SELinux or AppArmor can allow or deny operations independently of the chroot. A correctly owned jail can still fail because the policy does not permit the daemon to read a directory, create a file, or access a log socket. Conversely, a permissive policy does not make a chroot stronger. Check the distribution’s audit logs and policy documentation when behavior differs from the Unix permissions you see.

Keep the host boundary in perspective

A chroot changes what a process can name as its root; it is not a complete kernel isolation mechanism. Patch the operating system and OpenSSH, minimize privileges, avoid giving the account access to set-user-ID tools or administrative helpers, and separate transfer data from sensitive host paths. Treat the jail hierarchy as security-sensitive infrastructure that must not be writable by unrelated processes.

Troubleshooting common failures

Symptom Likely cause What to check
Login fails immediately with a chroot ownership or permissions message A parent or the chroot root is not root-owned, or is writable by group or others Run namei -l /srv/sftp/alice; correct ownership and mode on every component
SFTP opens but cannot upload The writable child is missing, owned by the wrong account, or lacks write permission Check ls -ld /srv/sftp/alice/data and test the intended mode and group
The user receives a shell prompt The account did not match the conditional block, or ForceCommand internal-sftp is overridden or absent Run sshd -T -C user=alice,addr=127.0.0.1,host=localhost and inspect the effective settings
SFTP reports that the starting directory is missing The account’s home points to a path that does not exist inside the chroot Create the child path or change the account home to an existing in-jail directory
Configuration reload is rejected A spelling, placement, or unsupported directive is present Run sshd -t, fix the reported line, then reload again
File operations are denied despite correct Unix modes SELinux, AppArmor, filesystem flags, or another policy blocks the operation Review mandatory-access-control and service logs, then adjust the policy deliberately
Forwarding still works A different conditional block or global setting leaves a channel enabled Inspect the effective forwarding and tunnelling options with sshd -T -C

Deployment checklist

  • Choose SFTP chroot, an interactive mini-root, permissions/ACLs, or a systemd service boundary based on the actual requirement.
  • Make every chroot parent and the chroot directory root-owned and not writable by group or others.
  • Place all user-writable data in child directories below the chroot root.
  • Scope the SSH rule with Match User or Match Group, and use %u only when each account has a correctly prepared directory.
  • Pair a file-transfer jail with ForceCommand internal-sftp and disable forwarding features that are not needed.
  • Run sshd -t; inspect conditional results with sshd -T -C where available; then reload the correct service.
  • Test a new SFTP session, upload and download in the child directory, attempt an escape, and verify that normal SSH shell access is unavailable.
  • Review SELinux or AppArmor rules, logging, mount options, patch levels, and the possibility of unrelated processes modifying the jail hierarchy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.