SFTPGo is an open-source file transfer server that exposes SFTP, FTP/S, and WebDAV with pluggable storage backends and a web admin UI, a self-hosted alternative to AWS Transfer Family without per-endpoint billing. This guide deploys SFTPGo using Docker Compose with Traefik handling automatic HTTPS for the admin UI, SQLite for the data provider, and protocol ports for SFTP, FTP/S, and WebDAV exposed on the host. By the end, you'll have SFTPGo accepting multi-protocol file transfers backed by either local disk or S3-compatible object storage.
Set Up the Directory Structure
1. Create the project directories:
$ mkdir -p ~/sftpgo/{data,config}
$ cd ~/sftpgo
2. Create the environment file:
$ nano .env
DOMAIN=sftp.example.com
LETSENCRYPT_EMAIL=admin@example.com
Deploy with Docker Compose
1. Create the Compose manifest:
$ nano docker-compose.yml
services:
traefik:
image: traefik:v3.6
container_name: traefik
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
restart: unless-stopped
sftpgo:
image: drakkan/sftpgo:v2.6
container_name: sftpgo
restart: unless-stopped
ports:
- "2022:2022" # SFTP
- "2121:2121" # FTP/S control
- "10080:10080" # WebDAV
environment:
- SFTPGO_HTTPD__BINDINGS__0__PORT=8080
- SFTPGO_HTTPD__BINDINGS__0__ADDRESS=0.0.0.0
- SFTPGO_SFTPD__BINDINGS__0__PORT=2022
- SFTPGO_FTPD__BINDINGS__0__PORT=2121
- SFTPGO_WEBDAVD__BINDINGS__0__PORT=10080
- SFTPGO_DATA_PROVIDER__DRIVER=sqlite
- SFTPGO_DATA_PROVIDER__NAME=/var/lib/sftpgo/sftpgo.db
volumes:
- ./data:/srv/sftpgo
- ./config:/var/lib/sftpgo
labels:
- "traefik.enable=true"
- "traefik.http.routers.sftpgo.rule=Host(`${DOMAIN}`)"
- "traefik.http.routers.sftpgo.entrypoints=websecure"
- "traefik.http.routers.sftpgo.tls.certresolver=letsencrypt"
- "traefik.http.services.sftpgo.loadbalancer.server.port=8080"
healthcheck:
test: ["CMD", "sftpgo", "ping", "-c", "/var/lib/sftpgo"]
interval: 30s
timeout: 10s
retries: 3
2. Start the services and view logs:
$ docker compose up -d
$ docker compose ps
$ docker compose logs
Initial Admin Setup
- Open
https://sftp.example.com/web/admin. - Create the administrator account.
- Enable two-factor authentication, scan the QR code with an authenticator app, and save the recovery codes.
Enable the Defender (Brute-Force Protection)
Edit docker-compose.yml and add to the sftpgo service environment: block:
- SFTPGO_COMMON__DEFENDER__ENABLED=true
- SFTPGO_COMMON__DEFENDER__DRIVER=memory
- SFTPGO_COMMON__DEFENDER__BAN_TIME=30
- SFTPGO_COMMON__DEFENDER__THRESHOLD=15
- SFTPGO_COMMON__DEFENDER__SCORE_INVALID=2
- SFTPGO_COMMON__DEFENDER__SCORE_VALID=1
Redeploy:
$ docker compose up -d
Trusted IPs go under IP Manager → IP Lists; auto-banned IPs appear under Auto Block List.
Create a User (Password + SSH Key)
-
Users → Add: username, password, status Active, file system Local disk, root directory
/srv/sftpgo/USERNAME. - (Optional) Generate an SSH key on a client:
$ ssh-keygen -t ed25519 -f ~/.ssh/sftpgo_key -C "sftp-user"
$ cat ~/.ssh/sftpgo_key.pub
- Users → Edit → Public keys — paste the public key.
- Test:
$ sftp -i ~/.ssh/sftpgo_key -P 2022 USERNAME@sftp.example.com
- (Optional) Require 2FA per-protocol under Edit → ACLs → Require 2FA for.
Connect S3-Compatible Object Storage
In the user's Edit → File system block:
-
File system:
S3 (Compatible) -
Bucket:
BUCKET_NAME -
Region:
REGION_CODE - Access Key / Access Secret: S3 credentials
-
Endpoint:
https://YOUR_S3_ENDPOINT - Use path-style addressing: enabled
-
Upload Part Size:
5MB · Upload Concurrency:2
Save and round-trip a test file:
$ echo "Hello SFTPGo" > testfile.txt
$ sftp -P 2022 USERNAME@sftp.example.com
sftp> put testfile.txt
sftp> ls
sftp> exit
S3 caveats: resume uploads disabled, no symlinks, non-atomic directory rename, chmod/chown ignored.
Enable FTP/S and WebDAV
1. Generate a self-signed cert for FTP/S:
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout ~/sftpgo/config/server.key \
-out ~/sftpgo/config/server.crt \
-subj "/CN=sftp.example.com"
2. Create the SFTPGo JSON config:
$ nano ~/sftpgo/config/sftpgo.json
{
"ftpd": {
"bindings": [
{
"port": 2121,
"address": "",
"tls_mode": 1,
"certificate_file": "/var/lib/sftpgo/server.crt",
"certificate_key_file": "/var/lib/sftpgo/server.key"
}
],
"passive_port_range": {"start": 50000, "end": 50100}
},
"webdavd": {
"bindings": [
{"port": 10080, "address": "", "enable_https": false}
]
}
}
3. Expose the passive port range in docker-compose.yml:
- "50000-50100:50000-50100"
4. Restart and test:
$ docker compose restart sftpgo
$ lftp -u USERNAME -p 2121 ftp://sftp.example.com
$ curl -u USERNAME:PASSWORD http://sftp.example.com:10080/
Add Quotas, Bandwidth Limits, and IP ACLs
Per-user Edit → Disk quota and bandwidth limits:
-
Quota size:
10GB(0= unlimited) · Quota files:10000 -
Bandwidth UL/DL (KB/s): e.g.
5120for 5 MB/s
Per-user ACLs:
-
Allowed IP/Mask:
192.168.1.0/24,10.0.0.0/8 - Denied IP/Mask: specific blocked addresses
- Per-directory permissions: restrict each path's operations
File Logging and Webhooks
1. Enable file logging:
$ mkdir -p ~/sftpgo/logs
Add to the sftpgo service:
volumes:
- ./logs:/var/log/sftpgo
environment:
- SFTPGO_LOG_FILE_PATH=/var/log/sftpgo/sftpgo.log
- SFTPGO_LOG_MAX_SIZE=10
- SFTPGO_LOG_MAX_BACKUPS=5
- SFTPGO_LOG_MAX_AGE=28
- SFTPGO_LOG_LEVEL=info
$ docker compose up -d
2. Set up webhook notifications:
-
Event Manager → Actions → Add: HTTP, set the server URL and
POSTmethod. - Event Manager → Rules → Add: trigger on filesystem events (e.g. Upload) and attach the webhook action.
Next Steps
SFTPGo is running with multi-protocol access and HTTPS for the admin UI. From here you can:
- Plug an external Postgres or MySQL backend by switching the data provider env vars
- Generate user API tokens and provision accounts via the REST API for migrations
- Front the SFTP port with a VPN (WireGuard) for tighter network isolation
For the full guide with additional tips, visit the original article on Vultr Docs.
Top comments (0)