Cloud

Replacing Legacy SFTP with AWS Transfer Family in a Multi-Account Landing Zone

How to architect a secure, multi-tenant SFTP service across AWS accounts using Transfer Family, NLB, Transit Gateway, and per-partner S3 isolation.

Alexandre Agius

Alexandre Agius

AWS Solutions Architect

10 min read
Share:

Replacing legacy SFTP servers with a managed AWS solution sounds straightforward until you factor in multi-account isolation, external partner authentication, tenant-level S3 access control, and integration with an existing centralized network architecture. This post walks through the architecture decisions I worked through for a real enterprise deployment.

The Problem

A large enterprise runs a multi-account AWS landing zone with a centralized Network account. A Fortinet firewall serves as the single internet entry/exit point via Transit Gateway. Multiple business units need to exchange files with external partners over SFTP, and the legacy SFTP servers are due for replacement.

The requirements:

  • Multi-account isolation β€” each business unit runs in its own AWS account
  • External-facing β€” partners connect from the public internet
  • Static IPs β€” partners whitelist IP addresses in their firewalls
  • Per-partner isolation β€” each partner only sees their own files in S3
  • Flexible authentication β€” some partners prefer SSH keys, others need username/password
  • Source IP preservation β€” logs must show the real partner IP, not an intermediary
  • Centralized entry point β€” one set of public IPs for all business units

The question: what’s the right combination of AWS services to satisfy all of these?

The Solution

A centralized Network Load Balancer in the Network account with Elastic IPs, routing SFTP traffic via Transit Gateway to per-account AWS Transfer Family servers (internal VPC endpoints). Each account runs its own authentication stack (API Gateway + Lambda + Secrets Manager) and stores files in S3 with per-partner prefix isolation enforced by IAM scope-down policies. Proxy Protocol v2 preserves the original partner IP end-to-end.

Architecture diagram showing the multi-account SFTP flow from external partners through NLB, Transit Gateway, to Transfer Family per business account

How It Works

Why NLB β€” Not GWLB, Not VPC Lattice

This was the first design decision. Three AWS networking services can route TCP traffic across accounts, but only one fits.

Gateway Load Balancer is designed for transparent inline appliance inspection β€” it sends all traffic through virtual appliances (firewalls, IDS/IPS) via GENEVE encapsulation. It doesn’t do port-based routing, doesn’t support Elastic IPs for partner whitelisting, and doesn’t speak Proxy Protocol v2. GWLB would make sense if we were routing through Fortinet, but we explicitly decided against that (more on why below).

VPC Lattice supports TCP resources for cross-account connectivity, but it’s designed for internal service-to-service communication (east-west). It’s not internet-facing, has no Elastic IP support, and no Proxy Protocol v2. The partners are external β€” VPC Lattice has no role here.

Network Load Balancer operates at Layer 4, supports multiple listeners with port-based routing, Elastic IPs, and Proxy Protocol v2 natively. It’s the only option that checks every box.

RequirementNLBGWLBVPC Lattice
Internet-facing ingressYesYes (via appliances)No
Elastic IPsYesNoNo
Port-based routingYesNoN/A
Proxy Protocol v2YesNo (GENEVE)No
Cross-account TCPYes (via TGW)Yes (via appliances)Yes (native)

Why Not Route Through Fortinet

The existing security policy mandates all traffic flows through the centralized Fortinet. The instinct is to comply and route SFTP through it. But SFTP runs over SSH (port 22) β€” the Fortinet cannot inspect encrypted SSH payloads. It can only see:

  • Source/destination IP
  • Port numbers
  • Connection metadata

All of which NLB Security Groups, NACLs, and CloudWatch already handle natively. Routing through Fortinet would add an NLB, TGW data processing costs, and operational complexity β€” for zero additional security value on encrypted traffic.

The stronger argument: a dedicated SFTP VPC with VPC endpoints and no default internet route is actually a better security posture than routing through Fortinet. There’s literally no outbound internet path to exploit.

Network Architecture β€” NLB + TGW + Transfer Family

The traffic flow:

External Partner (sftp-biz-a.acme-corp.com:2222)
  β†’ NLB (Network Account, Elastic IPs)
    β†’ Proxy Protocol v2 header added (original source IP)
      β†’ Transit Gateway
        β†’ Transfer Family VPC Endpoint (Business Account A, internal)
          β†’ S3

Port-based routing maps each business unit to a different TCP port on the same NLB:

PortBusiness UnitTransfer Family Target
2222Business Account ATF VPC endpoint ENIs via TGW
2223Business Account BTF VPC endpoint ENIs via TGW
2224Business Account CTF VPC endpoint ENIs via TGW

All DNS hostnames (sftp-biz-a.acme-corp.com, sftp-biz-b.acme-corp.com) resolve to the same NLB Elastic IPs. Partners differentiate by port number.

Proxy Protocol v2 must be enabled at two points: the NLB target group (proxy_protocol_v2.enabled = true) and Transfer Family (which reads PP v2 headers automatically when traffic arrives through an NLB). After setup, the clientIp field in Transfer Family structured logs shows the real partner IP β€” not the NLB private IP.

Authentication β€” Three Options, One Recommendation

SFTP runs inside SSH. Authentication happens during the SSH handshake. Two methods exist at the protocol level:

  • SSH Key Pair β€” partner holds a private key, server holds the public key. Cryptographic challenge-response. Nothing secret crosses the wire.
  • Username + Password β€” partner sends credentials over the encrypted SSH tunnel. Server validates against a credential store.

Transfer Family maps these to three identity provider types:

Service-ManagedCustom IdPDirectory Service
Auth methodsSSH keys onlySSH keys and/or passwordPassword only (AD)
Where credentials liveInside Transfer FamilySecrets Manager, DynamoDB, etc.AWS Managed AD
Custom code neededNoneAPI Gateway + LambdaNone (but need AD infra)
Extra cost$0~$20/mo~$72/mo minimum
Best forTechnical partnersMixed partner baseExisting AD environments

The recommendation: Custom IdP supporting both SSH keys and password. The Lambda receives whatever the client presents β€” if it’s a key, validate the key; if it’s a password, check Secrets Manager. Different partners can use different methods on the same server. This allows gradual migration from passwords to SSH keys over time.

Identity-to-IAM-Role Mapping

This is the piece that confused me initially. After authentication, how does a username become S3 permissions?

Transfer Family calls sts:AssumeRole on behalf of the authenticated user. The Lambda auth function returns three things:

  1. IAM Role ARN β€” the role Transfer Family assumes
  2. Home Directory β€” which S3 prefix the user lands in
  3. Session Policy (scope-down) β€” restricts what the role can do for this specific user

The effective permissions are the intersection of the role’s policy and the scope-down policy. Most restrictive wins.

IAM Role Policy (broad):              Scope-Down Policy (narrow):
  Allow s3:* on entire bucket           Allow s3:* on /partner-alpha/* only
                ↓                                      ↓
                └────────── INTERSECTION β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                 ↓
                  Effective: s3:* on /partner-alpha/* only

The recommended pattern: one shared IAM role for all partners + per-user scope-down policies. The role grants bucket-wide access. The scope-down (returned dynamically by Lambda per user) restricts to their prefix. One role to manage, same isolation.

Multi-Tenant S3 β€” Folder Structure

Each partner gets a prefix with inbound/ and outbound/ subdirectories:

s3://acme-sftp-bizacct-a/
  |-- partner-alpha/
  |   |-- inbound/     (partner uploads)
  |   |-- outbound/    (company places files for download)
  |-- partner-beta/
      |-- inbound/
      |-- outbound/

Using HomeDirectoryType: LOGICAL, partners see a clean root without knowing the S3 structure:

/            β†’ s3://acme-sftp-bizacct-a/partner-alpha/
/inbound/    β†’ s3://acme-sftp-bizacct-a/partner-alpha/inbound/
/outbound/   β†’ s3://acme-sftp-bizacct-a/partner-alpha/outbound/

One gotcha: ${transfer:HomeBucket} and ${transfer:HomeFolder} variables don’t work with logical directory mappings. Use explicit paths in the scope-down policy or have Lambda generate it dynamically per user.

Security Layering

Seven layers, each with a distinct role:

LayerScopeWhat It Protects
NLB Security GroupsNLBNetwork access to SFTP ports (partner CIDRs)
Transfer Family SGsTF VPC endpointAllow only NLB subnets
NACLsSubnet levelBroad stateless backup
WAFAPI Gateway (auth only)Brute-force, geo-blocking, rate limiting
IAM Scope-DownPer-user S3 accessTenant isolation
S3 Bucket PolicyBucket levelEnforce KMS encryption, restrict to TF role
KMS Key PolicyEncryption keyWho can encrypt/decrypt

WAF protects the authentication API, not SFTP traffic. SFTP runs on port 22 (SSH) β€” WAF operates on HTTP/HTTPS. If you’re using SSH key-only auth via service-managed users, there’s no API Gateway and no WAF needed at all.

Cost

For a 3-account deployment with 20 partners and 100 GB/month per account:

ComponentMonthly Cost
Transfer Family (3 servers)$657
Auth stack per account (API GW + Lambda + WAF + Secrets Mgr)$60
S3 + KMS + CloudWatch (3 accounts)$18
NLB (shared)~$16
Transit Gateway (3 attachments + data)$114
Route 53~$1
Total~$863/mo

TGW attachment cost ($108/mo for 3 accounts at $0.05/hr each) is the biggest fixed cost. If the accounts already have TGW attachments for other workloads, this is shared and shouldn’t be fully attributed to SFTP.

What I Learned

  • Fortinet can’t inspect SSH β€” routing SFTP through a firewall adds complexity for zero security value on encrypted protocols. A VPC with no outbound internet route is stronger than inline firewall inspection.
  • NLB is the only option for this pattern β€” GWLB is for appliance inspection, VPC Lattice is for internal service mesh. Neither supports the combination of internet-facing ingress + Elastic IPs + Proxy Protocol v2 + port-based routing.
  • SSH keys simplify everything β€” service-managed users with SSH keys eliminate the entire API Gateway + Lambda + WAF + Secrets Manager stack. The trade-off is partner onboarding friction (SSH key management vs. familiar username/password).
  • Scope-down policies are the real isolation mechanism β€” one shared IAM role + per-user scope-down policies is simpler and equally secure compared to one role per partner. The intersection model means the scope-down can only restrict, never expand.
  • Proxy Protocol v2 is critical but easy to miss β€” without it, all Transfer Family logs show the NLB private IP instead of the real partner IP. Enable it on both the NLB target group and verify in TF structured logs.

What’s Next

  • Build a PoC validating NLB + Proxy Protocol v2 + Transfer Family (internal VPC endpoint) across TGW
  • Validate PP v2 source IP preservation specifically across Transit Gateway (the critical unknown)
  • Test the Custom IdP Lambda supporting both SSH key and password auth on the same server
  • Document the partner onboarding workflow (credential provisioning, IP whitelisting, DNS configuration)
  • Evaluate whether per-account NLBs (instead of shared NLB with port routing) simplify operations enough to justify the extra ~$16/mo per account
Alexandre Agius

Alexandre Agius

AWS Solutions Architect

Passionate about AI & Security. Building scalable cloud solutions and helping organizations leverage AWS services to innovate faster. Specialized in Generative AI, serverless architectures, and security best practices.

Related Posts

Back to Blog