DEV Community

Cover image for Deploying SFTPGo as an Azure Storage SFTP Alternative on Linux
Sanskriti Harmukh for Vultr

Posted on with Aashish Chaurasiya • Originally published at docs.vultr.com

Deploying SFTPGo as an Azure Storage SFTP Alternative on Linux

Azure Storage SFTP is Microsoft's managed file transfer service on top of Azure Blob Storage, convenient, but billed continuously per endpoint (roughly $0.30/hour, ~$220/month) and tied to Azure AD. SFTPGo is an open-source file transfer server offering SFTP, FTP/S, and WebDAV with pluggable storage backends (local disk, Azure Blob, S3-compatible, GCS) and no per-endpoint charge. This guide deploys SFTPGo with Docker Compose and Traefik, sets up user auth (password + SSH key + 2FA), connects S3-compatible object storage, and covers the migration path from Azure Storage SFTP. By the end, you'll have a self-hosted file transfer server with the same capabilities at zero endpoint cost.

Azure Storage SFTP → SFTPGo Mapping

Azure Storage SFTP SFTPGo Equivalent Notes
SFTP Endpoint SFTPGo SFTP Server Configurable port, default 2022
Azure Blob Storage Azure Blob backend Native support; point at the same container, no migration needed
Azure AD Authentication LDAP/OIDC plugin External identity provider via plugin
Local Users Web UI / REST API user management
Hierarchical Namespace Virtual directories No HNS requirement
Azure Monitor Built-in logging + webhooks/syslog

Prerequisite: Linux server with Docker + Compose, a DNS A record for your domain, and (if migrating) an existing Azure Storage account with SFTP enabled plus the Azure CLI installed locally.


Deploy with Docker Compose

1. Create the project directories:

$ mkdir -p ~/sftpgo/{data,config}
$ cd ~/sftpgo
Enter fullscreen mode Exit fullscreen mode

2. Create the environment file:

$ nano .env
Enter fullscreen mode Exit fullscreen mode
DOMAIN=sftp.example.com
LETSENCRYPT_EMAIL=admin@example.com
Enter fullscreen mode Exit fullscreen mode

3. Create the Compose manifest:

$ nano docker-compose.yml
Enter fullscreen mode Exit fullscreen mode
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.7
    container_name: sftpgo
    restart: unless-stopped
    ports:
      - "2022:2022"
      - "2121:2121"
      - "10080:10080"
    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

volumes:
  letsencrypt:
Enter fullscreen mode Exit fullscreen mode

Traefik terminates TLS on 80/443 for the admin UI; SFTP/FTP/S/WebDAV listen directly on 2022/2121/10080. Port 8080 (the admin UI's internal port) is never exposed to the host — Traefik proxies to it over the Docker network.

4. Start the stack:

$ docker compose up -d
$ docker compose ps
$ docker compose logs
Enter fullscreen mode Exit fullscreen mode

Initial Admin Setup

  1. Open https://sftp.example.com/web/admin.
  2. Create the administrator: username, strong password, confirm, Create admin and Sign in.
  3. Review Server Manager → Configurations — check the SFTP, ACME, and SMTP sections match your requirements.

Create a User and Configure Authentication

1. Password auth: Users → Add — username, password, Status: Active, File system: Local disk, Root directory: /srv/sftpgo/USERNAME. Save.

2. SSH key auth (passwordless, mirrors Azure's SSH key support):

$ ssh-keygen -t ed25519 -f ~/.ssh/sftpgo_key -C "sftp-user"
$ cat ~/.ssh/sftpgo_key.pub
Enter fullscreen mode Exit fullscreen mode

Paste the public key into Users → Edit → Public keys, then test:

$ sftp -i ~/.ssh/sftpgo_key -P 2022 USERNAME@sftp.example.com
Enter fullscreen mode Exit fullscreen mode

3. 2FA: Users → Edit → ACLs → Require 2FA for — select the protocols to gate. The user sets up their authenticator app on next WebClient login.


Connect S3-Compatible Object Storage

SFTPGo supports Azure Blob directly, but also any S3-compatible provider — useful if you're consolidating storage away from Azure.

  1. Gather your endpoint URL, access key, secret key, and bucket name from the provider.
  2. Users → Edit → File system → Storage: S3 (Compatible).
  3. Fill in Bucket, Region, Access Key, Access Secret, Endpoint (full URL).
  4. Enable Use path-style addressing.
  5. Save, then verify:
$ echo "Hello SFTPGo" > testfile.txt
$ sftp -P 2022 USERNAME@sftp.example.com
sftp> put testfile.txt
sftp> ls
sftp> exit
Enter fullscreen mode Exit fullscreen mode

S3 backend limitations: resume uploads disabled, no symlinks, non-atomic directory rename, chmod/chown silently ignored.


Test All Protocols

$ echo "Hello SFTPGo" > testfile.txt
$ sftp -P 2022 USERNAME@sftp.example.com
sftp> put testfile.txt
sftp> get testfile.txt /tmp/testfile-downloaded.txt
sftp> exit

$ sftp -i ~/.ssh/sftpgo_key -P 2022 USERNAME@sftp.example.com

$ sudo apt install lftp -y
$ lftp -u USERNAME -p 2121 ftp://sftp.example.com
lftp> ls
lftp> exit
Enter fullscreen mode Exit fullscreen mode

Migrate from Azure Storage SFTP

Export Users

$ az storage account local-user list --account-name STORAGE_ACCOUNT --resource-group RESOURCE_GROUP
$ az storage account local-user show --account-name STORAGE_ACCOUNT --resource-group RESOURCE_GROUP --name USERNAME
Enter fullscreen mode Exit fullscreen mode

Record username, SSH public keys, home directory (container + path), and permission scope for each user.

Recreate Users in SFTPGo

$ curl -u ADMIN_USERNAME:ADMIN_PASSWORD https://sftp.example.com/api/v2/token
Enter fullscreen mode Exit fullscreen mode

Copy the access_token — it expires in 15 minutes.

$ curl -X POST "https://sftp.example.com/api/v2/users" \
    -H "Authorization: Bearer ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "username": "USERNAME",
      "password": "PASSWORD",
      "status": 1,
      "public_keys": ["SSH_PUBLIC_KEY"],
      "home_dir": "/srv/sftpgo/data/USERNAME",
      "permissions": {"/": ["*"]}
    }'
Enter fullscreen mode Exit fullscreen mode

"status": 1 is required — without it the account is created disabled.

Migrate Data

If SFTPGo will use Azure Blob as its own backend, just point users at the same container — no data transfer needed. For local-disk backends, pull the data down:

$ az storage blob download-batch --account-name STORAGE_ACCOUNT --source CONTAINER_NAME --destination ~/sftpgo/data/USERNAME/
$ sudo chown -R 1000:1000 ~/sftpgo/data/
Enter fullscreen mode Exit fullscreen mode

Map Authentication Methods

Azure Method SFTPGo Equivalent
SSH Public Keys Public Keys (direct)
Local User Passwords Password Authentication
Azure AD (Preview) LDAP/OIDC plugin, or external auth hook

Update Clients

Document the new endpoint (sftp.example.com, port 2022, SSH key or password) and roll it out before decommissioning the Azure endpoint. Export SFTPGo's SSH host key (/var/lib/sftpgo/id_*) and share the fingerprint so clients don't hit host-key warnings.

Things to Watch

  • Hierarchical Namespace: Azure Storage SFTP requires HNS; SFTPGo simulates directories without it — check any app that depends on HNS-specific behavior.
  • Container-level permissions: map to SFTPGo's per-directory ACLs.
  • Lifecycle policies: SFTPGo doesn't manage Azure Blob lifecycle rules — keep configuring those in Azure if you stay on the Blob backend.
  • Azure Monitor: replace with SFTPGo's logging, syslog, or webhook integrations.

Next Steps

SFTPGo is running as a self-hosted Azure Storage SFTP replacement with zero per-endpoint charge. From here you can:

  • Enable the Azure Blob backend directly if you want to keep data in place
  • Set quotas, bandwidth limits, and IP ACLs per user under Edit → Disk quota / ACLs
  • Wire event hooks (webhooks/syslog) to replace Azure Monitor alerting

For the full guide with additional tips, visit the original article on Vultr Docs.

Top comments (0)