Storage AWS

SFTPGo 2.7.6 on AWS User Guide

| Product: SFTPGo 2.7.6 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the SFTPGo 2.7.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.

SFTPGo is a full featured SFTP server. Users connect with any SFTP client (FileZilla, WinSCP, Cyberduck, OpenSSH sftp, rclone, scripts) or sign in to the WebClient to browse, upload, download and share files in a web browser. Administrators manage users, groups, virtual folders, quotas, bandwidth limits, IP lists and event rules in the WebAdmin console or through the REST API. Each user's files can live on the instance's disk or on a cloud storage back end such as Amazon S3.

The cloudimg image adds a secure deployment around SFTPGo:

  • HTTPS from the first boot. nginx serves WebAdmin, WebClient and the REST API on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS. SFTP listens on port 2022.
  • A password unique to your instance. The administrator is admin; its password is generated on first boot and written to /root/sftpgo-credentials.txt. SFTPGo stores only a bcrypt hash of it. The first-run setup page is closed, so no one can claim the instance before you.
  • No default users and no shared secrets. The image holds no database, no users, no SFTP host keys and no signing keys. First boot creates the database, the administrator, the SFTP host keys (ed25519, ECDSA and RSA), the key that signs web sessions and the key that encrypts stored secrets, all unique to your instance. The host key fingerprints are in the credentials file so you can check them on the first connection.
  • Brute force protection on. SFTPGo's defender blocks an address for 30 minutes after repeated wrong logins (on SFTP, the web interfaces and the API alike), and a rate limiter caps requests per address.
  • Users are confined. Each user is jailed to their own home directory under /srv/sftpgo/data; a symbolic link or a .. path cannot reach outside it. SFTPGo runs as its own unprivileged account in a strict systemd sandbox that can write only its data directories.
  • Closed until first boot, and fail closed. SFTPGo cannot start without the per instance database and keys, and nginx ships switched off. First boot creates them, proves an SFTP and HTTPS round trip and only then opens the public listener; if anything in that sequence fails, it closes again.
  • Hardened defaults. WebAdmin and the API listen on loopback behind nginx; FTP, WebDAV and telemetry are off. SFTPGo and nginx are blocked from the instance metadata service and the AMI requires IMDSv2. nginx drops requests for host names the instance does not serve, passes SFTPGo the real client address and refuses state-changing requests a web browser sends on behalf of another site.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: SFTPGo 2.7.6 at /usr/local/bin/sftpgo, built by cloudimg from the unmodified v2.7.6 source with the patched Go 1.26.10 toolchain and security fixes in its dependencies (see Step 15), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

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 (SSH administration), TCP 2022 (SFTP), TCP 443 (WebAdmin, WebClient and the REST API) and TCP 80 (redirects to HTTPS) from your own networks only. Open 2022 and 443 wider only for the networks your users connect from.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick SFTPGo 2.7.6 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (size up for many concurrent transfers), your key pair, and a security group as above. Size the root EBS volume for the files your users will store, or attach a separate volume (Step 13).

With the AWS CLI (replace the values in angle brackets):

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/sda1,Ebs={VolumeSize=30,VolumeType=gp3}' \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=sftpgo}]'

Step 2: Connect to your instance

Version SSH user
SFTPGo 2.7.6 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

SSH administration stays on port 22 with your EC2 key pair. SFTPGo's own SFTP service is on port 2022, with its own users and host keys.

Step 3: Check the services

First boot generates the keys and the password, starts SFTPGo, opens nginx and proves a round trip before it finishes (usually under a minute):

systemctl is-active sftpgo nginx sftpgo-firstboot
sudo test -f /var/lib/cloudimg/sftpgo-firstboot.done && echo FIRSTBOOT_DONE
active
active
active
FIRSTBOOT_DONE

SSH, SFTP (2022) and nginx (80, 443) listen on public addresses; WebAdmin and the API listen on loopback only:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | grep -v '127.0.0.5' | sort -u
*:2022 users:(("sftpgo"))
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:8080 users:(("sftpgo"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))

Step 4: Get your URL and password

sudo cat /root/sftpgo-credentials.txt

The file is readable by root only:

# SFTPGo 2.7.6 - generated on first boot by sftpgo-firstboot.service
# These credentials are unique to this instance; none was baked into the image. SFTPGo stores only a bcrypt hash of
# the admin password (in /var/lib/sftpgo/sftpgo.db). The image ships NO SFTP users: create them in WebAdmin.
# Keep this file secret.

SFTPGO_WEBADMIN_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/web/admin
SFTPGO_WEBCLIENT_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/web/client
SFTPGO_ADMIN_USER=admin
SFTPGO_ADMIN_PASSWORD=<SFTPGO_ADMIN_PASSWORD>
SFTPGO_SFTP_HOST=ec2-<public-ip-dashed>.<region>.compute.amazonaws.com
SFTPGO_SFTP_PORT=2022
SFTPGO_SFTP_HOSTKEY_ED25519=SHA256:foO9fyP0SrCopPsxqtcFcmk/5O946flzP8rgIL5MJt4
SFTPGO_SFTP_HOSTKEY_ECDSA=SHA256:/r6B1gaDl7JVwkzt9dlTbV9S6f81OMyRA0CgOdZQ6hU
SFTPGO_SFTP_HOSTKEY_RSA=SHA256:yXlM3V/ue9qP2BeWCFmnN8WnTY2sDd2DHVbgoUhY4WI
#
# Sign in to WebAdmin as the admin, change the password (top right > Change password), then add SFTP users.
# Users sign in to WebClient (browser) or any SFTP client on port 2022; compare the host key fingerprint on
# first connect. The HTTPS certificate is self-signed, minted on this instance's first boot; port 80 redirects to
# https. User files live under /srv/sftpgo/data/<user>.
# Own DNS name:   sudo sftpgo-set-hostname <your-dns-name>
# User guide: https://www.cloudimg.co.uk/guides/sftpgo-2-7-6-on-ubuntu-24-04-aws/

The URLs use the instance's public DNS name from EC2 instance metadata (the public IP address when the subnet has no public DNS names). The certificate also names the public and private IP addresses, so https://<public-ip>/web/admin works too. To use your own DNS name, see Step 11.

Step 5: Sign in to WebAdmin

Open the SFTPGO_WEBADMIN_URL in your browser. The certificate is self signed, so your browser asks you to confirm it the first time (or install your own certificate, Step 11). The sign in page has no account filled in and no default credentials:

The SFTPGo WebAdmin sign in page with empty Username and Password fields and a link to the SFTPGo 2.7.6 source code

Sign in as admin with the password from Step 4. WebAdmin opens on the Users page. A new instance has no users; the one below was added for the screenshot:

The WebAdmin Users page listing one user, alice, with status Active and the time of the last login

First, change the admin password: open the person icon at the top right, choose Change password, enter the current password from Step 4 and a new one (SFTPGo rejects weak passwords). Then put the new password into SFTPGO_ADMIN_PASSWORD in /root/sftpgo-credentials.txt (sudo nano /root/sftpgo-credentials.txt), because the built in check (Step 9) signs in with it; or delete the line if you prefer not to keep the password on the instance, and the check can no longer run. In the same menu, Two-factor auth turns on a TOTP second factor for the admin account; it is a good idea for any WebAdmin reachable from the internet. Add further administrators (with roles that limit what they can manage) under Admins.

Server Manager > Status shows the SFTP service and the fingerprints of this instance's host keys, the same ones that are in the credentials file:

The WebAdmin Server status page: SSH/SFTP server active on port 2022 with the ed25519 and ECDSA host key paths and their SHA256 fingerprints

The setup page that SFTPGo shows on an empty installation (/web/admin/setup) is closed on this image: it redirects to the sign in page and refuses any attempt to create an administrator.

Step 6: Add SFTP users

In WebAdmin choose Users, then Add. Give the user a name and a password, a public key, or both (paste the user's OpenSSH public key under Public keys). Leave Root directory empty and the user's files go to /srv/sftpgo/data/<user>. Under ACLs you can limit what the user may do (for example upload only, or no delete), and under Disk quota and bandwidth limits set quotas and speed caps. Groups let you share settings between users, and Virtual folders map extra directories into a user's view.

You can do the same through the REST API. The block below, run on the instance, gets an admin token, creates a user demo that signs in with a new SSH key, uploads, lists and downloads a file over SFTP with the host key pinned, then deletes the user again. The admin password goes to curl on standard input, never on a command line:

sudo bash <<'EOF'
F=/root/sftpgo-credentials.txt
H=$(grep '^SFTPGO_SFTP_HOST=' $F | cut -d= -f2-)
api() { curl -s --cacert /etc/ssl/sftpgo/sftpgo.crt --connect-to "$H:443:127.0.0.1:443" "$@"; }
TOKEN=$(grep '^SFTPGO_ADMIN_PASSWORD=' $F | cut -d= -f2- | sed 's/^/user = "admin:/; s/$/"/' | api -K - "https://$H/api/v2/token" | python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')
W=$(mktemp -d)
ssh-keygen -q -t ed25519 -N '' -C demo -f $W/demo_key
printf '{"username":"demo","status":1,"public_keys":["%s"],"permissions":{"/":["*"]}}' "$(cat $W/demo_key.pub)" > $W/user.json
api -o /dev/null -w 'create user demo -> HTTP %{http_code}\n' -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' --data-binary @$W/user.json "https://$H/api/v2/users"
for t in ed25519 ecdsa rsa; do printf '[127.0.0.1]:2022 %s\n' "$(cut -d' ' -f1-2 /var/lib/sftpgo/keys/id_$t.pub)"; done > $W/known_hosts
echo 'hello from cloudimg' > $W/report.txt
printf 'put %s\nls -l\nget report.txt %s/back.txt\n' $W/report.txt $W > $W/batch
sftp -q -P 2022 -i $W/demo_key -o UserKnownHostsFile=$W/known_hosts -o StrictHostKeyChecking=yes -b $W/batch demo@127.0.0.1 | sed -E 's/ +[A-Z][a-z]{2} +[0-9]+ +[0-9:]+ / <date> /'
echo "downloaded: $(cat $W/back.txt)"
echo "stored at:  $(ls /srv/sftpgo/data/demo/)"
api -o /dev/null -w 'delete user demo -> HTTP %{http_code}\n' -H "Authorization: Bearer $TOKEN" -X DELETE "https://$H/api/v2/users/demo"
rm -rf "$W" /srv/sftpgo/data/demo
EOF
create user demo -> HTTP 201
sftp> put /tmp/tmp.glBophMrOp/report.txt
sftp> ls -l
-rw-r-----    1 997      987            20 <date> report.txt
sftp> get report.txt /tmp/tmp.glBophMrOp/back.txt
downloaded: hello from cloudimg
stored at:  report.txt
delete user demo -> HTTP 200

The API is documented in /usr/share/sftpgo/openapi/openapi.yaml and at https://docs.sftpgo.com/. For scripts, create an API key with the /api/v2/apikeys endpoint and allow API key authentication in the admin's Profile, instead of using the password.

Step 7: Connect with an SFTP client

Give each user the host name from SFTPGO_SFTP_HOST, port 2022, their user name and their password or private key:

Client How to connect
OpenSSH sftp -P 2022 <user>@<host> (or -i <private-key>)
FileZilla File, Site Manager: protocol SFTP, host <host>, port 2022
WinSCP File protocol SFTP, host <host>, port 2022
Cyberduck Open Connection, SFTP (SSH File Transfer Protocol), port 2022
rclone rclone config, storage type sftp, host <host>, port 2022

On the first connection the client shows the server's host key fingerprint. Compare it with the SFTPGO_SFTP_HOSTKEY_* lines of the credentials file before you accept it; this is what proves you are talking to your instance. You can also read the fingerprints straight from the running service:

sudo grep '^SFTPGO_SFTP_HOSTKEY_' /root/sftpgo-credentials.txt
for t in ed25519 ecdsa rsa; do ssh-keyscan -p 2022 -t $t 127.0.0.1 2>/dev/null | ssh-keygen -lf -; done
SFTPGO_SFTP_HOSTKEY_ED25519=SHA256:foO9fyP0SrCopPsxqtcFcmk/5O946flzP8rgIL5MJt4
SFTPGO_SFTP_HOSTKEY_ECDSA=SHA256:/r6B1gaDl7JVwkzt9dlTbV9S6f81OMyRA0CgOdZQ6hU
SFTPGO_SFTP_HOSTKEY_RSA=SHA256:yXlM3V/ue9qP2BeWCFmnN8WnTY2sDd2DHVbgoUhY4WI
256 SHA256:foO9fyP0SrCopPsxqtcFcmk/5O946flzP8rgIL5MJt4 [127.0.0.1]:2022 (ED25519)
256 SHA256:/r6B1gaDl7JVwkzt9dlTbV9S6f81OMyRA0CgOdZQ6hU [127.0.0.1]:2022 (ECDSA)
3072 SHA256:yXlM3V/ue9qP2BeWCFmnN8WnTY2sDd2DHVbgoUhY4WI [127.0.0.1]:2022 (RSA)

SFTPGo also accepts scp from OpenSSH clients and the checksum commands (sha256sum and others) that tools such as rclone use to verify transfers. Shell access is not possible: an SFTPGo user is not an operating system account.

Step 8: Use the WebClient

Users can work in a browser instead of an SFTP client. Send them the SFTPGO_WEBCLIENT_URL; they sign in with the same user name and password:

The SFTPGo WebClient sign in page with empty Username and Password fields

The Files page lists the user's home directory. Users create folders, upload files (drag and drop works), download, rename, move, copy and delete, and preview text files and images:

The WebClient Files page for a user, with the Invoices and Reports folders and a zip file, and the New Folder and Upload Files buttons

The WebClient inside the Invoices folder, showing a CSV file with its size and date

Shares creates a link to a file or folder for someone without an account, optionally with a password, an expiry date and an upload only mode. 2FA lets each user add a TOTP second factor. A user signs in to the WebClient only with a password; users with only a public key use SFTP.

Step 9: Run the built in check

The image ships an end to end check you can run at any time; first boot runs it too. It reads the credentials file itself, creates a temporary user, drives SFTP and HTTPS through the public listeners with the host key and certificate pinned, and deletes the user again:

sudo sftpgo-verify.sh
port 80 -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ ; an unknown Host is dropped on 80 and 443; /healthz answers ok; an encoded CR/LF stays encoded
admin admin: API token issued over HTTPS; SFTP service active
anonymous: every API path 401 (also with a forged X-Forwarded-For 127.0.0.1); web pages redirect to login; the setup page redirects to login and refuses a POST; cross-origin POST 403; login pages link the AGPL source
SFTP cloudimg-selftest-2026110806352118020@<private-ip>:2022 (host key pinned): put, mkdir, rename, get, byte-compare identical (262144 bytes, sha256 8d712d399138c39f4350900165386cec540f8321553c767ff1e95e790ce0b1ed)
jail: symlinks to /etc/passwd, /etc, the credentials file and a relative ../ chain are not served; '..' and absolute paths stay inside /srv/sftpgo/data/cloudimg-selftest-2026110806352118020
WebClient API cloudimg-selftest-2026110806352118020: upload 201, download byte-compare identical; symlink and '..' downloads refused
guessed admin credentials (admin/admin, admin/password, admin/sftpgo, the right password for another user, a near miss) refused (401)
defender: <private-ip> banned after repeated wrong logins (HTTPS 403 even with the right password, SFTP refused), listed, released by the admin
SFTPGO_ROUNDTRIP_OK https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ via <private-ip>: admin API, anonymous refused, setup page closed, SFTP round trip with pinned host keys, WebClient upload/download, home-directory jail, defender ban + release, throwaway user removed

The check briefly blocks a test address in the defender to prove it works and then releases it; it leaves your users and files as it found them.

Step 10: Security checks

The files that matter are locked down. The database, the keys and the configuration are readable by SFTPGo's own account only (the configuration holds no secret at all):

sudo stat -c '%a %U:%G %n' /etc/sftpgo/sftpgo.json /var/lib/sftpgo /var/lib/sftpgo/sftpgo.db /var/lib/sftpgo/secrets /var/lib/sftpgo/secrets/kms-master-key /var/lib/sftpgo/keys /var/lib/sftpgo/keys/id_ed25519 /srv/sftpgo/data /root/sftpgo-credentials.txt /etc/ssl/sftpgo/sftpgo.key
640 root:sftpgo /etc/sftpgo/sftpgo.json
750 sftpgo:sftpgo /var/lib/sftpgo
600 sftpgo:sftpgo /var/lib/sftpgo/sftpgo.db
700 sftpgo:sftpgo /var/lib/sftpgo/secrets
600 sftpgo:sftpgo /var/lib/sftpgo/secrets/kms-master-key
700 sftpgo:sftpgo /var/lib/sftpgo/keys
600 sftpgo:sftpgo /var/lib/sftpgo/keys/id_ed25519
750 sftpgo:sftpgo /srv/sftpgo/data
600 root:root /root/sftpgo-credentials.txt
600 root:root /etc/ssl/sftpgo/sftpgo.key

The configuration settings that make it safe on the internet:

sudo python3 - <<'EOF'
import json
c = json.load(open("/etc/sftpgo/sftpgo.json"))
d, r = c["common"]["defender"], c["common"]["rate_limiters"][0]
b = c["httpd"]["bindings"][0]
print("defender:", d["enabled"], "threshold", d["threshold"], "ban", d["ban_time"], "min")
print("rate limit:", r["average"], "requests/s per address,", ",".join(r["protocols"]))
print("sftp:", [x["port"] for x in c["sftpd"]["bindings"]], "keyboard-interactive", c["sftpd"]["keyboard_interactive_authentication"], "max auth tries", c["sftpd"]["max_auth_tries"])
print("web/API:", b["address"] + ":" + str(b["port"]), "behind nginx; trusted proxy", b["proxy_allowed"])
print("ftp ports:", [x["port"] for x in c["ftpd"]["bindings"]], "webdav ports:", [x["port"] for x in c["webdavd"]["bindings"]], "telemetry port:", c["telemetry"]["bind_port"])
print("default admin created:", c["data_provider"]["create_default_admin"], "| setup installation code:", repr(c["httpd"]["setup"]["installation_code"]))
print("signing key file:", c["httpd"]["signing_passphrase_file"], "| KMS master key file:", c["kms"]["secrets"]["master_key_path"])
EOF
defender: True threshold 15 ban 30 min
rate limit: 100 requests/s per address, SSH,FTP,DAV,HTTP
sftp: [2022] keyboard-interactive False max auth tries 4
web/API: 127.0.0.1:8080 behind nginx; trusted proxy ['127.0.0.1']
ftp ports: [0] webdav ports: [0] telemetry port: 0
default admin created: False | setup installation code: ''
signing key file: /var/lib/sftpgo/secrets/jwt-signing-passphrase | KMS master key file: /var/lib/sftpgo/secrets/kms-master-key

The certificate was generated on this instance and names its own addresses:

H=$(sudo grep '^SFTPGO_SFTP_HOST=' /root/sftpgo-credentials.txt | cut -d= -f2-)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
    DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost

SFTPGo runs unprivileged with no capabilities in a strict sandbox that can write only its data directories, and neither it nor nginx can reach the instance metadata service:

systemctl show sftpgo -p User -p NoNewPrivileges -p CapabilityBoundingSet -p ProtectSystem -p ReadWritePaths -p IPAddressDeny
systemctl show nginx -p IPAddressDeny
systemd-analyze security sftpgo.service | tail -1
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
CapabilityBoundingSet=
User=sftpgo
ReadWritePaths=/var/lib/sftpgo /srv/sftpgo
ProtectSystem=strict
NoNewPrivileges=yes
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
→ Overall exposure level for sftpgo.service: 1.4 OK :-)

An anonymous caller gets nothing from the API, a forged X-Forwarded-For header does not make a remote caller look local, and the port 80 redirect copies the path still percent encoded, so an encoded line break cannot add a header of its own:

H=$(sudo grep '^SFTPGO_SFTP_HOST=' /root/sftpgo-credentials.txt | cut -d= -f2-)
IP=$(hostname -I | cut -d' ' -f1)
curl -s -o /dev/null -w 'http://            -> HTTP %{http_code}\n' --connect-to "$H:80:$IP:80" "http://$H/web/admin"
curl -sk -o /dev/null -w 'API, no login      -> HTTP %{http_code}\n' --connect-to "$H:443:$IP:443" "https://$H/api/v2/users"
curl -sk -o /dev/null -w 'API, forged XFF    -> HTTP %{http_code}\n' -H 'X-Forwarded-For: 127.0.0.1' --connect-to "$H:443:$IP:443" "https://$H/api/v2/users"
curl -sk -o /dev/null -w 'first-run setup    -> HTTP %{http_code} %{redirect_url}\n' --connect-to "$H:443:$IP:443" "https://$H/web/admin/setup"
curl -sk -o /dev/null -w 'healthz            -> HTTP %{http_code}\n' --connect-to "$H:443:$IP:443" "https://$H/healthz"
N=$(curl -s -o /dev/null -D - --connect-to "$H:80:$IP:80" "http://$H/x%0d%0aX-Inj:%201" | tr -d '\r' | grep -ic '^x-inj' || true)
echo "injected headers:  $N"
http://            -> HTTP 301
API, no login      -> HTTP 401
API, forged XFF    -> HTTP 401
first-run setup    -> HTTP 302 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/web/admin/login
healthz            -> HTTP 200
injected headers:  0

No per instance secret reached a log, the journal, shell history, backups, /etc or the file store:

sudo sftpgo-secret-leak-check.sh
LEAKCHECK_OK no per-instance secret in the journal, auth.log, syslog, cloud-init, nginx logs, shell history, MOTD, /var/backups, /etc, the file store, the database, /tmp; secrets and host keys sftpgo:0600

SFTPGo logs to the journal in JSON. Every wrong login is recorded with the client address and the user name tried (never the password); sudo journalctl -u sftpgo | grep connection_failed lists them. Addresses the defender has blocked are in WebAdmin under IP Manager > Auto Block List, where you can also release one. Under IP Lists you can allow or deny networks for SFTP and the web interfaces.

Step 11: Your own DNS name and certificate

Point a DNS record at the instance, then set the name SFTPGo is served as. This mints a new self signed certificate for the name, updates the nginx host and same-origin rules and the URLs in /root/sftpgo-credentials.txt, reloads nginx and keeps the name across reboots:

sudo sftpgo-set-hostname <your-domain>

To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running sftpgo-set-hostname again replaces it with a self signed one):

sudo install -m 0644 <your-domain>.crt /etc/ssl/sftpgo/sftpgo.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/sftpgo/sftpgo.key
sudo nginx -t && sudo systemctl reload nginx

Behind an Application Load Balancer with a certificate from AWS Certificate Manager, register the instance on port 443 (HTTPS), use /healthz as the health check path, and set the instance's name to your domain with sftpgo-set-hostname. SFTP on port 2022 goes through a Network Load Balancer (TCP) or straight to the instance. If you put a proxy in front of the web interfaces, keep it the only path to the instance: nginx records the proxy's address as the client, so the defender would see every user as the proxy.

Step 12: Configuration

SFTPGo reads /etc/sftpgo/sftpgo.json (owned by root, readable by the sftpgo group). The image derives it from upstream's default configuration (kept at /usr/share/doc/sftpgo/sftpgo.json.upstream-default) and sets:

Setting Meaning Image default
sftpd.bindings SFTP listener port 2022 on all addresses
sftpd.host_keys Host keys, generated on first boot /var/lib/sftpgo/keys/id_{ed25519,ecdsa,rsa}
httpd.bindings WebAdmin, WebClient, REST API 127.0.0.1:8080, behind nginx on 443
common.defender Blocks addresses after repeated wrong logins on, threshold 15, 30 minute block
common.rate_limiters Requests per second per address 100 (burst 200)
data_provider User database SQLite /var/lib/sftpgo/sftpgo.db
data_provider.users_base_dir Default home directories /srv/sftpgo/data
data_provider.create_default_admin Built in default admin off (first boot creates admin)
ftpd, webdavd, telemetry FTP, WebDAV and metrics listeners off
kms.secrets.master_key_path Encrypts stored secrets (S3 keys, share passwords) per instance key in /var/lib/sftpgo/secrets

Most day to day settings (users, groups, folders, IP lists, event rules, SMTP for notifications, branding) are made in WebAdmin and stored in the database, not in this file. After editing the file, restart SFTPGo and run the built in check:

sudo systemctl restart sftpgo
sudo sftpgo-verify.sh

SFTPGo can also serve FTP(S) and WebDAV. They are off on this image; if you turn one on, give it TLS, open its port only to the networks that need it and add it to the security group.

Step 13: Storage

By default each user's files are in /srv/sftpgo/data/<user> on the root volume. To keep them on a separate EBS volume, stop SFTPGo, mount the volume at /srv/sftpgo/data (add it to /etc/fstab), make it 0750 sftpgo:sftpgo, copy any existing files across and start SFTPGo again.

A user's storage can instead be an Amazon S3 bucket (or Google Cloud Storage, Azure Blob Storage, another SFTP server, or an encrypted local folder): in WebAdmin, edit the user and choose the back end under File system. SFTPGo is blocked from the instance metadata service, so it cannot use the instance's IAM role; give it an access key for an IAM user that can reach only that bucket. SFTPGo stores the secret key encrypted with this instance's master key.

Step 14: Back up SFTPGo

Back up the database, the per instance keys, the configuration and the users' files together, with SFTPGo stopped for a moment so the database is consistent. Never copy the credentials file into a backup location:

sudo install -d -o root -g root -m 0700 /var/backups/sftpgo
sudo systemctl stop sftpgo
sudo bash -c 'umask 077; tar -czf /var/backups/sftpgo/sftpgo-$(date -u +%Y%m%d).tar.gz -C / var/lib/sftpgo etc/sftpgo srv/sftpgo'
sudo systemctl start sftpgo
sudo ls /var/backups/sftpgo/
sftpgo-20261108.tar.gz

Copy the archive off the instance (for example to Amazon S3), or snapshot the volumes. The archive holds the host keys, the key that encrypts stored secrets and the users' password hashes, so keep it private. To restore, stop SFTPGo, extract the archive at /, and start SFTPGo. Server Manager > Maintenance in WebAdmin also exports and imports the users, groups, folders and settings as a JSON file (without the files themselves).

Step 15: Server components and source code

Component Version Path
SFTPGo 2.7.6 (commit 62ae9ba3) /usr/local/bin/sftpgo
nginx 1.24.0 /etc/nginx/sites-available/cloudimg-sftpgo
Ubuntu 24.04 LTS
Path Purpose
/srv/sftpgo/data Users' home directories
/etc/sftpgo/sftpgo.json SFTPGo configuration (no secrets)
/var/lib/sftpgo/sftpgo.db Users, admins, settings (SQLite)
/var/lib/sftpgo/keys/ SFTP host keys (first boot)
/var/lib/sftpgo/secrets/ Web session signing key and secrets master key (first boot)
/etc/ssl/sftpgo/ HTTPS certificate and key
/root/sftpgo-credentials.txt URLs, admin password, host key fingerprints (mode 0600)
/usr/share/doc/sftpgo/ LICENSE, NOTICE, SOURCE, go.mod changes, build script
/usr/local/sbin/sftpgo-firstboot.sh First boot
/usr/local/sbin/sftpgo-set-hostname Host name and certificate
/usr/local/sbin/sftpgo-verify.sh End to end check (Step 9)
/usr/local/sbin/sftpgo-secret-leak-check.sh Proves no secret reached a log

SFTPGo is licensed under the GNU Affero General Public License v3 (AGPL-3.0). cloudimg builds the binary from the unmodified v2.7.6 source tag; the only change is that the minimum versions of some Go dependencies are raised to versions with security fixes (and the Go 1.26.10 toolchain is used). The complete corresponding source is the upstream tag plus the go.mod and go.sum shipped on the image, and the exact build is in /usr/share/doc/sftpgo/BUILD.sh:

sftpgo -v
cat /usr/share/doc/sftpgo/SOURCE
grep '^[-+]' /usr/share/doc/sftpgo/go.mod.diff | grep -v '^[-+][-+]'
SFTPGo 2.7.6-62ae9ba3-2026-09-19T07:01:05Z +metrics +azblob +gcs +s3 +bolt +mysql +pgsql +sqlite -unixcrypt +portable
SFTPGo 2.7.6 installed by cloudimg. Licence: GNU AGPL-3.0-only with the additional terms in NOTICE (section 7(b)).

Corresponding Source:
  - SFTPGo itself, unmodified: https://github.com/drakkan/sftpgo/tree/v2.7.6
    (tag archive sha256 c4e023d926ced068284ac83494e2f51bacb430fe260d3cb2d6807d3bede0608d)
  - go.mod / go.sum in this directory: upstream's, with dependency MINIMUM versions raised for security fixes only
    (go.mod.diff; go.mod.orig is upstream's); no line of SFTPGo's own code is changed.
  - BUILD.sh: the exact build (official go1.26.10, tags nopgxregisterdefaulttypes,disable_grpc_modules, CGO for
    SQLite, -trimpath). BUILDINFO.txt: the module list embedded in the binary (go version -m).
  - Dist tarball sftpgo-2.7.6-cloudimg1-linux-amd64.tar.gz sha256 7186814dc9648fc829fe726c0951e4bf9c2cfe8aa5d8714e9e7a0ddc03b831a2; binary sha256 f799c902fc218af51732e511e36b6b4f947dcf7fd238ae8bef3ef7a5f16ae570.
-   github.com/go-chi/chi/v5 v5.3.2
+   github.com/go-chi/chi/v5 v5.4.0
-   github.com/go-jose/go-jose/v4 v4.1.5
+   github.com/go-jose/go-jose/v4 v4.1.9
-   github.com/klauspost/compress v1.20.0
+   github.com/klauspost/compress v1.20.2
-   golang.org/x/crypto v0.57.0
-   golang.org/x/net v0.59.0
+   golang.org/x/crypto v0.58.0
+   golang.org/x/net v0.62.0
-   golang.org/x/sys v0.48.0
-   golang.org/x/term v0.46.0
+   golang.org/x/sys v0.49.0
+   golang.org/x/term v0.47.0
-   golang.org/x/sync v0.23.0 // indirect
-   golang.org/x/text v0.42.0 // indirect
+   golang.org/x/sync v0.24.0 // indirect
+   golang.org/x/text v0.43.0 // indirect

The WebAdmin and WebClient sign in pages link to the source, as the licence requires for software used over a network. Take new versions of SFTPGo from a new version of this listing.

Troubleshooting

  • The browser or an API client closes the connection. nginx drops requests for host names it does not serve. Use the name in the credentials file, the instance's IP addresses, or set your own name with sftpgo-set-hostname (Step 11).
  • An SFTP client times out. Check that the security group allows TCP 2022 from the client's network. Port 22 is the instance's own SSH, not SFTPGo.
  • A user (or you) is suddenly refused everywhere. The defender has blocked the address after repeated wrong logins. Wait 30 minutes, or release it in WebAdmin under IP Manager > Auto Block List from another address. To unblock your own address from the instance, sudo systemctl restart sftpgo clears the list.
  • The client warns that the host key changed. Every instance has its own host keys. If you replaced the instance, update the client's known hosts; if you did not, stop and investigate.
  • A web form is refused (403). nginx refuses state-changing requests a browser sends on behalf of another site. Open WebAdmin and WebClient directly at their own URL.
  • Nothing answers after launch. Check systemctl status sftpgo-firstboot and journalctl -u sftpgo-firstboot. Until first boot completes, SFTPGo is deliberately closed.

Security recommendations

  • Allow ports 22, 80 and 443 in the security group from your own networks only, and 2022 only from the networks your users connect from.
  • Change the admin password after the first sign in (Step 5) and turn on two-factor authentication for administrators.
  • Prefer public keys for SFTP users, give each person their own account and limit their permissions with ACLs.
  • Compare the host key fingerprint on every client's first connection (Step 7).
  • Use your own DNS name and a certificate your users' browsers trust (Step 11).
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.
  • Back up SFTPGo (Step 14).

Support and licensing

SFTPGo is open source under the GNU Affero General Public License v3 with additional attribution terms under section 7(b) of the licence (the licence text and the notice are at /usr/share/doc/sftpgo/LICENSE and /usr/share/doc/sftpgo/NOTICE; source: https://github.com/drakkan/sftpgo/tree/v2.7.6). SFTPGo's name, logo and attribution are kept as the project requires. There is no per user or per CPU licence fee; the AMI price covers cloudimg's build, hardening, first boot setup and support. SFTPGo is the work of Nicola Murino and its contributors; cloudimg is not affiliated with or endorsed by the SFTPGo project. Commercial SFTPGo editions and support from the SFTPGo authors are available at https://sftpgo.com/.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA