Skip to content

SFTP Server — SFTPGo on Ubuntu (vm-sftp-prod-001)

Overview

Azure VM running SFTPGo as the managed SFTP service for eSHARS data exchange with external partners and customers. Fronted by a Standard Load Balancer for inbound traffic. The VM has no direct public IP — all access is through the LB frontend.

Migration in progress: Clients currently connect to the legacy VM (vm-eshars-sftp / 40.77.98.52). The cutover plan is to reassign 40.77.98.52 to the LB frontend once testing is complete. See Cutover Plan below.

Architecture

Internet (port 22)
┌─────────────────────────────┐
│ lb-sftp-prod (Standard) │
│ Frontend: pip-sftp-prod │
│ 130.131.245.25 │
│ │
│ Rule: TCP 22 → 22 │
│ Probe: TCP 22 │
│ Outbound: 1024 SNAT ports │
└────────────┬────────────────┘
┌─────────────────────────────────────────┐
│ rg-eshars-prod │
│ │
│ ┌───────────────────────────────────┐ │
│ │ vm-sftp-prod-001 (Standard_B2s) │ │
│ │ Ubuntu 24.04 LTS │ │
│ │ Private IP: 10.0.0.15 (Static) │ │
│ │ │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ SFTPGo │ │ │
│ │ │ Port: 22 │ │ │
│ │ │ Users DB: /var/lib/sftpgo/ │ │ │
│ │ │ Data: /srv/sftpgo/data │ │ │
│ │ │ Backups: /srv/sftpgo/backups│ │ │
│ │ └─────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────┐ │ │
│ │ │ fail2ban │ │ │
│ │ │ sshd jail: 3 tries → 24h │ │ │
│ │ │ sshd-ddos: 10/min → 1h │ │ │
│ │ └─────────────────────────────┘ │ │
│ │ │ │
│ │ Disks: │ │
│ │ /dev/sdb (30GB) — OS │ │
│ │ /dev/sda (128GB) — SFTPGo data │ │
│ │ └─ sda1 → /srv/sftpgo │ │
│ └───────────────────────────────────┘ │
│ │
│ nsg-vm-sftp-prod-001 │
│ ├─ Allow TCP 22 inbound (*) pri 100 │
│ ├─ Allow TCP 2222 inbound (VNet) pri 110│
│ └─ Deny all inbound pri 4000 │
└─────────────────────────────────────────┘

Resource Inventory (prod)

ResourceNameNotes
Resource Grouprg-eshars-prodShared prod RG
Virtual Machinevm-sftp-prod-001Standard_B2s, Central US
NICvm-sftp-prod-001-temp-nicName is legacy — rename in IaC
Private IP10.0.0.15Static
Load Balancerlb-sftp-prodStandard SKU
LB Frontend IPpip-sftp-prod130.131.245.25, Standard, Static
LB Backend Poolbe-sftpContains vm-sftp-prod-001 NIC
LB Rulerule-sftp-22TCP 22→22, outbound SNAT disabled
LB Probeprobe-sftp-22TCP 22
LB Outbound Ruleoutbound-sftpAll protocols, 1024 SNAT ports
NSGnsg-vm-sftp-prod-001See rules above
OS Disk30GB
Data Disk/dev/sda (sda1)128GB ext4, UUID 2bc4f457-e440-4d09-b7d8-30e6bfde287e
Defender for ServersP1Enabled on subscription

VM Configuration

OS & Software

  • OS: Ubuntu 24.04 LTS (6.17.0-1010-azure as of 2026-04-09)
  • SFTP daemon: SFTPGo (systemd service: sftpgo)
  • Brute-force protection: fail2ban 1.0.2

SFTPGo

  • Config: /etc/sftpgo/sftpgo.json
  • User database: /var/lib/sftpgo/sftpgo.db (SQLite, on OS disk)
  • Users base dir: /srv/sftpgo/data (on data disk)
  • Backups path: /srv/sftpgo/backups (on data disk)
  • Admin port: SFTPGo web admin (check config for port)
  • SSH port: 22

Disk Layout

MountDeviceSizeFilesystemNotes
//dev/sdb130GBext4OS disk
/srv/sftpgo/dev/sda1128GBext4Data disk — all user files
/boot/dev/sdb16913MBext4
/boot/efi/dev/sdb15106MBvfat

Critical: /etc/fstab mounts /srv/sftpgo by UUID. Do NOT change it to a device name — Azure may reassign device letters on resize/reboot. To verify the current UUID on the VM, check grep /srv/sftpgo /etc/fstab and confirm it with blkid or lsblk -f.

fail2ban (/etc/fail2ban/jail.local)

[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
[sshd]
enabled = true
port = 22
logpath = %(sshd_log)s
maxretry = 3
bantime = 24h
[sshd-ddos]
enabled = true
port = 22
filter = sshd
logpath = %(sshd_log)s
maxretry = 10
findtime = 1m
bantime = 1h

Admin SSH

Port 2222 is open to VNet only (AllowSSHAdmin NSG rule, priority 110). Use Bastion or a jump host on the VNet for administrative access.

Cutover Plan

Clients currently connect to the legacy VM at 40.77.98.52 (vm-hisd-sftp-ip, attached to vm-eshars-sftp896 NIC in vm-eshars-sftp_group).

Cutover steps (zero client changes required)

  1. Confirm testing complete on 130.131.245.25
  2. Detach vm-hisd-sftp-ip from legacy VM NIC:
    Terminal window
    az network nic ip-config update \
    --resource-group vm-eshars-sftp_group \
    --nic-name vm-eshars-sftp896 \
    --name ipconfig1 \
    --remove publicIpAddress
  3. Attach vm-hisd-sftp-ip to LB frontend:
    Terminal window
    az network lb frontend-ip update \
    --resource-group rg-eshars-prod \
    --lb-name lb-sftp-prod \
    --name fe-sftp \
    --public-ip-address vm-hisd-sftp-ip
  4. Clients now hit 40.77.98.52 → LB → vm-sftp-prod-001. No changes needed on client side.

Rollback (if cutover fails, ~90 seconds)

Terminal window
# Step 1 — restore LB to its own IP
az network lb frontend-ip update \
--resource-group rg-eshars-prod \
--lb-name lb-sftp-prod \
--name fe-sftp \
--public-ip-address pip-sftp-prod
# Step 2 — reattach old IP to legacy VM NIC
az network nic ip-config update \
--resource-group vm-eshars-sftp_group \
--nic-name vm-eshars-sftp896 \
--name ipconfig1 \
--public-ip-address vm-hisd-sftp-ip

40.77.98.52 is never deleted — it stays allocated in the subscription throughout, so rollback is always available.

Security Posture

ControlStatusNotes
No direct public IP on VMRemoved — LB only
fail2ban3 failures = 24h ban
Defender for Servers P1Subscription-level
NSG deny-all defaultPriority 4000
SSH key auth onlyPassword auth still enabled — migration in progress
IP allowlistingNot feasible — customers connect from dynamic IPs

Password auth migration plan

  • New customers: provision with SSH keypair from day one
  • Existing customers: reach out to add public key, flip to key-only per account
  • Final step: disable PasswordAuthentication globally in sshd_config once all accounts migrated

Known Issues / Technical Debt

  1. NIC namevm-sftp-prod-001-temp-nic has a legacy “temp” name. Rename via IaC.
  2. Password auth enabled — see migration plan above.
  3. Legacy VMsHISD-SFTP, vm-hisd-sftp, and vm-eshars-sftp in prod still exist. Decommission after cutover is validated.
  4. Basic LBlb-eshars-prod (Basic SKU, empty) exists in rg-eshars-prod. Basic LB is retired — decommission or upgrade.
  5. IaC — this VM and LB were provisioned manually. IaC to be created (see below).

IaC Backlog

The following resources need to be codified in OpenTofu:

  • vm-sftp-prod-001 — VM, NIC, NSG, OS disk
  • lb-sftp-prod — LB, frontend IP, backend pool, rule, probe, outbound rule
  • pip-sftp-prod — Standard static public IP
  • Data disk (sda) attachment and fstab config
  • fail2ban provisioning (cloud-init or custom script extension)
  • SFTPGo installation and config
  • Defender for Servers P1 enablement

Target: infrastructure/hisd-sftp/ (module already exists, needs populating).

Operations

Check SFTPGo status

Terminal window
systemctl status sftpgo

Check fail2ban bans

Terminal window
fail2ban-client status sshd
fail2ban-client status sshd-ddos

Unban an IP

Terminal window
fail2ban-client set sshd unbanip <ip>

View SFTPGo logs

Terminal window
journalctl -u sftpgo -f

Mount check after reboot

Terminal window
df -h | grep srv
# Should show /dev/sda1 mounted at /srv/sftpgo