Skip to content

Fix: CloudFront Invalidation for Filenames with Apostrophes

In highly dynamic AWS environments, automated CI/CD pipelines or CMS systems often upload assets to S3 with special characters. A common issue occurs when attempting to invalidate a CloudFront cache for a filename containing an apostrophe (e.g., user's_profile.png).

The challenge stems from how the shell (Bash/Zsh) handles single quotes and how the AWS CLI parses the --paths argument. If not handled correctly, the CloudFront API receives a malformed path, leading to a failed invalidation or a silent failure where the cache is never cleared.

When running a standard AWS CLI command, you may encounter a shell-level error or an API response like this:

Terminal window
# Attempting a direct CLI invalidation
aws cloudfront create-invalidation --distribution-id E123456789ABCD --paths "/images/o'reilly.jpg"
# Potential Error Output
An error occurred (InvalidArgument) when calling the CreateInvalidation operation:
The parameter Paths contains a character that is not allowed or the request is malformed.

Or, if using an unescaped string in a shell script:

Terminal window
bash: syntax error near unexpected token `)'
  1. S3 Key Verification: Confirm the object key in the S3 bucket actually contains the literal ' character and not a URL-encoded equivalent (like %27).
  2. Shell Interpretation: Determine if your shell is stripping the apostrophe before passing the string to the AWS CLI.
  3. Encoding Standards: CloudFront requires paths to be UTF-8. If the apostrophe is part of a multi-byte character sequence, the invalidation will fail.
  4. IAM Permissions: Ensure the execution role has cloudfront:CreateInvalidation permissions for the specific distribution ARN.
  5. Path Wildcards: Check if using a wildcard (e.g., /images/*) is a viable workaround, though this may incur higher costs if done frequently.
Section titled “Method 1: Using a JSON Invalidation Batch (Recommended)”

The most robust way to handle special characters in a Senior Cloud Architect’s workflow is to bypass shell escaping issues entirely by using a JSON input file. This ensures the apostrophe is treated as a literal string within the JSON parser.

  1. Create a file named invalidation-batch.json:
{
"Paths": {
"Quantity": 1,
"Items": [
"/assets/images/o'reilly_book.png"
]
},
"CallerReference": "invalidation-manual-$(date +%s)"
}
  1. Execute the invalidation using the --invalidation-batch flag:
Terminal window
aws cloudfront create-invalidation \
--distribution-id EXXXXXXXXXXXXX \
--invalidation-batch file://invalidation-batch.json

Method 2: Proper Shell Escaping (Bash/Zsh)

Section titled “Method 2: Proper Shell Escaping (Bash/Zsh)”

If you must use the CLI inline (e.g., inside a Jenkins or GitHub Actions runner), use double quotes around the path and escape the apostrophe, or wrap the whole path string carefully.

Terminal window
# Wrap the path in double quotes to allow the single quote to be literal
aws cloudfront create-invalidation --distribution-id EXXXXXXXXXXXXX \
--paths "/products/men's-apparel.pdf"

In some environments, S3 objects are stored with URL-encoded names. If the file is literally named cat%27s.jpg on S3, you must invalidate that exact string. However, if it is named cat's.jpg, CloudFront usually expects the literal character.

If you are automating this via a Python/Boto3 Lambda function, ensure you are not double-encoding:

import boto3
import time
client = boto3.client('cloudfront')
def invalidate_path(dist_id, path_with_apostrophe):
# path_with_apostrophe = "/images/o'reilly.jpg"
response = client.create_invalidation(
DistributionId=dist_id,
InvalidationBatch={
'Paths': {
'Quantity': 1,
'Items': [path_with_apostrophe]
},
'CallerReference': str(time.time())
}
)
return response

To verify the invalidation was accepted and is processing:

Terminal window
aws cloudfront list-invalidations --distribution-id EXXXXXXXXXXXXX --max-items 1

Look for the Status: "InProgress" or "Completed". If the status is Completed but the file still shows the old version, verify that the path provided exactly matches the object key in S3, including leading slashes.