Amazon S3 Files: When S3 Finally Became a Real File System Interface

Amazon S3 Files: When S3 Finally Became a Real File System Interface

👁2views

Amazon S3 Files gives applications a native file system interface directly over S3 storage, eliminating the need for third-party gateways, custom mounting tools, or separate NFS layers. Announced by AWS on 7 April 2026, it lets a bucket or prefix act as a shared file system, with AWS managing metadata, synchronization, permissions, and performance automatically, collapsing years of workaround architecture.

CloudScale AI SEO - Article Summary
  • 1.
    What it is
    Amazon S3 Files explains how AWS's April 2026 release exposes S3 buckets or prefixes as a shared file system to EC2, ECS, EKS and Lambda via NFS v4.1/4.2, using EFS underneath for active data.
  • 2.
    Why it matters
    It shows why this removes the need for custom sync layers between S3 and file storage, letting applications use standard POSIX file operations while S3 stays the authoritative data store.
  • 3.
    Key takeaway
    S3 Files isn't a universal file system replacement: renaming a directory still forces S3 Files to copy and delete every object under that prefix, since S3's flat object model never actually goes away.
~37 min read

A deep technical walkthrough of Amazon S3 Files: what it actually is, where it earns its complexity, setup scripts, monitoring, costs and the production gotchas that will bite you if you skip straight to the mount command.

Jump to: What it is · What it is not · Setup script · Monitoring · Gotchas · Production checklist · Outside AWS

1. The release that matters

Amazon S3 Files is one of those AWS releases that looks simple until you think about the amount of architecture it quietly collapses. On 7 April 2026, AWS announced S3 Files, a new capability that lets you expose an S3 general purpose bucket, or a prefix inside that bucket, as a shared file system to AWS compute. The important phrase here is not “mount S3”, because we have had ways to mount S3 like storage for years. The important phrase is “shared file system”, because S3 Files gives applications a file interface over S3 data while AWS manages the translation, synchronisation, metadata, permissions and active data performance layer underneath.

This changes one of the oldest architectural trade offs in AWS. S3 has always been the natural place to keep durable, cheap and massively scalable data, but many applications still expect files, directories, POSIX permissions (the standard Linux model of owner, group and other access rules), locks, renames and ordinary filesystem calls. Before S3 Files, you usually had to choose between writing directly to S3 using object APIs, copying S3 data into EFS (AWS’s managed NFS file service) or FSx (AWS’s family of managed file systems for Windows, NetApp, OpenZFS and Lustre workloads), mounting S3 through a client with limited semantics, or building your own synchronisation layer between object storage and a file system. S3 Files does not make all of those options obsolete, but it does make a large class of messy glue architecture look unnecessary. AWS describes the release as making general purpose S3 buckets accessible as native file systems from EC2, ECS, EKS, Fargate, Batch and Lambda, with S3 objects presented as files and directories through NFS (Network File System, the long established protocol Linux and Unix machines use to mount a remote file system as though it were local) v4.1 and later operations.

The trade off is that the object model still leaks at the edges, particularly around rename, conflict handling, archive storage classes and high churn metadata workloads. None of that is a flaw so much as a consequence of what S3 fundamentally is, but it is worth saying plainly before the rest of this piece gets enthusiastic about what the service unlocks.

The mental model is straightforward once you sit with it. S3 remains the authoritative data store, but applications can now use standard file operations against a managed file system view of that data. Reads and writes go through a file system interface. Active data and metadata are placed on a high performance storage layer. Changes are synchronised back to S3, and changes made directly in S3 are reflected back into the file system view. The result is not S3 pretending to be POSIX in the old, brittle, FUSE (a Linux mechanism that lets an ordinary program implement a file system in user space, the basis for most of the janky S3 mount clients that came before this) client sense. It is a managed AWS service that deliberately bridges object and file semantics.

If you only take one thing from this article before deciding whether to bother, it is probably this decision table. A quick note on a few of the more specialist entries: SMB is the Windows file sharing protocol, ONTAP is NetApp’s storage operating system that many enterprises already run on premises, and Lustre is a file system built for high performance computing scratch space where thousands of nodes read and write in parallel.

NeedBetter choice
Shared Linux file system not anchored on S3EFS
Existing S3 data with file oriented workloadsS3 Files
High throughput read heavy S3 access with limited mutationMountpoint for Amazon S3
Windows shares or SMBFSx for Windows File Server
ONTAP or NAS migrationFSx for NetApp ONTAP
HPC scratch or Lustre semanticsFSx for Lustre
On premises file gateway patternAWS Storage Gateway

2. What S3 Files actually is

S3 Files is a shared file system linked to an S3 bucket or bucket prefix. You create an S3 file system, create mount targets in your VPC, and mount it from compute using the S3 Files mount helper. The mounted path behaves like a file system, while the backing data continues to live in S3. AWS says the service supports NFS v4.1 and v4.2 protocols, including file system access semantics such as read after write consistency, file locking and POSIX permissions.

The core resources are simple but worth setting out clearly:

ResourceWhat it does
S3 bucket or prefixThe authoritative object store where your durable data lives
S3 file systemThe shared file system linked to the bucket or prefix
High performance storageThe low latency active data layer used for metadata and frequently accessed file contents
Mount targetA network endpoint in a VPC availability zone through which compute mounts the file system
Access pointAn application specific entry point that can enforce a POSIX identity and root directory
Synchronisation configurationRules that decide what data is imported to the file system and when it expires from the active layer

The clever part is the two tier model. S3 Files does not copy your whole bucket into a file system, because that would be expensive and architecturally pointless. Instead, metadata and selected file data are loaded onto the high performance storage layer as you access directories and files. Small, latency sensitive files can be served from that layer, while large reads and data that is not resident there can be streamed directly from S3. AWS documents the default import threshold as 128 KiB and notes that large reads of 1 MiB or more are streamed directly from S3 even when the data also resides on the high performance storage layer.

Under the hood, S3 Files uses Amazon EFS and delivers roughly one millisecond of latency for active data, according to the launch blog. That detail matters because AWS is not simply exposing S3 through a thin file client. It is using a real managed file system layer for the hot working set, then routing other reads directly to S3 where S3 is stronger. For mixed workloads, this is the entire point of the design. Small, interactive, metadata heavy operations get file system behaviour, while large sequential reads still benefit from S3 throughput.

3. What S3 Files is not

S3 Files is not a magic way to pretend that object storage and file storage are the same thing. S3 still has a flat object model. Directories are still prefixes. Objects are still immutable. Renames and moves still have to be translated into object level operations when changes are synchronised back to S3. AWS explicitly warns that when you rename or move a file, S3 Files must write the data to a new object with the updated key and delete the original, and when you rename a directory it must repeat that copy and delete process for every object under the prefix.

It is also not a replacement for every AWS file service. EFS still makes sense when you want a general shared Linux file system that is not fundamentally anchored on S3 as the source of truth. FSx still makes sense for Windows file shares, ONTAP compatibility, OpenZFS, Lustre based HPC and NAS migrations where the required feature set is broader or more specialised. AWS itself positions FSx as the right choice for on premises NAS migrations, HPC and workloads that need the specific capabilities of FSx for NetApp ONTAP, OpenZFS, Windows File Server or Lustre.

It is not the same thing as Mountpoint for Amazon S3 either. Mountpoint is a high throughput open source client that lets Linux applications access S3 objects through file operations such as open and read, but AWS documents that Mountpoint cannot modify existing files or delete directories, and it does not support symbolic links or file locking. Mountpoint is excellent for large scale read heavy workloads that want S3 throughput through a local file interface, while S3 Files is the more complete shared file system option when mutation, collaboration, locking, POSIX permissions and synchronisation matter.

It is not a free lunch for small file churn. S3 Files has its own metering model. You pay for active data stored on the high performance layer, for file system access charges when reading from and writing to that layer, and for S3 requests involved in synchronisation. Every operation is metered as a read or write against file data or metadata, with minimum metered sizes for reads, writes and metadata operations. This is much better than copying an entire bucket into a file system, but it means noisy metadata heavy workloads, repeated permission changes and excessive tiny writes can still cost money.

It is worth making that concrete rather than leaving it as an abstract warning. File and metadata operations are metered with minimum sizes of 32 KiB for data and 4 KiB for metadata. A workload that performs ten million tiny metadata operations, for example a build tool that stats every file in a large repository on every run, is billed as if each of those ten million operations moved 4 KiB, which is roughly 40 GB of metered metadata traffic, regardless of how small the actual files are. That is not an enormous sum of money on its own, but it compounds quickly once you have several such workloads running on a schedule, and it is exactly the kind of cost that never shows up in a proof of concept because nobody runs a proof of concept ten million times.

It is not an archive access layer. Objects in Glacier Flexible Retrieval, Glacier Deep Archive and the archive tiers of S3 Intelligent Tiering cannot be accessed through S3 Files until they are restored using the S3 API. If your bucket is a long term archive, S3 Files will not turn cold objects into immediately mountable working files.

It is not full NFS with every optional feature available. Test the actual application, not just basic file operations such as touch, cat and ls, because the gaps below are easy to miss until something in your stack quietly depends on one of them.

Unsupported NFS featureWhy it matters
pNFSParallel NFS layout distribution is not available
Client delegation and callbacksClients cannot be granted exclusive local caching authority
Mandatory lockingOnly advisory locking semantics are supported
NFS ACLsAccess control is POSIX permissions only, not ACL based
Kerberos based securityNo Kerberos authentication for the NFS mount
The nconnect mount optionYou cannot multiplex a single mount over several TCP connections
Server side copyCopy operations are not offloaded server side
Sparse file operationsSparse file semantics are not preserved
Custom user defined extended attributesOnly the S3 Files status attribute is exposed, not arbitrary xattrs

None of this makes S3 Files weak. It simply means the protocol surface is deliberately narrower than a full NFS server, in exchange for the whole thing being managed and anchored on S3.

4. Why this is better than the old pattern

The old pattern was usually ugly. An application would read from S3, copy data to local disk, mutate files, upload the result and then clean up. In a batch job that was irritating but manageable. In an application running across many nodes, an ML training pipeline, a media pipeline or an agentic AI workflow, it became a whole architecture made of staging buckets, temporary disks, synchronisation jobs, lifecycle rules, retries, locking conventions and error recovery scripts.

S3 Files replaces customer built synchronisation glue with a managed, observable, asynchronous synchronisation contract, rather than removing synchronisation as a system property altogether. The application can use ordinary file operations while the data remains anchored in S3. The file system can be mounted by multiple compute resources, including EC2, ECS, EKS and Lambda, and it supports concurrent access from multiple compute resources with NFS close to open consistency, according to the AWS launch blog. That makes it a considerably better fit for collaborative workloads than a single node object mount or a custom loop that downloads, processes and uploads in sequence. The synchronisation still exists, and still has real limits, as sections 6 and 21 cover, but it is now something you can observe and alarm on rather than something you wrote yourself and hoped would hold.

The second improvement is that it avoids full data duplication. S3 Files copies only the active working set onto the high performance layer. AWS says small, frequently accessed files can be served from that layer at latencies from under a millisecond to a few milliseconds, while large reads are streamed directly from S3 at up to terabytes per second of aggregate throughput. That is exactly the split you want. Low latency for interactive file work, high throughput for large object reads, and no requirement to preload an entire lake before you can use it.

The third improvement is operational. S3 Files publishes CloudWatch metrics for storage, inodes, pending exports, import failures, export failures, data reads and writes, metadata reads and writes, lost and found files, and client connections. The client also publishes connectivity metrics showing whether the NFS connection is accessible, whether the S3 bucket is accessible, and whether the S3 bucket is reachable from the client. That means synchronisation lag, permission errors, conflicts and client connectivity problems can be monitored directly instead of inferred after the fact from application failures.

5. Why you should care

You should care if you have ever written code whose real job was not business logic but moving files between object storage and file systems. You should care if your Python library, shell tool, ML framework, video tool, indexer or enterprise application expects paths rather than S3 URIs. You should care if your organisation has built a data lake in S3 but still runs tools that behave as though the world is a directory tree.

The biggest immediate use case is agentic AI. Agents like files. They explore directories, read text, write temporary outputs, call command line tools, patch files and chain tools that were never designed for object APIs. A bucket full of documents, logs, prompts, code, configuration or generated artefacts becomes far more useful once many agents and jobs can walk it as a file system without every tool needing to be rewritten against the S3 API.

The second use case is ML training and data preparation. Many training loops still assume a directory of files, particularly for image, text, media and small record datasets. S3 Files lets you keep the dataset in S3 while preloading or caching the hot subset that benefits from lower latency. AWS gives an ML example where repeated reads of thousands of small files can use a higher size threshold, such as 10 MiB, with directory first import and a short expiration window once the training job has completed.

The third use case is enterprise modernisation. Many old applications do not need to become cloud native in one heroic rewrite. They need a compatible file surface over cloud native storage so they can move first and be rewritten later, if the rewrite is still justified once they have moved. S3 Files gives you that middle ground. Retain S3 as the system of record, but give file oriented code a path based interface into it.

The fourth use case is shared operational tooling. Think of incident bundles, reconciliation files, generated reports, support artefacts, fraud evidence packages, model outputs or risk exports. S3 is often where this data should live, but humans and tools generally want to browse and mutate a file tree. S3 Files gives those workflows a cleaner interface without forcing every participant to speak the S3 API directly.

6. The architecture in more detail

A simple S3 Files architecture has an S3 bucket, an S3 file system scoped to the bucket or a prefix, one mount target per availability zone where compute runs, and one or more compute clients mounting the file system. All of this sits inside a VPC (Virtual Private Cloud, your own logically isolated network within AWS), which is why the mount target has a private address rather than a public one. When you mount from EC2, the mount helper uses the instance profile credentials, starts the proxy process, establishes a TLS encrypted connection to the mount target, and starts the mount watchdog process. AWS documents the mount helper as defining a new file system type called s3files, compatible with the standard Linux mount command.

The network path matters more than it might first appear. A mount target is created in a subnet and has security groups attached. The compute security group must be able to send outbound TCP 2049 to the mount target security group, and the mount target security group must allow inbound TCP 2049 from the compute security group. If this is wrong, the failure looks like an NFS timeout rather than an S3 error, which is exactly the kind of thing that eats an afternoon if you have not documented it in advance.

The IAM model has two sides. First, the S3 Files service needs an IAM role it can assume to read from and write to the bucket, manage synchronisation, and manage the EventBridge rule (AWS’s event routing service, used here purely as internal plumbing to detect changes on the S3 side) it uses to detect S3 side changes. AWS documents the service principal as elasticfilesystem.amazonaws.com, with a trust policy scoped by source account and S3 Files source ARN. Second, the compute resource mounting the file system needs permissions such as s3files:ClientMount, optionally s3files:ClientWrite, optionally root access, and direct S3 read permissions if you want the client side direct read optimisation to work.

Synchronisation is asynchronous, but predictably so, which means you can design around it rather than being surprised by it. Writes go to the high performance layer and are durable immediately, but S3 Files waits about sixty seconds to aggregate successive changes before exporting the result back to S3. AWS documents up to 800 exported files per second per file system and up to 2,700 MB per second of export throughput, while import from S3 can process up to 2,400 object changes per second per file system with up to 700 MB per second of import throughput.

That means S3 Files is genuinely useful, but it is not a synchronous S3 object write through layer. If your application writes a file and another process immediately calls the S3 copy command expecting to read the object through the S3 API, you need to account for the batching window. If your application reads back through the mounted file system, the file is visible there immediately. If another system reads through S3, the object appears once it has been exported.

7. Configuration options that actually matter

The first configuration option is the file system scope. You can create a file system over a whole bucket or over a prefix. Prefix scoping is usually the better choice because it reduces blast radius, reduces accidental traversal, reduces rename risk and makes access control simpler. AWS explicitly recommends scoping a file system to the smallest prefix your workload needs, partly because directory renames over large prefixes can become expensive and slow at the S3 synchronisation layer.

The second option is the import rule. An import data rule has a prefix, a trigger and a size threshold. The trigger can be set to import file data when a directory is first accessed, or only when a file is first read. The size threshold controls which files have data imported onto the high performance layer, with a default of 131,072 bytes, or 128 KiB. You can set it to zero for metadata only browsing, or raise it for workloads that repeatedly read larger small files.

The third option is the expiration rule. This controls when unused data is removed from the high performance layer once it has not been read for a period of time. AWS documents this window as ranging from one to 365 days, with a default of thirty days. Short lived batch jobs may want one to seven days. Developer file shares may want thirty days. Reused ML datasets may want thirty to ninety days.

The fourth option is access points. Access points let you enforce a POSIX user identity and a root directory for a particular application. This is extremely useful in shared environments because you can avoid every application mounting the entire file system under its own arbitrary Linux user context. The AWS CLI supports creating access points with a POSIX user and a root directory, including creation permissions for the access point root path.

The fifth option is encryption. Your S3 bucket must use SSE-S3 or SSE-KMS, and S3 Files encrypts data at rest using AWS KMS (Key Management Service, AWS’s managed service for creating and controlling the encryption keys behind your data) keys, with AWS owned keys by default or customer managed keys if you specify them. For a production environment, customer managed KMS keys are usually preferable because they make ownership, auditing and key lifecycle decisions explicit rather than implicit. AWS also states that S3 Files encrypts data in transit using TLS between the client and the mount target.

The sixth option is network reachability to S3. Direct read routing depends on the client being able to read from the linked S3 bucket. AWS says that if the bucket reachable metric is zero, you should verify that the compute resource has network access to S3, for example through a VPC endpoint or a NAT gateway. In private subnets, you should normally use an S3 gateway VPC endpoint rather than paying NAT gateway costs for S3 traffic that does not need to leave the AWS network in the first place.

8. Basic AWS CLI setup script

The script below creates a minimal S3 Files environment for a test account. It assumes you already have a VPC, subnet and EC2 security group in place. It uses SSE-S3 for simplicity, creates a bucket with versioning enabled, creates the S3 Files service role, creates a mount target security group, creates the S3 file system, applies a synchronisation configuration, and creates a mount target.

Run this in a non production AWS account first. The syntax follows the documented aws s3files command family, including create-file-system, create-mount-target and put-synchronization-configuration.

#!/usr/bin/env bash
set -euo pipefail
# ----------------------------
# User configuration
# ----------------------------
export AWS_PAGER=""
REGION="${REGION:-eu-west-1}"
NAME="${NAME:-s3files-demo}"
BUCKET="${BUCKET:-$NAME-$(date +%s)-$RANDOM}"
# Existing network resources
VPC_ID="${VPC_ID:?Set VPC_ID, for example vpc-0123456789abcdef0}"
SUBNET_ID="${SUBNET_ID:?Set SUBNET_ID in the same VPC, for example subnet-0123456789abcdef0}"
EC2_SG_ID="${EC2_SG_ID:?Set EC2_SG_ID for the EC2 instance that will mount S3 Files}"
ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
BUCKET_ARN="arn:aws:s3:::$BUCKET"
echo "Region:     $REGION"
echo "Account:    $ACCOUNT_ID"
echo "Bucket:     $BUCKET"
echo "VPC:        $VPC_ID"
echo "Subnet:     $SUBNET_ID"
echo "EC2 SG:     $EC2_SG_ID"
# ----------------------------
# 1. Create S3 bucket
# ----------------------------
if [[ "$REGION" == "us-east-1" ]]; then
  aws s3api create-bucket \
    --bucket "$BUCKET" \
    --region "$REGION"
else
  aws s3api create-bucket \
    --bucket "$BUCKET" \
    --region "$REGION" \
    --create-bucket-configuration LocationConstraint="$REGION"
fi
aws s3api put-bucket-versioning \
  --bucket "$BUCKET" \
  --versioning-configuration Status=Enabled
aws s3api put-bucket-encryption \
  --bucket "$BUCKET" \
  --server-side-encryption-configuration '{
    "Rules": [
      {
        "ApplyServerSideEncryptionByDefault": {
          "SSEAlgorithm": "AES256"
        }
      }
    ]
  }'
aws s3api put-public-access-block \
  --bucket "$BUCKET" \
  --public-access-block-configuration '{
    "BlockPublicAcls": true,
    "IgnorePublicAcls": true,
    "BlockPublicPolicy": true,
    "RestrictPublicBuckets": true
  }'
mkdir -p /tmp/s3files-seed/data /tmp/s3files-seed/config
echo "hello from S3 before mounting" > /tmp/s3files-seed/config/hello.txt
dd if=/dev/urandom of=/tmp/s3files-seed/data/1mb.bin bs=1M count=1 status=none
aws s3 sync /tmp/s3files-seed "s3://$BUCKET/"
# ----------------------------
# 2. Create IAM role assumed by S3 Files
# ----------------------------
ROLE_NAME="$NAME-service-role"
cat > /tmp/s3files-trust-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowS3FilesAssumeRole",
      "Effect": "Allow",
      "Principal": {
        "Service": "elasticfilesystem.amazonaws.com"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "aws:SourceAccount": "$ACCOUNT_ID"
        },
        "ArnLike": {
          "aws:SourceArn": "arn:aws:s3files:$REGION:$ACCOUNT_ID:file-system/*"
        }
      }
    }
  ]
}
EOF
aws iam create-role \
  --role-name "$ROLE_NAME" \
  --assume-role-policy-document file:///tmp/s3files-trust-policy.json >/dev/null
cat > /tmp/s3files-service-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "S3BucketPermissions",
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:ListBucketVersions"
      ],
      "Resource": "$BUCKET_ARN",
      "Condition": {
        "StringEquals": {
          "aws:ResourceAccount": "$ACCOUNT_ID"
        }
      }
    },
    {
      "Sid": "S3ObjectPermissions",
      "Effect": "Allow",
      "Action": [
        "s3:AbortMultipartUpload",
        "s3:DeleteObject*",
        "s3:GetObject*",
        "s3:List*",
        "s3:PutObject*"
      ],
      "Resource": "$BUCKET_ARN/*",
      "Condition": {
        "StringEquals": {
          "aws:ResourceAccount": "$ACCOUNT_ID"
        }
      }
    },
    {
      "Sid": "EventBridgeManage",
      "Effect": "Allow",
      "Action": [
        "events:DeleteRule",
        "events:DisableRule",
        "events:EnableRule",
        "events:PutRule",
        "events:PutTargets",
        "events:RemoveTargets"
      ],
      "Condition": {
        "StringEquals": {
          "events:ManagedBy": "elasticfilesystem.amazonaws.com"
        }
      },
      "Resource": [
        "arn:aws:events:*:*:rule/DO-NOT-DELETE-S3-Files*"
      ]
    },
    {
      "Sid": "EventBridgeRead",
      "Effect": "Allow",
      "Action": [
        "events:DescribeRule",
        "events:ListRuleNamesByTarget",
        "events:ListRules",
        "events:ListTargetsByRule"
      ],
      "Resource": [
        "arn:aws:events:*:*:rule/*"
      ]
    }
  ]
}
EOF
aws iam put-role-policy \
  --role-name "$ROLE_NAME" \
  --policy-name "$NAME-service-policy" \
  --policy-document file:///tmp/s3files-service-policy.json
ROLE_ARN="arn:aws:iam::$ACCOUNT_ID:role/$ROLE_NAME"
sleep 15
# ----------------------------
# 3. Create security group for the mount target
# ----------------------------
MT_SG_ID="$(aws ec2 create-security-group \
  --region "$REGION" \
  --group-name "$NAME-mount-target-sg" \
  --description "S3 Files mount target security group" \
  --vpc-id "$VPC_ID" \
  --query GroupId \
  --output text)"
aws ec2 authorize-security-group-ingress \
  --region "$REGION" \
  --group-id "$MT_SG_ID" \
  --protocol tcp \
  --port 2049 \
  --source-group "$EC2_SG_ID"
echo "Created mount target security group: $MT_SG_ID"
# ----------------------------
# 4. Create the S3 file system
# ----------------------------
FS_ID="$(aws s3files create-file-system \
  --region "$REGION" \
  --bucket "$BUCKET_ARN" \
  --role-arn "$ROLE_ARN" \
  --tags key=Name,value="$NAME" key=Environment,value=demo \
  --query fileSystemId \
  --output text)"
echo "Created file system: $FS_ID"
for i in {1..60}; do
  STATUS="$(aws s3files get-file-system \
    --region "$REGION" \
    --file-system-id "$FS_ID" \
    --query status \
    --output text)"
  echo "File system status: $STATUS"
  [[ "$STATUS" == "available" || "$STATUS" == "AVAILABLE" ]] && break
  sleep 10
done
# ----------------------------
# 5. Tune synchronisation
# ----------------------------
aws s3files put-synchronization-configuration \
  --region "$REGION" \
  --file-system-id "$FS_ID" \
  --import-data-rules prefix="",trigger=ON_DIRECTORY_FIRST_ACCESS,sizeLessThan=131072 \
  --expiration-data-rules daysAfterLastAccess=30
# ----------------------------
# 6. Create mount target in the EC2 instance AZ
# ----------------------------
MT_ID="$(aws s3files create-mount-target \
  --region "$REGION" \
  --file-system-id "$FS_ID" \
  --subnet-id "$SUBNET_ID" \
  --security-groups "$MT_SG_ID" \
  --query mountTargetId \
  --output text)"
echo "Created mount target: $MT_ID"
cat > "$NAME.outputs" <<EOF
REGION=$REGION
BUCKET=$BUCKET
FILE_SYSTEM_ID=$FS_ID
MOUNT_TARGET_ID=$MT_ID
MOUNT_TARGET_SG_ID=$MT_SG_ID
SERVICE_ROLE_ARN=$ROLE_ARN
EOF
cat "$NAME.outputs"
echo "Now attach the right client permissions to your EC2 instance role, install amazon-efs-utils 3.0.0 or later, and mount with:"
echo "sudo mkdir -p /mnt/s3files"
echo "sudo mount -t s3files $FS_ID:/ /mnt/s3files"

9. EC2 client setup and mount script

On the EC2 instance, the client needs amazon-efs-utils version 3.0.0 or later. AWS documents amazon-efs-utils as the S3 Files client package, and says the client includes the mount helper as well as CloudWatch mount status monitoring support. It also documents that botocore is needed for the mount helper to interact with AWS services, including CloudWatch monitoring.

Your EC2 instance role should have either the managed policy for S3 Files client full access for a write test, or a more restricted policy with the required permissions. For direct read optimisation, the instance role also needs read access to the linked bucket, since without it the mount helper cannot read directly from S3 and all reads instead go through the file system layer.

#!/usr/bin/env bash
set -euo pipefail
FS_ID="${FS_ID:?Set FS_ID, for example fs-0123456789abcdef0}"
MOUNT_POINT="${MOUNT_POINT:-/mnt/s3files}"
sudo yum -y install amazon-efs-utils jq attr || true
if ! command -v mount.s3files >/dev/null 2>&1; then
  sudo apt-get update || true
  sudo apt-get install -y amazon-efs-utils jq attr || true
fi
if ! command -v mount.s3files >/dev/null 2>&1; then
  echo "mount.s3files not found. Install amazon-efs-utils 3.0.0 or later for your distribution."
  exit 1
fi
sudo mkdir -p "$MOUNT_POINT"
sudo mount -t s3files "$FS_ID:/" "$MOUNT_POINT"
findmnt "$MOUNT_POINT"
df -hT "$MOUNT_POINT"
echo "Mounted $FS_ID at $MOUNT_POINT"

For automatic mounting after reboot, add an fstab entry using the _netdev option. AWS explicitly warns that this option should be used for automatic mounting because network file systems must initialise after networking is available, and missing it can leave the instance unresponsive on boot. It is also worth adding nofail, since that lets the instance complete its boot even if the file system happens to be unreachable at mount time, rather than hanging the boot sequence on a dependency that may be temporarily degraded.

FS_ID="${FS_ID:?Set FS_ID}"
MOUNT_POINT="${MOUNT_POINT:-/mnt/s3files}"
sudo mkdir -p "$MOUNT_POINT"
grep -q "$FS_ID:/" /etc/fstab || echo "$FS_ID:/ $MOUNT_POINT s3files _netdev,nofail 0 0" | sudo tee -a /etc/fstab
sudo systemctl daemon-reload
sudo mount -a
findmnt "$MOUNT_POINT"

10. Access point setup for application isolation

An access point lets you mount a constrained view of the file system with a forced POSIX identity and an optional root directory. That is the safer production pattern for multiple tenants or multiple applications sharing a file system. Rather than allowing every workload to mount the whole file system under whatever user context the process happens to run as, you create one access point per workload and mount through it. AWS documents access points as application specific entry points that can enforce user identities and permissions for requests made through them.

#!/usr/bin/env bash
set -euo pipefail
REGION="${REGION:-eu-west-1}"
FS_ID="${FS_ID:?Set FS_ID}"
AP_ID="$(aws s3files create-access-point \
  --region "$REGION" \
  --file-system-id "$FS_ID" \
  --posix-user uid=1000,gid=1000 \
  --root-directory path=/app,creationPermissions="{ownerUid=1000,ownerGid=1000,permissions=0750}" \
  --tags key=Name,value=s3files-demo-app \
  --query accessPointId \
  --output text)"
echo "Access point: $AP_ID"
sudo mkdir -p /mnt/s3files-app
sudo mount -t s3files -o accesspoint="$AP_ID" "$FS_ID" /mnt/s3files-app
findmnt /mnt/s3files-app

11. Basic functional test script

This test proves four things. First, that data uploaded directly to S3 can be seen through the mount. Second, that data written through the mount eventually appears in S3. Third, that ordinary file operations work through the mounted file system. Fourth, that metadata and synchronisation are not instantaneous in both directions, which means tests must poll rather than assume synchronous object visibility.

#!/usr/bin/env bash
set -euo pipefail
BUCKET="${BUCKET:?Set BUCKET}"
MOUNT_POINT="${MOUNT_POINT:-/mnt/s3files}"
TEST_ID="$(date +%Y%m%d%H%M%S)"
S3_KEY="tests/$TEST_ID/from-s3.txt"
FS_FILE="$MOUNT_POINT/tests/$TEST_ID/from-fs.txt"
echo "Test 1: write to S3, read from file system"
aws s3api put-object \
  --bucket "$BUCKET" \
  --key "$S3_KEY" \
  --body <(echo "written directly to S3 at $TEST_ID") >/dev/null
mkdir -p "$MOUNT_POINT/tests/$TEST_ID"
echo "Polling for the S3 created object to appear in the mounted file system..."
for i in {1..60}; do
  if [[ -f "$MOUNT_POINT/$S3_KEY" ]]; then
    cat "$MOUNT_POINT/$S3_KEY"
    break
  fi
  sleep 2
done
if [[ ! -f "$MOUNT_POINT/$S3_KEY" ]]; then
  echo "Object did not appear in the file system within the polling window."
  exit 1
fi
echo "Test 2: write to file system, read from S3"
echo "written through S3 Files at $TEST_ID" > "$FS_FILE"
sync
echo "File exists immediately through the file system:"
ls -l "$FS_FILE"
cat "$FS_FILE"
echo "Polling for the file system created object to appear in S3..."
for i in {1..90}; do
  if aws s3api head-object --bucket "$BUCKET" --key "tests/$TEST_ID/from-fs.txt" >/dev/null 2>&1; then
    aws s3 cp "s3://$BUCKET/tests/$TEST_ID/from-fs.txt" -
    break
  fi
  sleep 2
done
if ! aws s3api head-object --bucket "$BUCKET" --key "tests/$TEST_ID/from-fs.txt" >/dev/null 2>&1; then
  echo "File did not export to S3 within the polling window."
  exit 1
fi
echo "Test 3: rename and verify the eventual S3 key change"
mv "$FS_FILE" "$MOUNT_POINT/tests/$TEST_ID/from-fs-renamed.txt"
for i in {1..90}; do
  if aws s3api head-object --bucket "$BUCKET" --key "tests/$TEST_ID/from-fs-renamed.txt" >/dev/null 2>&1; then
    echo "Renamed key exists in S3."
    break
  fi
  sleep 2
done
echo "Test complete"

12. Performance and behaviour test script

Do not treat this as a benchmark suitable for vendor comparison. Treat it instead as a behaviour test that tells you whether your workload is doing lots of metadata operations, lots of tiny writes, large sequential reads, or repeated small file reads. That matters because S3 Files uses minimum metering sizes for file and metadata operations, while direct S3 streaming is favoured for large reads. AWS documents a 32 KiB minimum for file reads and writes, a 4 KiB minimum for metadata operations, and direct S3 streaming for large reads.

#!/usr/bin/env bash
set -euo pipefail
MOUNT_POINT="${MOUNT_POINT:-/mnt/s3files}"
TEST_DIR="$MOUNT_POINT/bench/$(date +%Y%m%d%H%M%S)"
mkdir -p "$TEST_DIR"
echo "Benchmark directory: $TEST_DIR"
echo "Metadata: create 10,000 small files"
time bash -c '
for i in $(seq 1 10000); do
  printf "hello-%05d\n" "$i" > "'"$TEST_DIR"'/file-$i.txt"
done
'
echo "Metadata: list directory"
time ls "$TEST_DIR" >/dev/null
echo "Small file repeated read"
time bash -c '
for round in $(seq 1 5); do
  for f in "'"$TEST_DIR"'"/file-*.txt; do
    cat "$f" >/dev/null
  done
done
'
echo "Large sequential write"
time dd if=/dev/zero of="$TEST_DIR/large-512mb.bin" bs=8M count=64 conv=fdatasync status=progress
echo "Large sequential read"
time dd if="$TEST_DIR/large-512mb.bin" of=/dev/null bs=8M status=progress
echo "Rename a 10,000 file directory locally"
time mv "$TEST_DIR" "${TEST_DIR}-renamed"
echo "Done. Now watch PendingExports and DataWriteBytes in CloudWatch."

13. Synchronisation configuration examples

The default configuration is sensible for a general shared file tree. Import metadata and small file data on first directory access, then expire unused high performance data after thirty days. You should change it when your workload is obviously not general in this way. AWS supports up to ten import data rules per file system, and when multiple rules match a file, the most specific prefix wins.

13.1 Agentic browsing workload

Use this when agents need to discover many files but rarely reread the same content. It imports metadata, but does not import file data onto the high performance layer.

aws s3files put-synchronization-configuration \
  --region "$REGION" \
  --file-system-id "$FS_ID" \
  --import-data-rules prefix="",trigger=ON_FILE_ACCESS,sizeLessThan=0 \
  --expiration-data-rules daysAfterLastAccess=1

13.2 ML small file training workload

Use this when training repeatedly reads small examples over multiple epochs. It imports files under 10 MiB when a directory is first accessed, then expires the active data once the training period has ended.

aws s3files put-synchronization-configuration \
  --region "$REGION" \
  --file-system-id "$FS_ID" \
  --import-data-rules prefix="",trigger=ON_DIRECTORY_FIRST_ACCESS,sizeLessThan=10485760 \
  --expiration-data-rules daysAfterLastAccess=3

13.3 Hot and cold prefixes

Use this when a bucket has hot configuration or feature files under one prefix and cold archive like data under another. The hot prefix gets aggressive import, while the cold prefix remains metadata first and cheap to browse.

aws s3files put-synchronization-configuration \
  --region "$REGION" \
  --file-system-id "$FS_ID" \
  --import-data-rules \
    prefix="",trigger=ON_FILE_ACCESS,sizeLessThan=0 \
    prefix="config/",trigger=ON_DIRECTORY_FIRST_ACCESS,sizeLessThan=1048576 \
    prefix="archive/",trigger=ON_FILE_ACCESS,sizeLessThan=0 \
  --expiration-data-rules daysAfterLastAccess=30

14. Operational monitoring model

You should monitor S3 Files as a synchronisation system, a file system, and a client mounted network dependency, because those are three genuinely different failure modes. If you only monitor EC2 disk usage, you will miss S3 export failures. If you only monitor S3 request errors, you will miss lost and found conflicts. If you only monitor application logs, you will discover mount failures only after your customers have already discovered them.

The minimum CloudWatch metrics worth alarming on are set out below.

MetricNamespaceAlarm conditionWhy it matters
ExportFailuresAWS/S3/FilesGreater than zeroFiles failed terminal export back to S3
ImportFailuresAWS/S3/FilesGreater than zeroS3 side objects failed import into the file system
PendingExportsAWS/S3/FilesGrowing for a sustained periodFile system writes are outpacing synchronisation
LostAndFoundFilesAWS/S3/FilesGreater than zeroConflicting writes occurred between S3 and the file system
StorageBytesAWS/S3/FilesUnexpected growthActive data or lost and found data is accumulating cost
InodesAWS/S3/FilesUnexpected growthMetadata footprint is growing
NFSConnectionAccessibleefs-utils/S3FilesEquals zeroClient cannot reach the file system mount
S3BucketAccessibleefs-utils/S3FilesEquals zeroClient lacks S3 permissions for direct reads
S3BucketReachableefs-utils/S3FilesEquals zeroClient cannot reach the bucket or prefix

AWS documents S3 Files metrics in the AWS/S3/Files namespace with FileSystemId as the dimension, and client connectivity metrics in the efs-utils/S3Files namespace emitted by amazon-efs-utils. Most S3 Files metrics are sent at one minute intervals, while storage metrics are sent every fifteen minutes.

15. CloudWatch alarm creation script

This script creates conservative alarms. Tune the thresholds for your own environment, and in production connect the alarms to an SNS topic or whatever notification path actually wakes the right team.

#!/usr/bin/env bash
set -euo pipefail
REGION="${REGION:-eu-west-1}"
FS_ID="${FS_ID:?Set FS_ID}"
SNS_TOPIC_ARN="${SNS_TOPIC_ARN:-}"
ALARM_ACTIONS=()
if [[ -n "$SNS_TOPIC_ARN" ]]; then
  ALARM_ACTIONS=(--alarm-actions "$SNS_TOPIC_ARN")
fi
put_alarm() {
  local name="$1"
  local metric="$2"
  local statistic="$3"
  local period="$4"
  local evals="$5"
  local threshold="$6"
  local comparison="$7"
  local description="$8"
  aws cloudwatch put-metric-alarm \
    --region "$REGION" \
    --alarm-name "$name" \
    --alarm-description "$description" \
    --namespace "AWS/S3/Files" \
    --metric-name "$metric" \
    --dimensions Name=FileSystemId,Value="$FS_ID" \
    --statistic "$statistic" \
    --period "$period" \
    --evaluation-periods "$evals" \
    --threshold "$threshold" \
    --comparison-operator "$comparison" \
    --treat-missing-data notBreaching \
    "${ALARM_ACTIONS[@]}"
}
put_alarm \
  "s3files-$FS_ID-export-failures" \
  "ExportFailures" "Sum" 60 1 0 "GreaterThanThreshold" \
  "S3 Files export failures detected. Files may not have reached S3."
put_alarm \
  "s3files-$FS_ID-import-failures" \
  "ImportFailures" "Sum" 60 1 0 "GreaterThanThreshold" \
  "S3 Files import failures detected. S3 objects may not be visible through the file system."
put_alarm \
  "s3files-$FS_ID-lost-and-found" \
  "LostAndFoundFiles" "Sum" 60 1 0 "GreaterThanThreshold" \
  "S3 Files lost and found contains conflict files."
put_alarm \
  "s3files-$FS_ID-pending-exports-high" \
  "PendingExports" "Sum" 60 15 1000 "GreaterThanThreshold" \
  "S3 Files pending exports have remained high for 15 minutes."
put_alarm \
  "s3files-$FS_ID-storage-50gb" \
  "StorageBytes" "Maximum" 900 4 53687091200 "GreaterThanThreshold" \
  "S3 Files high performance storage exceeds 50 GiB."
echo "Created S3 Files alarms for $FS_ID"

16. CloudWatch dashboard script

This creates a dashboard showing active storage, metadata footprint, pending synchronisation, failed synchronisation, throughput, metadata activity, conflicts and client count.

#!/usr/bin/env bash
set -euo pipefail
REGION="${REGION:-eu-west-1}"
FS_ID="${FS_ID:?Set FS_ID}"
DASHBOARD_NAME="${DASHBOARD_NAME:-s3files-$FS_ID}"
cat > /tmp/s3files-dashboard.json <<EOF
{
  "widgets": [
    {
      "type": "metric", "x": 0, "y": 0, "width": 12, "height": 6,
      "properties": {
        "region": "$REGION",
        "title": "S3 Files active storage and inodes",
        "view": "timeSeries",
        "stacked": false,
        "metrics": [
          [ "AWS/S3/Files", "StorageBytes", "FileSystemId", "$FS_ID", { "stat": "Maximum" } ],
          [ ".", "Inodes", ".", ".", { "stat": "Sum", "yAxis": "right" } ]
        ],
        "period": 900
      }
    },
    {
      "type": "metric", "x": 12, "y": 0, "width": 12, "height": 6,
      "properties": {
        "region": "$REGION",
        "title": "Synchronisation health",
        "view": "timeSeries",
        "metrics": [
          [ "AWS/S3/Files", "PendingExports", "FileSystemId", "$FS_ID", { "stat": "Sum" } ],
          [ ".", "ImportFailures", ".", ".", { "stat": "Sum" } ],
          [ ".", "ExportFailures", ".", ".", { "stat": "Sum" } ],
          [ ".", "LostAndFoundFiles", ".", ".", { "stat": "Sum" } ]
        ],
        "period": 60
      }
    },
    {
      "type": "metric", "x": 0, "y": 6, "width": 12, "height": 6,
      "properties": {
        "region": "$REGION",
        "title": "Data throughput",
        "view": "timeSeries",
        "metrics": [
          [ "AWS/S3/Files", "DataReadBytes", "FileSystemId", "$FS_ID", { "stat": "Sum" } ],
          [ ".", "DataWriteBytes", ".", ".", { "stat": "Sum" } ]
        ],
        "period": 60
      }
    },
    {
      "type": "metric", "x": 12, "y": 6, "width": 12, "height": 6,
      "properties": {
        "region": "$REGION",
        "title": "Metadata activity and clients",
        "view": "timeSeries",
        "metrics": [
          [ "AWS/S3/Files", "MetadataReadBytes", "FileSystemId", "$FS_ID", { "stat": "Sum" } ],
          [ ".", "MetadataWriteBytes", ".", ".", { "stat": "Sum" } ],
          [ ".", "ClientConnections", ".", ".", { "stat": "Sum", "yAxis": "right" } ]
        ],
        "period": 60
      }
    }
  ]
}
EOF
aws cloudwatch put-dashboard \
  --region "$REGION" \
  --dashboard-name "$DASHBOARD_NAME" \
  --dashboard-body file:///tmp/s3files-dashboard.json
echo "Created dashboard: $DASHBOARD_NAME"

17. Command line health report script

This script produces a quick operational report for one file system. It fetches recent CloudWatch data, checks whether failed synchronisation or lost and found files are present, shows pending exports, and reports whether active storage is growing.

#!/usr/bin/env bash
set -euo pipefail
REGION="${REGION:-eu-west-1}"
FS_ID="${FS_ID:?Set FS_ID}"
END="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
START="$(date -u -d '30 minutes ago' +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u -v-30M +"%Y-%m-%dT%H:%M:%SZ")"
metric_sum() {
  local metric="$1"
  aws cloudwatch get-metric-statistics \
    --region "$REGION" \
    --namespace AWS/S3/Files \
    --metric-name "$metric" \
    --dimensions Name=FileSystemId,Value="$FS_ID" \
    --start-time "$START" \
    --end-time "$END" \
    --period 60 \
    --statistics Sum \
    --query 'Datapoints[].Sum' \
    --output text | awk '{s=0; for(i=1;i<=NF;i++) s+=$i; print s+0}'
}
metric_max() {
  local metric="$1"
  local period="${2:-60}"
  aws cloudwatch get-metric-statistics \
    --region "$REGION" \
    --namespace AWS/S3/Files \
    --metric-name "$metric" \
    --dimensions Name=FileSystemId,Value="$FS_ID" \
    --start-time "$START" \
    --end-time "$END" \
    --period "$period" \
    --statistics Maximum \
    --query 'Datapoints[].Maximum' \
    --output text | awk '{m=0; for(i=1;i<=NF;i++) if($i>m) m=$i; print m+0}'
}
echo "S3 Files health report"
echo "File system: $FS_ID"
echo "Region:      $REGION"
echo "Window:      $START to $END"
echo
EXPORT_FAILURES="$(metric_sum ExportFailures)"
IMPORT_FAILURES="$(metric_sum ImportFailures)"
PENDING_EXPORTS="$(metric_max PendingExports)"
LOST_AND_FOUND="$(metric_max LostAndFoundFiles)"
STORAGE_BYTES="$(metric_max StorageBytes 900)"
INODES="$(metric_max Inodes 900)"
DATA_READ="$(metric_sum DataReadBytes)"
DATA_WRITE="$(metric_sum DataWriteBytes)"
META_READ="$(metric_sum MetadataReadBytes)"
META_WRITE="$(metric_sum MetadataWriteBytes)"
printf "%-24s %s\n" "ExportFailures:" "$EXPORT_FAILURES"
printf "%-24s %s\n" "ImportFailures:" "$IMPORT_FAILURES"
printf "%-24s %s\n" "PendingExports max:" "$PENDING_EXPORTS"
printf "%-24s %s\n" "LostAndFoundFiles max:" "$LOST_AND_FOUND"
printf "%-24s %s bytes\n" "StorageBytes max:" "$STORAGE_BYTES"
printf "%-24s %s\n" "Inodes max:" "$INODES"
printf "%-24s %s bytes\n" "DataReadBytes sum:" "$DATA_READ"
printf "%-24s %s bytes\n" "DataWriteBytes sum:" "$DATA_WRITE"
printf "%-24s %s bytes\n" "MetadataReadBytes sum:" "$META_READ"
printf "%-24s %s bytes\n" "MetadataWriteBytes sum:" "$META_WRITE"
echo
STATUS="OK"
if [[ "$EXPORT_FAILURES" != "0" ]]; then
  echo "CRITICAL: Export failures detected."
  STATUS="CRITICAL"
fi
if [[ "$IMPORT_FAILURES" != "0" ]]; then
  echo "CRITICAL: Import failures detected."
  STATUS="CRITICAL"
fi
if [[ "$LOST_AND_FOUND" != "0" ]]; then
  echo "WARNING: Lost and found files detected. Resolve conflicts and delete unneeded files."
  [[ "$STATUS" == "OK" ]] && STATUS="WARNING"
fi
if (( ${PENDING_EXPORTS%.*} > 1000 )); then
  echo "WARNING: Pending exports are high. Synchronisation may be falling behind."
  [[ "$STATUS" == "OK" ]] && STATUS="WARNING"
fi
echo "Overall: $STATUS"

18. Client mount health script

CloudWatch is necessary, but local mount health still matters in its own right. This script is designed to run from cron, a systemd timer, SSM Run Command, or whatever node health agent you already operate. It checks the mount, confirms that the file system is writable if you allow writes, checks the watchdog process, and collects the most useful local log entries.

#!/usr/bin/env bash
set -euo pipefail
MOUNT_POINT="${MOUNT_POINT:-/mnt/s3files}"
EXPECT_WRITE="${EXPECT_WRITE:-true}"
echo "S3 Files local mount health"
echo "Mount point: $MOUNT_POINT"
echo
if ! mountpoint -q "$MOUNT_POINT"; then
  echo "CRITICAL: $MOUNT_POINT is not a mount point."
  exit 2
fi
findmnt "$MOUNT_POINT"
df -hT "$MOUNT_POINT"
echo
echo "Checking read access..."
if ! ls "$MOUNT_POINT" >/dev/null; then
  echo "CRITICAL: Cannot list mount point."
  exit 2
fi
if [[ "$EXPECT_WRITE" == "true" ]]; then
  echo "Checking write access..."
  TEST_FILE="$MOUNT_POINT/.s3files-health-$(hostname)-$(date +%s).txt"
  if echo "health check $(date -u)" > "$TEST_FILE"; then
    rm -f "$TEST_FILE"
    echo "Write check passed."
  else
    echo "CRITICAL: Write check failed."
    exit 2
  fi
fi
echo
echo "Checking watchdog or proxy processes..."
ps aux | grep -E 'amazon-efs-mount-watchdog|efs-proxy|stunnel' | grep -v grep || true
echo
echo "Recent mount logs:"
sudo tail -n 50 /var/log/amazon/efs/mount.log 2>/dev/null || echo "No mount.log found."
echo
echo "Health check complete."

19. Lost and found conflict diagnostic script

Conflicts happen when the same logical file is modified through the file system and directly through S3 before S3 Files has had the chance to synchronise the file system side change back to S3. AWS documents that S3 is treated as the source of truth in that scenario, and the conflicting file system version is moved into a lost and found directory named for the file system. Those files remain there indefinitely and continue to count towards file system storage costs until you delete them.

#!/usr/bin/env bash
set -euo pipefail
MOUNT_POINT="${MOUNT_POINT:-/mnt/s3files}"
LOST_DIR="$(find "$MOUNT_POINT" -maxdepth 1 -type d -name '.s3files-lost+found-*' | head -n 1 || true)"
if [[ -z "$LOST_DIR" ]]; then
  echo "No S3 Files lost and found directory found."
  exit 0
fi
echo "Lost and found directory: $LOST_DIR"
echo
find "$LOST_DIR" -type f | while read -r f; do
  echo "File: $f"
  if command -v getfattr >/dev/null 2>&1; then
    getfattr -n "user.s3files.status;$(date -u +%s)" "$f" --only-values 2>/dev/null || true
  else
    echo "Install the attr package to inspect S3 Files extended attributes."
  fi
  echo
done

20. Export failure diagnostic script

When a file written through the file system does not appear in S3 after the expected export window, inspect the file status using the S3 Files extended attribute. AWS documents getfattr -n "user.s3files.status;<timestamp>" as the way to retrieve the latest export status, and possible export errors include access denied, bucket not found, user metadata too large, file size over the S3 limit, an inaccessible KMS key, role assumption failure, path too long, and archived object.

#!/usr/bin/env bash
set -euo pipefail
FILE="${1:?Usage: ./s3files-export-status.sh /mnt/s3files/path/to/file}"
if ! command -v getfattr >/dev/null 2>&1; then
  echo "Install attr first, for example: sudo yum install -y attr"
  exit 1
fi
echo "Inspecting S3 Files export status for: $FILE"
getfattr -n "user.s3files.status;$(date -u +%s)" "$FILE" --only-values

21. Common gotchas

The first gotcha is forgetting S3 versioning. S3 Files requires versioning on the bucket so that it can synchronise changes between the file system and S3. If you try to use an unversioned bucket, fix the bucket first rather than debugging the mount path, because the mount path is not the problem.

The second gotcha is the sixty second write export window. A file written through the mount is immediately visible through the file system, but it is not necessarily immediately visible through the S3 API. AWS documents that S3 Files waits approximately sixty seconds to aggregate successive changes before copying the file to S3, which reduces request cost and version churn but does mean the two views are not perfectly synchronous.

The third gotcha is treating a directory rename as cheap. The rename is fast from the file system perspective, but S3 has no native directories and no atomic rename. When the change is exported to S3, every object under the prefix may require a copy and delete operation. A directory rename containing 100,000 files can take minutes to fully reflect in S3, according to the AWS performance documentation, and much larger trees can become expensive and operationally noisy in the process.

The fourth gotcha is IAM split brain. The S3 Files service role can be correct while the EC2 client role is wrong, or the EC2 client role can mount successfully but still be unable to use direct S3 read routing. If intelligent read routing is not working, AWS advises checking client connectivity metrics and verifying that the compute role has the required read permissions on the linked bucket.

The fifth gotcha is POSIX permissions. IAM can allow the mount while POSIX permissions still deny file access underneath it. AWS documents that S3 Files enforces standard POSIX owner, group and other permissions on files and directories, and recommends access points when you need to enforce a specific user and group identity for all requests.

The sixth gotcha is archived objects. Glacier and deep archive objects are not readable through the file system until restored. This often surprises teams that point S3 Files at an older bucket and assume every key will behave like a hot file.

The seventh gotcha is incompatible S3 keys. Empty path components, relative path components, null bytes, path components longer than 255 bytes, and object keys that do not map cleanly to POSIX paths are not supported through the file system. This is a real migration issue if your bucket was originally populated by systems that treated S3 as a loose key value store rather than as a path like object namespace.

The eighth gotcha is modifying the generated EventBridge rule or service role. AWS recommends not modifying or deleting the S3 Files IAM role used for synchronisation, and not changing the associated EventBridge rule. If you break that plumbing, the file system can become stale because S3 side changes are no longer detected correctly.

The ninth gotcha is logging sensitive key names. AWS notes that efs-utils writes S3 object key names into logs under the EFS log directory, and recommends restricting access to that directory if object keys contain sensitive information. That is easy to miss because engineers often treat mount logs as harmless infrastructure logs rather than as something that might contain sensitive data.

The tenth gotcha is assuming all NFS features work. S3 Files supports NFS v4.1 and v4.2 with exceptions, and those exceptions matter for applications that rely on Kerberos, ACLs, mandatory locking, custom extended attributes, sparse file operations or the nconnect option. Test the actual application, not just basic file operations such as touch, cat and ls.

22. Production design checklist

Use prefix scoped file systems unless you have a strong reason to expose the whole bucket. The smaller the scope, the easier the IAM, the lower the rename blast radius, and the cleaner the operational model overall.

Create mount targets in every availability zone where compute runs. AWS recommends creating a mount target in each availability zone you operate in, both to reduce cross zone data transfer costs and to improve performance. This also avoids confusing failures where the mount works on one node but not another, purely because that node landed in a zone without a local mount target.

Create one access point per application or trust boundary. Enforce POSIX identity through the access point, and use a root directory that maps to the application’s own prefix. That gives you a cleaner file system boundary than relying on every workload to voluntarily stay within its own lane.

Use customer managed KMS keys for regulated workloads, then test key disablement, key policy changes and service role assumption before production. AWS documents inaccessible KMS keys as a possible reason an encrypted file system can return NFS server errors or export failures.

Create CloudWatch alarms before allowing writes. The minimum alarms are export failures, import failures, lost and found files, and sustained pending exports. A production file system without those alarms is really a hidden data movement system with no visible synchronisation health contract.

Choose one primary writer for each path. Either the file system writes the active dataset and S3 side consumers read after export, or S3 side producers write and file system consumers read after import. When both sides write the same path at the same time, S3 wins, and the file system version is moved to lost and found.

Avoid giant mutable directories. If you expect a directory to be renamed, moved or reorganised, keep its object count bounded. S3 Files makes the file system side feel instant, but the S3 side still has to reconcile every object key underneath it.

Tune import rules by observed access patterns rather than by instinct. Start with metadata only for broad discovery, use 128 KiB for general development shares, and reach for larger thresholds only once repeated reads clearly justify the high performance storage cost.

Treat the mount as a dependency in its own right. The application should fail clearly when the mount is missing, degraded, read only or lagging. Add a startup check that validates the mount, performs a basic read, and optionally writes to a dedicated health check path.

23. When I would use S3 Files

I would use S3 Files for agent workspaces built over S3 backed corpora, especially where the agents use normal command line tools, Python libraries or other file based workflows. I would use it for ML training datasets made up of many small files where repeated reads make active set caching genuinely valuable. I would use it for document transformation pipelines where Lambda, ECS or EKS jobs need to mutate files without constantly downloading and uploading objects in a loop. I would use it for shared operational file trees where S3 must remain the durable system of record, but humans and tools still need a filesystem view over the top of it.

I would not use it as a blind replacement for every EFS workload. If the data does not belong in S3 in the first place, EFS may simply be the cleaner answer. I would not use it for HPC workloads that need Lustre semantics and performance patterns, because FSx for Lustre exists precisely for that reason. I would not use it for a bucket full of archived objects until the restore strategy is clear. And I would not use it for workloads that constantly rename massive directory trees, because S3’s object model will eventually present the bill for that decision.

24. The architectural significance

S3 Files matters because it softens a boundary that AWS spent twenty years teaching architects to respect. The old rule was simple. S3 is not a file system. That rule was useful because it stopped people from designing broken systems on top of a false assumption. Objects are immutable. Directories are prefixes. Renames are not atomic. POSIX assumptions leak badly over object storage if you let them.

The new rule is more interesting. S3 is still not a file system, but AWS can now place a real managed file system interface in front of selected S3 data and make that interface operationally credible for a wide range of workloads. That is a considerably more powerful idea than it first sounds. It means S3 can remain the durable centre of gravity while file oriented applications get a genuinely first class path into the same underlying data.

That is why S3 Files matters. It is not simply a storage feature. It is an architectural bridge between the object world and the file world, and a great deal of enterprise cloud architecture has spent years building that bridge badly, by hand, one glue script at a time.

25. Reaching S3 Files from outside AWS

The obvious next question, especially if you run anything on your own hardware, is whether S3 Files can be mounted from a device that is not itself inside AWS. Think a Raspberry Pi talking to another Raspberry Pi, or a Raspberry Pi talking to an EC2 instance, or any on premises machine that wants a genuine POSIX view of a bucket rather than a scripted sync. The honest answer has two parts, because the mount target itself is not internet facing, but there are documented ways to bridge into it.

A mount target is an elastic network interface inside your VPC, listening for NFS on TCP 2049, and by default it is only reachable from inside that VPC. This is exactly the same model EFS has always used, and AWS’s own on premises walkthrough for EFS confirms the pattern directly. Mounting an EFS file system from an on premises client requires either an AWS Direct Connect connection or a connection on an AWS VPN, and all of the VPC, the mount target and the file system must sit in the same AWS region. S3 Files sits on the same EFS based mount target architecture, so the same constraint applies to it. Your Raspberry Pi cannot simply resolve a public hostname and mount a file system the way it might reach an S3 bucket over HTTPS.

There are three practical ways to bridge that gap.

The first is AWS Client VPN or Site to Site VPN. You terminate a VPN connection into the VPC that holds your mount target, route your on premises network or your individual device into that VPC, and then mount exactly as you would from an EC2 instance, using the mount target’s private IP address. A community project that mounts S3 Files from macOS documents this as the recommended production pattern, noting that using AWS Client VPN to connect a client to the VPC is an AWS documented pattern for accessing EFS from on premises, and that it applies equally to S3 Files, which eliminates the need for an internet facing load balancer entirely. For a Raspberry Pi this means running a small VPN client on the Pi itself, which is well within what a Pi 5 can handle, and then treating the mount exactly as you would on any EC2 client.

The second is a Network Load Balancer fronting the mount target on port 2049, which gives you a single stable endpoint you can reach without a full VPN client on every device. The same project builds this for a proof of concept, with a mount target security group that only allows traffic from the load balancer’s security group, and recommends private subnets with a NAT Gateway, or better still a Gateway endpoint for S3, for anything beyond a proof of concept. This is a reasonable shape for a lab setup between two Raspberry Pis and an AWS account you control, but it does put an NFS listener on the internet, so if you go this route, lock the load balancer’s security group down to known source ranges and treat it the way you would treat any exposed NFS service, which is to say cautiously.

The third is a WireGuard or Tailscale style overlay network with one leg inside the VPC, for example a small EC2 instance or a container acting as a relay, and the other leg on your Raspberry Pi fleet. This is the closest match to how you already run andrewbaker.ninja, since your existing Pi already sits behind a Cloudflare Tunnel rather than a raw public listener. The same overlay pattern extends naturally to an S3 Files mount target, without opening anything new to the public internet.

A few things are worth weighing before you reach for any of these. Latency stops being an AWS internal concern and becomes an internet transit concern. Once you are mounting from outside AWS, latency is dominated by internet round trip time rather than by anything AWS controls, so picking the AWS region closest to your actual physical location matters more than it would for two resources that are already inside AWS. Cost also changes shape, since you are now paying for a VPN connection or a load balancer in addition to the usual S3 Files metering, and if you use a public NAT path instead of a VPC gateway endpoint for the underlying S3 traffic, that adds further cost for no real benefit.

Given all of that, I would only reach for a genuine S3 Files mount between a Raspberry Pi and AWS when you actually need POSIX semantics on both ends, meaning file locking, rename behaviour, or multiple writers touching the same tree concurrently. If what you actually want is a Raspberry Pi mirroring artefacts to or from a bucket on a schedule, plain aws s3 sync, rclone, or even Syncthing between two Pis will get you there with far less infrastructure and no VPN to keep alive. S3 Files earns its complexity when the file interface itself is the requirement, not when a simple copy would do the job.

For two Raspberry Pis with no AWS bucket in the picture at all, S3 Files is simply the wrong tool. That is a pure device to device problem, and your existing self hosted Docker stack, or something like Syncthing or Tailscale file sharing, solves it far more directly than routing two Pis through an AWS VPC just to talk to each other.