The aws:transfer/server:Server resource, part of the Pulumi AWS provider, provisions AWS Transfer Family servers that accept file transfers over SFTP, FTPS, FTP, or AS2 protocols. This guide focuses on three capabilities: endpoint types (public vs VPC), identity provider options, and security policies and logging.
Transfer servers may reference VPC infrastructure, Lambda functions for authentication, and IAM roles for logging. The examples are intentionally small. Combine them with your own user provisioning, home directory mappings, and workflow automation.
Create a public SFTP server with tags
Most deployments begin with a basic server that accepts SFTP connections over the public internet, providing a starting point without VPC complexity.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.transfer.Server("example", {tags: {
Name: "Example",
}});
import pulumi
import pulumi_aws as aws
example = aws.transfer.Server("example", tags={
"Name": "Example",
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/transfer"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := transfer.NewServer(ctx, "example", &transfer.ServerArgs{
Tags: pulumi.StringMap{
"Name": pulumi.String("Example"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var example = new Aws.Transfer.Server("example", new()
{
Tags =
{
{ "Name", "Example" },
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.transfer.Server;
import com.pulumi.aws.transfer.ServerArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var example = new Server("example", ServerArgs.builder()
.tags(Map.of("Name", "Example"))
.build());
}
}
resources:
example:
type: aws:transfer:Server
properties:
tags:
Name: Example
Without explicit configuration, the server defaults to a PUBLIC endpoint accessible over the internet, uses the SFTP protocol, and relies on SERVICE_MANAGED identity (users stored within Transfer Family). The tags property adds metadata for organization and cost tracking.
Apply a modern security policy
Transfer servers support multiple security policies that control cipher suites and cryptographic algorithms for compliance requirements.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.transfer.Server("example", {securityPolicyName: "TransferSecurityPolicy-2020-06"});
import pulumi
import pulumi_aws as aws
example = aws.transfer.Server("example", security_policy_name="TransferSecurityPolicy-2020-06")
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/transfer"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := transfer.NewServer(ctx, "example", &transfer.ServerArgs{
SecurityPolicyName: pulumi.String("TransferSecurityPolicy-2020-06"),
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var example = new Aws.Transfer.Server("example", new()
{
SecurityPolicyName = "TransferSecurityPolicy-2020-06",
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.transfer.Server;
import com.pulumi.aws.transfer.ServerArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var example = new Server("example", ServerArgs.builder()
.securityPolicyName("TransferSecurityPolicy-2020-06")
.build());
}
}
resources:
example:
type: aws:transfer:Server
properties:
securityPolicyName: TransferSecurityPolicy-2020-06
The securityPolicyName property selects from AWS-managed policies. TransferSecurityPolicy-2020-06 enforces stronger encryption than the 2018-11 default. Choose policies based on your compliance requirements; FIPS variants are available for government workloads.
Deploy a server in a VPC with Elastic IPs
Applications requiring private network access or static IP addresses deploy servers into VPCs with Elastic IP allocations.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.transfer.Server("example", {
endpointType: "VPC",
endpointDetails: {
addressAllocationIds: [exampleAwsEip.id],
subnetIds: [exampleAwsSubnet.id],
vpcId: exampleAwsVpc.id,
},
});
import pulumi
import pulumi_aws as aws
example = aws.transfer.Server("example",
endpoint_type="VPC",
endpoint_details={
"address_allocation_ids": [example_aws_eip["id"]],
"subnet_ids": [example_aws_subnet["id"]],
"vpc_id": example_aws_vpc["id"],
})
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/transfer"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := transfer.NewServer(ctx, "example", &transfer.ServerArgs{
EndpointType: pulumi.String("VPC"),
EndpointDetails: &transfer.ServerEndpointDetailsArgs{
AddressAllocationIds: pulumi.StringArray{
exampleAwsEip.Id,
},
SubnetIds: pulumi.StringArray{
exampleAwsSubnet.Id,
},
VpcId: pulumi.Any(exampleAwsVpc.Id),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var example = new Aws.Transfer.Server("example", new()
{
EndpointType = "VPC",
EndpointDetails = new Aws.Transfer.Inputs.ServerEndpointDetailsArgs
{
AddressAllocationIds = new[]
{
exampleAwsEip.Id,
},
SubnetIds = new[]
{
exampleAwsSubnet.Id,
},
VpcId = exampleAwsVpc.Id,
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.transfer.Server;
import com.pulumi.aws.transfer.ServerArgs;
import com.pulumi.aws.transfer.inputs.ServerEndpointDetailsArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var example = new Server("example", ServerArgs.builder()
.endpointType("VPC")
.endpointDetails(ServerEndpointDetailsArgs.builder()
.addressAllocationIds(exampleAwsEip.id())
.subnetIds(exampleAwsSubnet.id())
.vpcId(exampleAwsVpc.id())
.build())
.build());
}
}
resources:
example:
type: aws:transfer:Server
properties:
endpointType: VPC
endpointDetails:
addressAllocationIds:
- ${exampleAwsEip.id}
subnetIds:
- ${exampleAwsSubnet.id}
vpcId: ${exampleAwsVpc.id}
Setting endpointType to VPC places the server in your network. The endpointDetails block specifies the VPC, subnets, and Elastic IP allocations. Clients connect to the Elastic IPs, which route traffic to the server through your VPC’s network interfaces.
Authenticate users with a Lambda function
Custom authentication logic often lives in Lambda functions that validate credentials against external systems or apply business rules.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const example = new aws.transfer.Server("example", {
identityProviderType: "AWS_LAMBDA",
"function": exampleAwsLambdaIdentityProvider.arn,
});
import pulumi
import pulumi_aws as aws
example = aws.transfer.Server("example",
identity_provider_type="AWS_LAMBDA",
function=example_aws_lambda_identity_provider["arn"])
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/transfer"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := transfer.NewServer(ctx, "example", &transfer.ServerArgs{
IdentityProviderType: pulumi.String("AWS_LAMBDA"),
Function: pulumi.Any(exampleAwsLambdaIdentityProvider.Arn),
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var example = new Aws.Transfer.Server("example", new()
{
IdentityProviderType = "AWS_LAMBDA",
Function = exampleAwsLambdaIdentityProvider.Arn,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.transfer.Server;
import com.pulumi.aws.transfer.ServerArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var example = new Server("example", ServerArgs.builder()
.identityProviderType("AWS_LAMBDA")
.function(exampleAwsLambdaIdentityProvider.arn())
.build());
}
}
resources:
example:
type: aws:transfer:Server
properties:
identityProviderType: AWS_LAMBDA
function: ${exampleAwsLambdaIdentityProvider.arn}
Setting identityProviderType to AWS_LAMBDA delegates authentication to your function. The function property points to the Lambda ARN. Your function receives authentication requests and returns user configuration (home directory, IAM role) or rejects access.
Send structured logs to CloudWatch
Monitoring file transfer activity requires CloudWatch integration for auditing and troubleshooting.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const transfer = new aws.cloudwatch.LogGroup("transfer", {namePrefix: "transfer_test_"});
const transferAssumeRole = aws.iam.getPolicyDocument({
statements: [{
effect: "Allow",
principals: [{
type: "Service",
identifiers: ["transfer.amazonaws.com"],
}],
actions: ["sts:AssumeRole"],
}],
});
const iamForTransfer = new aws.iam.Role("iam_for_transfer", {
namePrefix: "iam_for_transfer_",
assumeRolePolicy: transferAssumeRole.then(transferAssumeRole => transferAssumeRole.json),
managedPolicyArns: ["arn:aws:iam::aws:policy/service-role/AWSTransferLoggingAccess"],
});
const transferServer = new aws.transfer.Server("transfer", {
endpointType: "PUBLIC",
loggingRole: iamForTransfer.arn,
protocols: ["SFTP"],
structuredLogDestinations: [pulumi.interpolate`${transfer.arn}:*`],
});
import pulumi
import pulumi_aws as aws
transfer = aws.cloudwatch.LogGroup("transfer", name_prefix="transfer_test_")
transfer_assume_role = aws.iam.get_policy_document(statements=[{
"effect": "Allow",
"principals": [{
"type": "Service",
"identifiers": ["transfer.amazonaws.com"],
}],
"actions": ["sts:AssumeRole"],
}])
iam_for_transfer = aws.iam.Role("iam_for_transfer",
name_prefix="iam_for_transfer_",
assume_role_policy=transfer_assume_role.json,
managed_policy_arns=["arn:aws:iam::aws:policy/service-role/AWSTransferLoggingAccess"])
transfer_server = aws.transfer.Server("transfer",
endpoint_type="PUBLIC",
logging_role=iam_for_transfer.arn,
protocols=["SFTP"],
structured_log_destinations=[transfer.arn.apply(lambda arn: f"{arn}:*")])
package main
import (
"fmt"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/cloudwatch"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/iam"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/transfer"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
transfer, err := cloudwatch.NewLogGroup(ctx, "transfer", &cloudwatch.LogGroupArgs{
NamePrefix: pulumi.String("transfer_test_"),
})
if err != nil {
return err
}
transferAssumeRole, err := iam.GetPolicyDocument(ctx, &iam.GetPolicyDocumentArgs{
Statements: []iam.GetPolicyDocumentStatement{
{
Effect: pulumi.StringRef("Allow"),
Principals: []iam.GetPolicyDocumentStatementPrincipal{
{
Type: "Service",
Identifiers: []string{
"transfer.amazonaws.com",
},
},
},
Actions: []string{
"sts:AssumeRole",
},
},
},
}, nil)
if err != nil {
return err
}
iamForTransfer, err := iam.NewRole(ctx, "iam_for_transfer", &iam.RoleArgs{
NamePrefix: pulumi.String("iam_for_transfer_"),
AssumeRolePolicy: pulumi.String(transferAssumeRole.Json),
ManagedPolicyArns: pulumi.StringArray{
pulumi.String("arn:aws:iam::aws:policy/service-role/AWSTransferLoggingAccess"),
},
})
if err != nil {
return err
}
_, err = transfer.NewServer(ctx, "transfer", &transfer.ServerArgs{
EndpointType: pulumi.String("PUBLIC"),
LoggingRole: iamForTransfer.Arn,
Protocols: pulumi.StringArray{
pulumi.String("SFTP"),
},
StructuredLogDestinations: pulumi.StringArray{
transfer.Arn.ApplyT(func(arn string) (string, error) {
return fmt.Sprintf("%v:*", arn), nil
}).(pulumi.StringOutput),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var transfer = new Aws.CloudWatch.LogGroup("transfer", new()
{
NamePrefix = "transfer_test_",
});
var transferAssumeRole = Aws.Iam.GetPolicyDocument.Invoke(new()
{
Statements = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementInputArgs
{
Effect = "Allow",
Principals = new[]
{
new Aws.Iam.Inputs.GetPolicyDocumentStatementPrincipalInputArgs
{
Type = "Service",
Identifiers = new[]
{
"transfer.amazonaws.com",
},
},
},
Actions = new[]
{
"sts:AssumeRole",
},
},
},
});
var iamForTransfer = new Aws.Iam.Role("iam_for_transfer", new()
{
NamePrefix = "iam_for_transfer_",
AssumeRolePolicy = transferAssumeRole.Apply(getPolicyDocumentResult => getPolicyDocumentResult.Json),
ManagedPolicyArns = new[]
{
"arn:aws:iam::aws:policy/service-role/AWSTransferLoggingAccess",
},
});
var transferServer = new Aws.Transfer.Server("transfer", new()
{
EndpointType = "PUBLIC",
LoggingRole = iamForTransfer.Arn,
Protocols = new[]
{
"SFTP",
},
StructuredLogDestinations = new[]
{
transfer.Arn.Apply(arn => $"{arn}:*"),
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.cloudwatch.LogGroup;
import com.pulumi.aws.cloudwatch.LogGroupArgs;
import com.pulumi.aws.iam.IamFunctions;
import com.pulumi.aws.iam.inputs.GetPolicyDocumentArgs;
import com.pulumi.aws.iam.Role;
import com.pulumi.aws.iam.RoleArgs;
import com.pulumi.aws.transfer.Server;
import com.pulumi.aws.transfer.ServerArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var transfer = new LogGroup("transfer", LogGroupArgs.builder()
.namePrefix("transfer_test_")
.build());
final var transferAssumeRole = IamFunctions.getPolicyDocument(GetPolicyDocumentArgs.builder()
.statements(GetPolicyDocumentStatementArgs.builder()
.effect("Allow")
.principals(GetPolicyDocumentStatementPrincipalArgs.builder()
.type("Service")
.identifiers("transfer.amazonaws.com")
.build())
.actions("sts:AssumeRole")
.build())
.build());
var iamForTransfer = new Role("iamForTransfer", RoleArgs.builder()
.namePrefix("iam_for_transfer_")
.assumeRolePolicy(transferAssumeRole.json())
.managedPolicyArns("arn:aws:iam::aws:policy/service-role/AWSTransferLoggingAccess")
.build());
var transferServer = new Server("transferServer", ServerArgs.builder()
.endpointType("PUBLIC")
.loggingRole(iamForTransfer.arn())
.protocols("SFTP")
.structuredLogDestinations(transfer.arn().applyValue(_arn -> String.format("%s:*", _arn)))
.build());
}
}
resources:
transfer:
type: aws:cloudwatch:LogGroup
properties:
namePrefix: transfer_test_
iamForTransfer:
type: aws:iam:Role
name: iam_for_transfer
properties:
namePrefix: iam_for_transfer_
assumeRolePolicy: ${transferAssumeRole.json}
managedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSTransferLoggingAccess
transferServer:
type: aws:transfer:Server
name: transfer
properties:
endpointType: PUBLIC
loggingRole: ${iamForTransfer.arn}
protocols:
- SFTP
structuredLogDestinations:
- ${transfer.arn}:*
variables:
transferAssumeRole:
fn::invoke:
function: aws:iam:getPolicyDocument
arguments:
statements:
- effect: Allow
principals:
- type: Service
identifiers:
- transfer.amazonaws.com
actions:
- sts:AssumeRole
The loggingRole grants Transfer Family permission to write logs. The structuredLogDestinations property enables detailed session and operation logs sent to CloudWatch Log Groups. Structured logs include user identity, file paths, and transfer status for each operation.
Beyond these examples
These snippets focus on specific server-level features: endpoint types and VPC placement, identity provider integration, and security policies and structured logging. They’re intentionally minimal rather than full file transfer solutions.
The examples may reference pre-existing infrastructure such as VPC subnets, Elastic IPs, security groups, Lambda functions for custom authentication, and IAM roles for logging and API Gateway integration. They focus on configuring the server rather than provisioning everything around it.
To keep things focused, common server patterns are omitted, including:
- Protocol-specific configuration (FTP, FTPS, AS2)
- Host key management for server identity
- Workflow automation (workflowDetails)
- User provisioning and home directory mapping
These omissions are intentional: the goal is to illustrate how each server feature is wired, not provide drop-in file transfer modules. See the Transfer Server resource reference for all available configuration options.
Let's create AWS Transfer Family Servers
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Identity & Authentication
Transfer Family supports four identity provider types (immutable after creation):
- SERVICE_MANAGED (default) - Store user credentials within the service
- API_GATEWAY - Integrate your own identity provider via API Gateway (requires
urlandinvocationRole) - AWS_DIRECTORY_SERVICE - Use AWS Managed Active Directory or on-premises AD (requires
directoryId) - AWS_LAMBDA - Use a Lambda function for authentication (requires
functionARN)
API_GATEWAY or AWS_LAMBDA identity providers, you can configure sftpAuthenticationMethods with: PASSWORD, PUBLIC_KEY, PUBLIC_KEY_OR_PASSWORD (default), or PUBLIC_KEY_AND_PASSWORD.Networking & Endpoints
PUBLIC endpoints (default) make your server accessible over the public internet. VPC or VPC_ENDPOINT types restrict access to within your VPC only, requiring endpointDetails with subnetIds and vpcId.VPC endpoint type, your IAM principal needs ec2:DescribeVpcEndpoints and ec2:ModifyVpcEndpoint permissions.endpointType to VPC and configure endpointDetails with subnetIds, vpcId, and optionally addressAllocationIds for Elastic IPs.Protocols & Security
AS2 (Applicability Statement 2), SFTP (SSH), FTPS (TLS encryption), and FTP (unencrypted). The default is SFTP.protocols includes FTPS, you must provide an ACM certificate ARN in the certificate property.preAuthenticationLoginBanner for SFTP servers, or use a different protocol if postAuthenticationLoginBanner is required.TransferSecurityPolicy-2018-11 (default) through TransferSecurityPolicy-2025-03, including FIPS variants, PQ-SSH experimental policies, and restricted policies. Choose based on your compliance and security requirements.Logging & Monitoring
structuredLogDestinations to an array of CloudWatch Log Group ARNs (e.g., ${logGroup.arn}:*) and provide a loggingRole with the AWSTransferLoggingAccess managed policy.Resource Management
identityProviderType (authentication mode) and domain (storage system: S3 or EFS). Changing these requires recreating the server.forceDestroy to true to automatically delete all associated users before destroying the server. This only applies to servers with SERVICE_MANAGED identity provider type (default is false).hostKey property cannot be read via the API during import. If you declare hostKey in your configuration, it will show a difference on the first run after import.aws.transfer.Tag resource to manage the system tags required for custom hostnames.Using a different cloud?
Explore integration guides for other cloud providers: