Create AWS Transfer Family Servers

The aws:transfer/server:Server resource, part of the Pulumi AWS provider, provisions AWS Transfer Family servers that provide managed SFTP, FTPS, FTP, and AS2 endpoints for file transfers. This guide focuses on three capabilities: endpoint types (public vs VPC), identity provider options (AWS-managed, Active Directory, Lambda, API Gateway), and security policies and structured logging.

Transfer servers may reference VPC infrastructure, AWS Directory Service directories, Lambda functions, or IAM roles depending on configuration. The examples are intentionally small. Combine them with your own networking, authentication infrastructure, and user management.

Create a public SFTP server with tags

Most deployments begin with a basic server that uses AWS-managed authentication and exposes an SFTP endpoint over the public internet.

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 SERVICE_MANAGED identity provider for AWS-managed users, and supports only the SFTP protocol. The tags property adds metadata for organization and cost tracking.

Apply a specific security policy

Organizations with compliance requirements enforce specific cryptographic algorithms and TLS versions through security policies.

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 controls which ciphers, key exchange algorithms, and TLS versions the server accepts. AWS provides multiple policies ranging from permissive (2018-11) to restrictive (FIPS variants), allowing you to balance compatibility with security requirements.

Deploy into a VPC with Elastic IPs

To restrict access to specific IP addresses or integrate with on-premises networks, deploy servers into VPCs with static Elastic IPs.

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 removes public internet access. The endpointDetails block specifies which VPC, subnets, and Elastic IP allocations to use. This configuration provides stable IP addresses for firewall rules and private network connectivity.

Authenticate users with AWS Directory Service

Enterprises with existing Active Directory infrastructure can integrate Transfer servers with AWS Managed Active Directory or AD Connector.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.transfer.Server("example", {
    identityProviderType: "AWS_DIRECTORY_SERVICE",
    directoryId: exampleAwsDirectoryServiceDirectory.id,
});
import pulumi
import pulumi_aws as aws

example = aws.transfer.Server("example",
    identity_provider_type="AWS_DIRECTORY_SERVICE",
    directory_id=example_aws_directory_service_directory["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{
			IdentityProviderType: pulumi.String("AWS_DIRECTORY_SERVICE"),
			DirectoryId:          pulumi.Any(exampleAwsDirectoryServiceDirectory.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()
    {
        IdentityProviderType = "AWS_DIRECTORY_SERVICE",
        DirectoryId = exampleAwsDirectoryServiceDirectory.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 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_DIRECTORY_SERVICE")
            .directoryId(exampleAwsDirectoryServiceDirectory.id())
            .build());

    }
}
resources:
  example:
    type: aws:transfer:Server
    properties:
      identityProviderType: AWS_DIRECTORY_SERVICE
      directoryId: ${exampleAwsDirectoryServiceDirectory.id}

Setting identityProviderType to AWS_DIRECTORY_SERVICE and providing a directoryId connects the server to your directory. Users authenticate with their AD credentials, and the directory controls access permissions centrally.

Integrate custom authentication with Lambda

Teams with custom identity providers or complex authentication logic can use Lambda functions to validate credentials dynamically.

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 and providing a function ARN delegates authentication to your Lambda function. The function receives credentials, validates them against your identity system, and returns user permissions and home directory mappings.

Send structured logs to CloudWatch

Production deployments enable logging to CloudWatch for monitoring file transfer activity and meeting audit requirements.

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 permissions to write logs. The structuredLogDestinations property specifies CloudWatch Log Group ARNs that receive detailed transfer events. Structured logs include authentication attempts, file operations, and error conditions in a queryable format.

Beyond these examples

These snippets focus on specific server-level features: endpoint types and network placement, identity provider integration, and security policies and logging. They’re intentionally minimal rather than full file transfer solutions.

The examples may reference pre-existing infrastructure such as VPC subnets, Elastic IPs, and security groups (for VPC endpoints), AWS Directory Service directories (for AD authentication), and Lambda functions (for custom authentication). 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 via protocols property)
  • Host key management for server identity (hostKey)
  • Custom domain names and certificates (certificate, domain properties)
  • Workflow automation (workflowDetails)

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 FREE

Frequently Asked Questions

Identity & Authentication
What are the requirements for each identity provider type?

Each identity provider type requires specific configuration:

  • SERVICE_MANAGED (default): No additional configuration needed
  • API_GATEWAY: Requires url and invocationRole
  • AWS_DIRECTORY_SERVICE: Requires directoryId
  • AWS_LAMBDA: Requires function (Lambda ARN)
What authentication methods are available for SFTP servers?
For SFTP servers using 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.
Can I change the identity provider type after creating the server?
No, identityProviderType is immutable and cannot be changed after creation. You must recreate the server to use a different identity provider.
Networking & Endpoints
What IAM permissions do I need for VPC endpoints?
When endpointType is set to VPC, your IAM principal needs ec2:DescribeVpcEndpoints and ec2:ModifyVpcEndpoint permissions.
How do I configure a VPC endpoint for my Transfer server?
Set endpointType to VPC and configure endpointDetails with subnetIds, vpcId, and optionally addressAllocationIds for Elastic IPs.
What's the difference between PUBLIC and VPC endpoint types?
PUBLIC (default) makes your server accessible over the public internet. VPC or VPC_ENDPOINT restricts access to within your VPC, requiring endpointDetails configuration.
Protocols & Security
What protocols are supported and what's the default?
The default protocol is SFTP. Available protocols are: AS2 (Applicability Statement 2), SFTP (SSH), FTPS (TLS encryption), and FTP (unencrypted).
Do I need a certificate for FTPS?
Yes, when protocols includes FTPS, you must provide an ACM certificate ARN in the certificate property.
What security policies are available?
The default is TransferSecurityPolicy-2018-11. Available policies include standard policies (2018-11 through 2025-03), FIPS policies, PQ-SSH experimental policies, restricted policies, and SSH audit-compliant policies. See the AWS documentation for details on each policy.
Configuration & Lifecycle
What properties can't be changed after creation?
Two properties are immutable: domain (S3 or EFS storage) and identityProviderType (authentication mode). Changing either requires recreating the server.
How do I delete a server with existing users?
Set forceDestroy to true to automatically delete all associated users before destroying the server. This only applies to servers with SERVICE_MANAGED identity provider (default is false).
Why does SFTP not support post-authentication banners?
The SFTP protocol does not support post-authentication display banners. Use preAuthenticationLoginBanner instead, which displays before authentication.
Why am I seeing a diff for hostKey after importing a server?
The hostKey argument cannot be read via the API during import. You’ll see a difference on the first run after importing, which is expected behavior.
Logging & Monitoring
How do I enable structured logging to CloudWatch?
Configure loggingRole with an IAM role ARN (with CloudWatch write permissions) and set structuredLogDestinations to an array of CloudWatch Log Group ARNs.
How do I manage custom hostname tags?
Use the aws.transfer.Tag resource to manage system tags required for custom hostnames, rather than the tags property on the server resource.

Using a different cloud?

Explore integration guides for other cloud providers: