Restricting SSH users to SFTP-only access reduces exposure to interactive shells while still providing a convenient way to exchange files. Limiting certain accounts to file transfer service simplifies delegation for partners, automation jobs, and internal teams that do not require full shell privileges.

OpenSSH implements SFTP using the internal-sftp subsystem inside the sshd daemon, controlled by directives in /etc/ssh/sshd_config. Configuration can select specific accounts or groups using Match blocks and place them inside a chroot jail via ChrootDirectory. Within that environment, sshd exposes a restricted filesystem view and runs only the SFTP protocol without spawning a user shell.

In a chroot-based SFTP setup, the directory tree referenced by ChrootDirectory and all of its parents must be owned by root and remain non-writable for ordinary users, otherwise sshd refuses login for safety. Incorrect permissions or syntax errors in the configuration may break access for restricted accounts or prevent the daemon from starting. On typical Linux servers running OpenSSH with systemd, maintaining a backup of the current configuration and an alternative access path, such as console or out-of-band management, reduces the risk of lockouts when reloading sshd.

Steps to restrict SSH users to SFTP only:

  1. Open a terminal session on the SSH server using an account with sudo privileges.
    $ whoami
    admin
    $ id
    uid=1000(admin) gid=1000(admin) groups=1000(admin),27(sudo)
  2. Create a dedicated sftpusers group for SFTP-only accounts.
    $ sudo groupadd --system sftpusers

    Using a separate sftpusers group keeps SFTP-only policy isolated from other SSH users.

  3. Create an SFTP-only account in the sftpusers group.
    $ sudo useradd -M -s /usr/sbin/nologin -g sftpusers sftpuser1
    $ sudo passwd sftpuser1
    New password:
    Retype new password:
    passwd: password updated successfully

    The -M option skips home directory creation because the account uses a dedicated chroot under /srv/sftp.

  4. Prepare the shared SFTP chroot base directory owned by root.
    $ sudo mkdir -p /srv/sftp
    $ sudo chmod 755 /srv/sftp
    $ sudo chown root:root /srv/sftp
    $ ls -ld /srv/sftp
    drwxr-xr-x 3 root root 4096 May 13 10:00 /srv/sftp
  5. Create a per-user directory inside the chroot containing a writable upload folder.
    $ sudo mkdir -p /srv/sftp/sftpuser1/upload
    $ sudo chown root:root /srv/sftp/sftpuser1
    $ sudo chmod 755 /srv/sftp/sftpuser1
    $ sudo chown sftpuser1:sftpusers /srv/sftp/sftpuser1/upload
    $ sudo chmod 750 /srv/sftp/sftpuser1/upload
    $ ls -ld /srv/sftp/sftpuser1 /srv/sftp/sftpuser1/upload
    drwxr-xr-x 3 root      root       4096 May 13 10:02 /srv/sftp/sftpuser1
    drwxr-x--- 2 sftpuser1 sftpusers  4096 May 13 10:02 /srv/sftp/sftpuser1/upload

    If /srv/sftp or the ChrootDirectory path is owned or made writable by the SFTP user or its group, sshd rejects SFTP logins for that account.

  6. Open /etc/ssh/sshd_config with an editor using sudo.
    $ sudo nano /etc/ssh/sshd_config

    Editing /etc/ssh/sshd_config incorrectly can disrupt all SSH access; keeping a backup of the original file allows quick rollback.

  7. Configure the sftp subsystem to use internal-sftp.
    Subsystem sftp internal-sftp

    Ensure only one active Subsystem sftp line exists so sshd does not use an older external sftp-server binary.

  8. Add a Match Group block for sftpusers that enforces chroot with SFTP-only access.
    Match Group sftpusers
        ChrootDirectory /srv/sftp/%u
        ForceCommand internal-sftp
        AllowTCPForwarding no
        X11Forwarding no

    The %u token in ChrootDirectory expands to the login name so each account is jailed in its own directory under /srv/sftp.

  9. Validate the sshd configuration syntax before reloading the service.
    $ sudo sshd -t

    No output indicates that /etc/ssh/sshd_config parsed successfully.

  10. Restart the SSH service so the new SFTP-only rules take effect.
    $ sudo systemctl restart ssh

    On RHEL, CentOS, and similar systems, the service name is often sshd instead of ssh.

  11. Confirm that the SSH service is active after the restart.
    $ sudo systemctl status ssh
    ● ssh.service - OpenBSD Secure Shell server
         Loaded: loaded (/lib/systemd/system/ssh.service; enabled; vendor preset: enabled)
         Active: active (running) since Mon 2024-05-13 10:15:01 UTC; 5s ago
    ##### snipped #####
  12. Verify SFTP-only access for the restricted user from a client machine.
    $ sftp sftpuser1@server.example.com
    sftpuser1@server.example.com's password:
    Connected to server.example.com.
    sftp> pwd
    Remote working directory: /
    sftp> ls
    upload
    sftp> cd upload
    sftp> put local-file.txt
    Uploading local-file.txt to /upload/local-file.txt
    local-file.txt                                                                 100%  123KB  1.2MB/s   00:00
    sftp> exit

    Interactive ssh login for the account is blocked by the nologin shell and ForceCommand internal-sftp, while SFTP sessions succeed.

Discuss the article:

Comment anonymously. Login not required.