Deployment guide

SFTP Server 9.6

Overview

SFTP (the SSH File Transfer Protocol) moves files over an encrypted SSH connection on port 22. Every operating system has a client (sftp, WinSCP, FileZilla, Cyberduck), and so do most backup, ETL and B2B tools.

This image is a plain, well-understood SFTP server: the OpenSSH server that ships with Ubuntu, set up so that you can hand accounts to partners and systems safely.

This guide covers the SFTP Server 9.6 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud SFTP and file server listing on AWS Marketplace, and you choose it as the software version when you subscribe and launch.

What you get:

  • SFTP users are jailed. Every member of the sftpusers group is locked (chrooted) into its own directory, /srv/sftp/<user>, on a dedicated 20 GiB EBS data volume. The jail is owned by root, and the only place a user can write is its upload directory. SFTP users get no shell, no terminal and no port, agent, X11 or socket forwarding, so an SFTP account can move files and nothing else.
  • Key only by default. Nobody can log in with a password on a fresh instance. You can switch on password logins for SFTP users only with one command. The administrator account (ubuntu) and root are never offered a password.
  • Unique per instance. The image contains no SSH host key, no user and no key. On first boot each instance gets its own SSH host keys and a demo SFTP user, sftpdemo, with its own ed25519 key. The host key fingerprints are written to a root-only file, so you can verify the very first connection.
  • Closed if anything goes wrong. Before it finishes, first boot proves a full SFTP round trip, the jail and every refusal, and that no key leaked into a log. If any check fails, the demo user and its key are removed again.
  • Per-user quotas and an audit log that works. Quotas are enforced on the data volume. Every SFTP session and file operation (open, read, write, rename, remove) is written to the system journal with the client's address. OpenSSH cannot log from inside a jail on its own, so the image places the journal's socket inside each jail for you.
  • Hardened service. fail2ban bans addresses that keep failing to log in. sshd and fail2ban are blocked from the instance metadata service, and the AMI requires IMDSv2.
  • Fully patched base. Ubuntu 24.04 LTS, patched at build time, with unattended security upgrades switched on.
  • OpenSSH 9.6p1 from Ubuntu 24.04's own package, openssh-server 1:9.6p1-3ubuntu13.20. It is installed from the Ubuntu archive (noble-security), so Ubuntu keeps patching it and unattended-upgrades applies its fixes. "9.6" is the upstream release Ubuntu 24.04 maintains. This build carries every Ubuntu OpenSSH security update to date, including USN-6859-1 (CVE-2024-6387, "regreSSHion"), USN-6887-1 (CVE-2024-39894), USN-7270-1 (CVE-2025-26465, CVE-2025-26466), USN-7457-1 (CVE-2025-32728), USN-8090-1, USN-8222-1, USN-8533-1, USN-8721-1 and USN-8928-1 (October 2026). When the image was built, Ubuntu's CVE tracker listed no open OpenSSH issue for Ubuntu 24.04.
  • fail2ban, the quota tools, cloud-init and the AWS Systems Manager agent. The SFTP account tools are in /usr/local/sbin (Step 8).
  • 24/7 SecureAnyCloud support

Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 from your administrators' and your SFTP users' networks only. SFTP and administration share port 22, so no other port is needed.
  • An SFTP client: the sftp command, WinSCP, FileZilla or Cyberduck.
1

Launch the instance

In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick SFTP Server 9.6 on Ubuntu 24.04 as the software version, pick your Region, then choose Launch through EC2.

Choose m5.large, your key pair and a security group as described above. SFTP needs little CPU, so size the instance for your users and transfer volume.

The AMI carries two volumes: the 8 GiB root volume and a 20 GiB data volume for /srv/sftp. You can make the data volume larger at launch, and first boot grows the file system to fill it.

To launch with the AWS CLI instead, replace the values in angle brackets. The second mapping sets the data volume size:

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --block-device-mappings 'DeviceName=/dev/sdf,Ebs={VolumeSize=100,VolumeType=gp3}' \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=sftp-server}]'
2

Connect to your instance

Version SSH user

SFTP Server 9.6 on Ubuntu 24.04 ubuntu

ssh -i <your-key.pem> ubuntu@<public-ip>

The administrator account works like any Ubuntu instance on EC2: key pair login, sudo and AWS Systems Manager Session Manager.

EC2 Instance Connect is switched off on this image. It queries the instance metadata service for every login, and sshd is blocked from that service (Step 11). Use your key pair or Session Manager.

3

Check the services

First boot creates the demo user and proves an SFTP round trip before it finishes, a few seconds after SSH answers. Check the services:

systemctl is-active ssh fail2ban sftp-prepare sftp-server-firstboot
sudo test -f /var/lib/sac/data/sftp-server-firstboot.done && echo FIRSTBOOT_DONE
active active active active FIRSTBOOT_DONE

Only sshd listens on a public address, and it listens on port 22:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0' | sort
0.0.0.0:22 users:(("sshd",...)) [::]:22 users:(("sshd",...))
4

Connection details and host key fingerprints

sudo grep -vE '^(#|$)' /root/sftp-server-info.txt

The file is readable by root only:

SFTP_HOST=ec2-<public-ip-dashed>.compute-1.amazonaws.com SFTP_PORT=22 SFTP_DEMO_USER=sftpdemo SFTP_DEMO_KEY=/root/sftp-demo-key SFTP_DEMO_KEY_FINGERPRINT=SHA256:<demo-key-fingerprint> SFTP_DEMO_JAIL=/srv/sftp/sftpdemo SFTP_PASSWORD_AUTH=off HOST_KEY_ED25519_SHA256=SHA256:<this-instance's-ed25519-host-key> HOST_KEY_ECDSA_SHA256=SHA256:<this-instance's-ecdsa-host-key> HOST_KEY_RSA_SHA256=SHA256:<this-instance's-rsa-host-key>

SFTP_HOST is the instance's public DNS name from EC2 instance metadata (the public or private IP address when the subnet has no public DNS names). To print a different name there, put it in /etc/sac/sftp-hostname before first boot.

The first time a client connects, it shows the server's host key fingerprint and asks whether to trust it. The fingerprint must equal one of the HOST_KEY_* values. If it does not, do not connect. You can print the fingerprints at any time:

for t in ed25519 ecdsa rsa; do ssh-keygen -l -E sha256 -f /etc/ssh/ssh_host_${t}_key.pub; done
5

Try the demo user

The demo user sftpdemo lets you try the server straight away. Its private key is unique to your instance and is stored at /root/sftp-demo-key. Copy the key to your own computer over your administrator SSH session, then connect:

ssh -i <your-key.pem> ubuntu@<public-ip> 'sudo cat /root/sftp-demo-key' > sftp-demo-key chmod 600 sftp-demo-key sftp -i sftp-demo-key sftpdemo@<public-ip> sftp> cd upload sftp> put report.csv sftp> ls -l sftp> bye

The built-in check runs the same kind of session on the instance itself. It tests an upload and a byte-for-byte download, the audit log entry, the jail, and the refusals: no shell, no terminal, no forwarding, no root login, and no password while passwords are off.

sudo sftp-verify.sh | tail -6
ok remote forwarding (-R) refused by the server (rc 255) ok root login refused (Permission denied) ok the demo key does not open the administrator account ok password authentication not offered to sftpdemo (key only: 'Permission denied (publickey)') SFTP_ROUNDTRIP_OK target 172.31.x.x

When your own users exist, remove the demo user and its key:sudo sftp-deluser sftpdemo --yes && sudo shred -u /root/sftp-demo-key /root/sftp-demo-key.pub After that, sftp-verify.sh has no account to test with.

6

Add a user with an SSH key (recommended)

Ask the user for their public key (the .pub file) and add it with --ssh-key @<file>, or paste the key line in quotes. The --quota option caps the user's storage.

This example makes a key pair on the instance only to show the whole flow. In practice, the user keeps the private key and sends you the public one:

ssh-keygen -q -t ed25519 -N '' -C bob -f /tmp/bob_key
sudo sftp-adduser bob --ssh-key @/tmp/bob_key.pub --quota 10G
SFTP user "bob" ready. jail (chroot) : /srv/sftp/bob writable: /srv/sftp/bob/upload ssh key : added to /etc/ssh/authorized_keys.d/bob quota : 10G

The key is stored in /etc/ssh/authorized_keys.d/bob, owned by root and outside the jail, with OpenSSH's restrict option. The tool refuses a private key if you pass one by mistake. Bob can now upload and download:

printf 'put /etc/hostname upload/hello.txt\nls -l upload\n' > /tmp/bob.batch sftp -i /tmp/bob_key -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/tmp/bob_known_hosts -b /tmp/bob.batch bob@127.0.0.1
sftp> put /etc/hostname upload/hello.txt sftp> ls -l upload -rw-r----- ? 1002 1002 16 Oct 24 06:10 upload/hello.txt

New files are created with mode 0640 (owner read-write, group read).

7

Password logins for SFTP users (optional)

Passwords are off on a fresh instance. Creating a user with --password generate switches password logins on for the sftpusers group only. The command prints a new 24-character password once, so store it straight away:

sudo sftp-adduser alice --password generate --quota 5G Note: SFTP password authentication is now ON for the sftpusers group (the administrator stays key only). SFTP user "alice" ready. jail (chroot) : /srv/sftp/alice writable: /srv/sftp/alice/upload quota : 5G GENERATED PASSWORD for alice (shown once, store it now): <24-character password>

To choose the password yourself, pass it on standard input. Never put it on the command line, because a command line ends up in your shell history and in sudo's log:

read -rs PW && printf '%s\n' "$PW" | sudo sftp-adduser carol --password-stdin; unset PW

Check the setting at any time:

sudo sftp-password-auth status
SFTP password authentication: OFF (key only)

sudo sftp-password-auth on writes /etc/ssh/sshd_config.d/66-sac-sftp-password.conf (a Match Group sftpusers block with PasswordAuthentication yes), and sudo sftp-password-auth off removes it. The administrator and root stay key only either way.

To move a password user to keys: add their public key with sudo sftp-adduser <user> --ssh-key @<user>.pub. Lock the password with sudo sftp-passwd <user> --lock, which keeps no password hash. When no one uses passwords any more, run sudo sftp-password-auth off.

fail2ban bans an address after 5 failed logins in 10 minutes. The first ban lasts 1 hour and grows to a week for repeat offenders.

8

Manage users

sudo sftp-listusers
USER PASSWORD KEYS QUOTA USED JAIL bob no 1 10240M 4.0K /srv/sftp/bob sftpdemo no 1 5120M 4.0K /srv/sftp/sftpdemo

Command Purpose

sftp-adduser <user> [--ssh-key @<file>] [--password generate \| --password-stdin] [--quota 10G] Create a user, or add a key, password or quota to one

sftp-passwd <user> generate \| --password-stdin \| --lock New password (printed once or read from standard input), or lock it

sftp-deluser <user> [--yes \| --keep-data] Remove a user; --yes also deletes /srv/sftp/<user>

sftp-listusers [--json] Users, password set, number of keys, quota, usage

sftp-password-auth on \| off \| status SFTP password logins for the sftpusers group

sftp-verify.sh, sftp-healthcheck The built-in end-to-end check and a quick health check

All of them need sudo. They refuse to touch an account that is not an SFTP user.

9

How the jail works

sudo ls -la /srv/sftp/bob
drwxr-xr-x 4 root root 4096 Oct 24 06:10 . drwxr-xr-x 5 root root 4096 Oct 24 06:10 .. drwxr-xr-x 2 root root 4096 Oct 24 06:10 dev drwxr-x--- 2 bob sftpusers 4096 Oct 24 06:10 upload

The jail root belongs to root, as OpenSSH requires, so the user cannot change it. upload is the user's own directory. dev holds only the socket that carries the audit log to the journal (Step 10).

From the user's side, / is the jail, .. leads nowhere and system files do not exist:

printf 'pwd\ncd ..\npwd\nls /\n' > /tmp/bob.batch2 sftp -i /tmp/bob_key -o UserKnownHostsFile=/tmp/bob_known_hosts -b /tmp/bob.batch2 bob@127.0.0.1
sftp> pwd Remote working directory: / sftp> cd .. sftp> pwd Remote working directory: / sftp> ls / /dev /upload

An SFTP account cannot run commands either:

ssh -i /tmp/bob_key -o BatchMode=yes -o UserKnownHostsFile=/tmp/bob_known_hosts bob@127.0.0.1 id || true
This service allows sftp connections only.

Compare the effective sshd settings for an SFTP user and for the administrator:

sudo sshd -T -C user=bob,host=client,addr=198.51.100.7 | grep -E '^(forcecommand|chrootdirectory|allowtcpforwarding|disableforwarding|permittty|authorizedkeysfile) '
sudo sshd -T -C user=ubuntu,host=client,addr=198.51.100.7 | grep -E '^(passwordauthentication|permitrootlogin|forcecommand) '
permittty no allowtcpforwarding no disableforwarding yes forcecommand internal-sftp -u 0027 -l INFO chrootdirectory /srv/sftp/%u authorizedkeysfile /etc/ssh/authorized_keys.d/%u permitrootlogin no passwordauthentication no forcecommand none

The SFTP settings live in /etc/ssh/sshd_config.d/65-sac-sftp.conf and apply to members of the sftpusers group only. /etc/ssh/sshd_config.d/00-sac.conf keeps the server key only and refuses root logins.

10

The transfer audit log

Every SFTP session and file operation is in the journal, with the client's address taken from the connection. Read the latest entries:

sudo journalctl -t internal-sftp --no-pager -n 6 -o cat
session opened for local user bob from [127.0.0.1] open "/upload/hello.txt" flags WRITE,CREATE,TRUNCATE mode 0644 close "/upload/hello.txt" bytes read 0 written 16 opendir "/upload/" closedir "/upload/" session closed for local user bob from [127.0.0.1]

OpenSSH writes these lines from inside the jail, where there is normally no log socket, so they would be lost. The image binds the journal's socket into each jail as dev/log at every boot (sftp-prepare.service) and whenever sftp-adduser creates a user.

Logins and refusals are in sshd's own log: sudo journalctl -u ssh.

11

Security checks

fail2ban watches sshd's log and bans an address after repeated failed logins. Check its status:

sudo fail2ban-client status sshd
Status for the jail: sshd |- Filter | |- Currently failed: 0 | |- Total failed: 0 | `- Journal matches: _SYSTEMD_UNIT=ssh.service + _COMM=sshd `- Actions |- Currently banned: 0 |- Total banned: 0 `- Banned IP list:

To lift a ban, run sudo fail2ban-client set sshd unbanip <address>.

The image points fail2ban at ssh.service, the unit sshd actually runs as. Ubuntu's stock filter watches sshd.service and would never ban anything.

sshd and fail2ban are blocked from the instance metadata service (and the Azure wire server address). SFTP users cannot reach it either, because they cannot run commands or forward connections.

systemctl show -p IPAddressDeny ssh fail2ban
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128

Because of this block, EC2 Instance Connect is switched off (/etc/systemd/system/ssh.service.d/ec2-instance-connect.conf). If you need it, delete that file and /etc/systemd/system/ssh.service.d/50-sac-imds-deny.conf, then run sudo systemctl daemon-reload && sudo systemctl restart ssh. This restores sshd's access to the metadata service.

12

The data volume and quotas

df -h /srv/sftp findmnt -no OPTIONS /srv/sftp
Filesystem Size Used Avail Use% Mounted on /dev/nvme1n1 20G 56K 19G 1% /srv/sftp rw,nosuid,nodev,noexec,relatime,quota,usrquota,grpquota

/srv/sftp is its own EBS volume (ext4, mounted by UUID with nodev,nosuid,noexec), so you can snapshot, resize or move it on its own.

To grow it, increase the volume size in EC2. At the next boot sftp-prepare.service grows the file system, or you can run sudo resize2fs $(findmnt -no SOURCE /srv/sftp) straight away.

Quotas are enforced by the usrquota,grpquota mount options. An upload that would exceed a user's quota is refused with "Disk quota exceeded". See each user's usage and limit (in KiB):

sudo repquota -u /srv/sftp | sed -n '1,3p;/bob\|sftpdemo/p'
13

Updates

OpenSSH is Ubuntu's package, so apt and unattended-upgrades keep it patched along with the rest of the system. Check the installed version and where it came from:

dpkg-query -W openssh-server cat /usr/local/share/doc/sac-sftp/SOURCE.txt

After an OpenSSH update, run sudo systemctl restart ssh so that the running server uses it. Open SFTP sessions are not interrupted.

14

Server components

Component Version Path

OpenSSH server (sshd, internal-sftp) 9.6p1, Ubuntu 1:9.6p1-3ubuntu13.20 (updated by apt) /usr/sbin/sshd

fail2ban Ubuntu 24.04 package /etc/fail2ban/jail.d/sac-sshd.conf

Ubuntu 24.04 LTS

Path Purpose

/etc/ssh/sshd_config.d/00-sac.conf Key only, no root login, no X11 forwarding (for everyone)

/etc/ssh/sshd_config.d/65-sac-sftp.conf The SFTP jail for the sftpusers group

/etc/ssh/sshd_config.d/66-sac-sftp-password.conf Present only while SFTP password logins are on

/etc/ssh/authorized_keys.d/<user> SFTP users' public keys

/srv/sftp/<user> Each user's jail; upload/ is writable

/root/sftp-server-info.txt Connection details and host key fingerprints (mode 0600)

/root/sftp-demo-key The demo user's private key (mode 0600)

/usr/local/sbin/sftp-* Account tools, checks and the per-boot preparation

/usr/local/share/doc/sac-sftp/SOURCE.txt Version and source notes

Troubleshooting

  • "Permission denied (publickey)" for an SFTP user. The key is not in /etc/ssh/authorized_keys.d/<user>, or the user is using the wrong private key. Add the public key with sftp-adduser <user> --ssh-key @<file>. Password users need sftp-password-auth on (Step 7).
  • "Connection refused" or a timeout after several failed logins. fail2ban has banned the address. Check sudo fail2ban-client status sshd and unban it (Step 11). Also check that the security group allows TCP 22 from that address.
  • "Couldn't write to remote file ... Permission denied". The user tried to write outside upload/. The jail root is read-only by design.
  • "Disk quota exceeded". Raise the quota with sudo sftp-adduser <user> --quota <size> or remove files.
  • The client warns that the host key changed. A new instance always has new host keys. Compare the fingerprint with /root/sftp-server-info.txt before accepting it.
  • Nothing works after launch. Check systemctl status sftp-server-firstboot and journalctl -u sftp-server-firstboot.

Security recommendations

  • Allow TCP 22 in the security group only from the networks your users and administrators use.
  • Prefer SSH keys. If you turn passwords on, use the generated passwords and turn them off again when they are no longer needed.
  • Remove the demo user and its key once your own users exist (Step 5).
  • Keep unattended upgrades on, and restart sshd after OpenSSH updates.
  • Send the journal (journalctl -t internal-sftp, journalctl -u ssh) to your log service if you need to keep an audit trail beyond the instance.

Support and licensing

SecureAnyCloud provides 24/7 support for this image by email and chat.

OpenSSH is free software under the OpenSSH license: the original author's free-use grant, with parts under the BSD and ISC licenses. The full terms are in /usr/share/doc/openssh-server/copyright.

OpenSSH is a trademark of the OpenBSD project. SecureAnyCloud is not affiliated with, endorsed by or sponsored by the OpenBSD or OpenSSH projects. The name only identifies the open source software in this image.

Deploy on AWS

Launch SFTP Server 9.6 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS