SFTP Server 9.6 on AWS User Guide
Overview
This guide covers launching and using the SFTP Server 9.6 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg SFTP and file server listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
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:
- SFTP users are jailed. Every member of the
sftpusersgroup is locked (chrooted) into its own directory,/srv/sftp/<user>, on a dedicated 20 GiB EBS data volume. The jail is owned by root; the only place a user can write is itsuploaddirectory. 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 check the very first connection. - Closed if anything goes wrong. First boot proves a full SFTP round trip, the jail and every refusal, and that no key leaked into a log, before it finishes; 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 Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What's on the image
- OpenSSH 9.6p1 from Ubuntu 24.04's own package
openssh-server 1:9.6p1-3ubuntu13.20, 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, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. The SFTP account tools are in
/usr/local/sbin(Step 8).
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; no other port is needed.
- An SFTP client: the
sftpcommand, WinSCP, FileZilla or Cyberduck.
Step 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 and your Region, then Launch through EC2. Choose m5.large (SFTP needs little CPU; size for your users and transfer volume), your key pair, and a security group as above. 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; first boot grows the file system to fill it.
With the AWS CLI (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}]'
Step 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 exactly 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, because it has to query the instance metadata service for every login and sshd is blocked from it (Step 11). Use your key pair or Session Manager.
Step 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):
systemctl is-active ssh fail2ban sftp-prepare sftp-server-firstboot
sudo test -f /var/lib/cloudimg/sftp-server-firstboot.done && echo FIRSTBOOT_DONE
active
active
active
active
FIRSTBOOT_DONE
Only sshd listens on a public address, 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",...))
Step 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/cloudimg/sftp-hostname before first boot.
The first time any client connects, it shows the server's host key fingerprint and asks whether to trust it. It 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
Step 5: Try the demo user
The demo user sftpdemo exists so you can try the server straight away. Its private key, unique to your instance, is /root/sftp-demo-key. Copy it 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: 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, 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
Once 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).
Step 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). --quota 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. A private key is refused if it is passed 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).
Step 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 and 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 on the command line (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 or change 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-cloudimg-sftp-password.conf (a Match Group sftpusers block with PasswordAuthentication yes); sudo sftp-password-auth off removes it. The administrator and root stay key only either way. Moving 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 (no password hash is kept), and once no one uses passwords, run sudo sftp-password-auth off. fail2ban bans an address after 5 failed logins in 10 minutes (1 hour at first, growing to a week for repeat offenders).
Step 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.
Step 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 (OpenSSH requires this), 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.
The effective sshd settings for an SFTP user and for the administrator show the difference:
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-cloudimg-sftp.conf and apply to members of the sftpusers group only; /etc/ssh/sshd_config.d/00-cloudimg.conf keeps the server key only and refuses root logins.
Step 10: The transfer audit log
Every SFTP session and file operation is in the journal, with the client's address taken from the connection:
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.
Step 11: Security checks
fail2ban watches sshd's log and bans an address after repeated failed logins:
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: 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: 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-cloudimg-imds-deny.conf, then run sudo systemctl daemon-reload && sudo systemctl restart ssh. This gives sshd metadata access back.
Step 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 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'
Step 13: Updates
OpenSSH is Ubuntu's package, so apt and unattended-upgrades keep it patched along with the rest of the system. The installed version and where it came from:
dpkg-query -W openssh-server
cat /usr/local/share/doc/cloudimg-sftp/SOURCE.txt
After an OpenSSH update, sudo systemctl restart ssh makes the running server use it (open SFTP sessions are not interrupted).
Step 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/cloudimg-sshd.conf |
| Ubuntu | 24.04 LTS |
| Path | Purpose |
|---|---|
/etc/ssh/sshd_config.d/00-cloudimg.conf |
Key only, no root login, no X11 forwarding (for everyone) |
/etc/ssh/sshd_config.d/65-cloudimg-sftp.conf |
The SFTP jail for the sftpusers group |
/etc/ssh/sshd_config.d/66-cloudimg-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/cloudimg-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 withsftp-adduser <user> --ssh-key @<file>. Password users needsftp-password-auth on(Step 7). - "Connection refused" or a timeout after several failed logins. fail2ban has banned the address; check
sudo fail2ban-client status sshdand unban it (Step 11). Also check 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.txtbefore accepting it. - Nothing works after launch. Check
systemctl status sftp-server-firstbootandjournalctl -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
cloudimg provides support for this image by email and chat, around the clock. OpenSSH is free software under the OpenSSH licence (the original author's free-use grant, with parts under the BSD and ISC licences); the full terms are in /usr/share/doc/openssh-server/copyright. OpenSSH is a trademark of the OpenBSD project. cloudimg 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.