AWS Favicon Complete Guide

Master favicon deployment on AWS: S3 static hosting, CloudFront CDN distribution, Lambda@Edge dynamic favicons, Route 53 DNS, and AWS best practices.

AWS Favicon Architecture

S3 Bucket
Store favicon files

CloudFront
Global CDN delivery

Route 53
DNS management

Lambda@Edge
Edge processing

S3 Static Website Hosting

Step 1: Create S3 Bucket

Create Bucket via AWS Console

  1. Open S3 Console: https://console.aws.amazon.com/s3/
  2. Click "Create bucket"
  3. Bucket name: your-domain-com
  4. Region: Choose closest to users (e.g., us-east-1)
  5. Uncheck "Block all public access" (for public website)
  6. Acknowledge public access warning
  7. Click "Create bucket"

Enable Static Website Hosting

  1. Select your bucket ? Properties tab
  2. Scroll to "Static website hosting" ? Edit
  3. Enable: Enable
  4. Index document: index.html
  5. Error document: 404.html (optional)
  6. Save changes
  7. Note the endpoint: http://your-bucket.s3-website-us-east-1.amazonaws.com

Upload Favicon Files

# Via AWS CLI
aws s3 cp favicon.ico s3://your-bucket/ --content-type "image/x-icon"
aws s3 cp favicon-16x16.png s3://your-bucket/ --content-type "image/png"
aws s3 cp favicon-32x32.png s3://your-bucket/ --content-type "image/png"
aws s3 cp favicon-96x96.png s3://your-bucket/ --content-type "image/png"
aws s3 cp favicon-512x512.png s3://your-bucket/ --content-type "image/png"
aws s3 cp apple-touch-icon.png s3://your-bucket/ --content-type "image/png"
aws s3 cp site.webmanifest s3://your-bucket/ --content-type "application/manifest+json"

# Set cache control headers
aws s3 cp favicon.ico s3://your-bucket/ \
  --content-type "image/x-icon" \
  --cache-control "public, max-age=31536000, immutable" \
  --metadata-directive REPLACE

Bucket Policy (Public Read)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket/*"
    }
  ]
}
Test: Access http://your-bucket.s3-website-us-east-1.amazonaws.com/favicon.ico

CloudFront CDN Configuration

Step 2: Create CloudFront Distribution

Create Distribution

  1. Open CloudFront Console: https://console.aws.amazon.com/cloudfront/
  2. Click "Create distribution"
  3. Origin domain: Select your S3 bucket website endpoint
  4. Origin path: Leave empty (or /)
  5. Viewer protocol policy: Redirect HTTP to HTTPS
  6. Allowed HTTP methods: GET, HEAD
  7. Cache policy: CachingOptimized (recommended)
  8. Click "Create distribution"

Custom Cache Behavior for Favicons

After distribution is created ? Behaviors tab ? Create behavior:

  • Path pattern: favicon* or *.ico
  • Cache policy: Create custom or use CachingOptimized
  • TTL: Min: 31536000, Max: 31536000, Default: 31536000 (1 year)
  • Compress objects: Yes (for SVG)

Custom Cache Policy (Advanced)

Caching ? Cache policies ? Create cache policy:

Name: FaviconLongCache
Description: 1 year cache for favicon files

TTL Settings:
  Minimum TTL: 31536000 seconds (1 year)
  Maximum TTL: 31536000 seconds
  Default TTL: 31536000 seconds

Cache Key Settings:
  Query strings: None
  Headers: None
  Cookies: None

Compression: Enabled

HTTPS with ACM Certificate

  1. Request certificate in AWS Certificate Manager (ACM)
  2. Region: us-east-1 (required for CloudFront)
  3. Domain: yourdomain.com and *.yourdomain.com
  4. Validate via DNS (add CNAME records)
  5. In CloudFront distribution ? General ? Edit
  6. Alternate domain names (CNAMEs): yourdomain.com
  7. Custom SSL certificate: Select your ACM certificate
Distribution URL: https://d1234abcd.cloudfront.net (takes 10-20 minutes to deploy)

Route 53 DNS Configuration

Step 3: Connect Domain to CloudFront

Create A Record (Alias)

  1. Open Route 53 Console ? Hosted zones
  2. Select your domain
  3. Click "Create record"
  4. Record name: Leave blank (for root) or www
  5. Record type: A - IPv4 address
  6. Alias: Yes
  7. Route traffic to: Alias to CloudFront distribution
  8. Choose distribution: Select your CloudFront distribution
  9. Click "Create records"

Example DNS Configuration

Name Type Alias Value
@ A Yes d1234abcd.cloudfront.net
www A Yes d1234abcd.cloudfront.net
Verification: Wait 5-60 minutes for DNS propagation. Test with nslookup yourdomain.com

Lambda@Edge Dynamic Favicons

Advanced: Edge Functions

Use Cases

  • Serve different favicon per region/country
  • A/B testing different favicons
  • Custom cache headers per client
  • Redirect old favicon paths

Example: Origin Request Function

'use strict';

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const uri = request.uri;
    
    // Redirect /favicon.ico to /favicons/default.ico
    if (uri === '/favicon.ico') {
        request.uri = '/favicons/default.ico';
    }
    
    // Serve favicon based on country
    const headers = request.headers;
    const country = headers['cloudfront-viewer-country'] 
        ? headers['cloudfront-viewer-country'][0].value 
        : 'US';
    
    if (uri.startsWith('/favicon')) {
        // Different favicon per country
        const countryFavicons = {
            'US': '/favicons/us-favicon.ico',
            'GB': '/favicons/uk-favicon.ico',
            'DE': '/favicons/de-favicon.ico'
        };
        
        request.uri = countryFavicons[country] || '/favicon.ico';
    }
    
    callback(null, request);
};

Deploy Lambda@Edge Function

  1. Create Lambda function in us-east-1 region (required)
  2. Runtime: Node.js 18.x
  3. Paste code above
  4. Deploy function
  5. Publish new version (Actions ? Publish new version)
  6. Copy ARN with version number
  7. In CloudFront ? Behaviors ? Edit
  8. Function associations ? Origin request ? Paste Lambda ARN
  9. Save and wait for deployment
Note: Lambda@Edge runs in all CloudFront edge locations. Test thoroughly before deploying.

CloudFront Cache Invalidation

Update Cached Favicons

Method 1: AWS Console

  1. Open CloudFront Console
  2. Select your distribution
  3. Invalidations tab ? Create invalidation
  4. Object paths:
    /favicon.ico
    /favicon-*.png
    /apple-touch-icon.png
  5. Click "Create invalidation"
  6. Wait 5-15 minutes for completion

Method 2: AWS CLI

# Invalidate specific files
aws cloudfront create-invalidation \
  --distribution-id E1234ABCD5678 \
  --paths "/favicon.ico" "/favicon-16x16.png" "/favicon-32x32.png"

# Invalidate all favicon files
aws cloudfront create-invalidation \
  --distribution-id E1234ABCD5678 \
  --paths "/favicon*" "/*favicon*"

# Check invalidation status
aws cloudfront get-invalidation \
  --distribution-id E1234ABCD5678 \
  --id I2J4K6L8M0N2

Method 3: Versioned URLs (Recommended)

Instead of invalidation, use versioned filenames:

  • Upload new file: favicon-v2.ico
  • Update HTML: <link rel="icon" href="/favicon-v2.ico">
  • No invalidation needed (new URL)
  • Free (invalidations cost $0.005 per path after first 1000/month)
Cost Tip: First 1,000 invalidation paths per month are free. After that, $0.005 per path.

Infrastructure as Code (Terraform)

Automate AWS Setup

Complete Terraform Configuration

# S3 Bucket
resource "aws_s3_bucket" "website" {
  bucket = "your-domain-com"
}

resource "aws_s3_bucket_website_configuration" "website" {
  bucket = aws_s3_bucket.website.id

  index_document {
    suffix = "index.html"
  }
}

resource "aws_s3_bucket_public_access_block" "website" {
  bucket = aws_s3_bucket.website.id

  block_public_acls       = false
  block_public_policy     = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}

resource "aws_s3_bucket_policy" "website" {
  bucket = aws_s3_bucket.website.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "PublicReadGetObject"
        Effect    = "Allow"
        Principal = "*"
        Action    = "s3:GetObject"
        Resource  = "${'$'}{aws_s3_bucket.website.arn}/*"
      }
    ]
  })
}

# CloudFront Distribution
resource "aws_cloudfront_distribution" "website" {
  enabled             = true
  is_ipv6_enabled     = true
  default_root_object = "index.html"
  aliases             = ["yourdomain.com", "www.yourdomain.com"]

  origin {
    domain_name = aws_s3_bucket_website_configuration.website.website_endpoint
    origin_id   = "S3-Website"

    custom_origin_config {
      http_port              = 80
      https_port             = 443
      origin_protocol_policy = "http-only"
      origin_ssl_protocols   = ["TLSv1.2"]
    }
  }

  default_cache_behavior {
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = "S3-Website"
    viewer_protocol_policy = "redirect-to-https"
    compress               = true

    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }

    min_ttl     = 0
    default_ttl = 86400
    max_ttl     = 31536000
  }

  # Custom cache for favicons
  ordered_cache_behavior {
    path_pattern           = "favicon*"
    allowed_methods        = ["GET", "HEAD"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = "S3-Website"
    viewer_protocol_policy = "redirect-to-https"
    compress               = true

    forwarded_values {
      query_string = false
      cookies {
        forward = "none"
      }
    }

    min_ttl     = 31536000
    default_ttl = 31536000
    max_ttl     = 31536000
  }

  viewer_certificate {
    acm_certificate_arn      = aws_acm_certificate.cert.arn
    ssl_support_method       = "sni-only"
    minimum_protocol_version = "TLSv1.2_2021"
  }

  restrictions {
    geo_restriction {
      restriction_type = "none"
    }
  }
}

# ACM Certificate
resource "aws_acm_certificate" "cert" {
  provider          = aws.us-east-1
  domain_name       = "yourdomain.com"
  validation_method = "DNS"

  subject_alternative_names = ["www.yourdomain.com"]
}

# Route 53 Records
resource "aws_route53_record" "website" {
  zone_id = var.hosted_zone_id
  name    = "yourdomain.com"
  type    = "A"

  alias {
    name                   = aws_cloudfront_distribution.website.domain_name
    zone_id                = aws_cloudfront_distribution.website.hosted_zone_id
    evaluate_target_health = false
  }
}

AWS Favicon Best Practices

? Recommendations

  • Use CloudFront for global delivery
  • Set cache TTL to 1 year for favicons
  • Enable HTTPS with ACM certificates (free)
  • Use S3 bucket versioning for rollback
  • Set proper Content-Type headers
  • Use versioned URLs instead of invalidations
  • Enable CloudFront compression (for SVG)
  • Monitor with CloudWatch metrics
  • Use Terraform/CloudFormation for IaC

? Common Mistakes

  • Forgetting to make S3 bucket public
  • Not setting Cache-Control headers
  • Using S3 directly (bypass CloudFront)
  • ACM certificate in wrong region (must be us-east-1)
  • Not compressing SVG files
  • Excessive CloudFront invalidations (costs)
  • Missing Route 53 alias records
  • Not enabling HTTPS
  • Ignoring CloudFront cache statistics

Monitoring & Costs

AWS Metrics & Pricing

CloudWatch Metrics

  • Requests: Total favicon requests
  • BytesDownloaded: Data transfer
  • CacheHitRate: Percentage served from cache
  • 4xxErrorRate / 5xxErrorRate: Errors

Estimated Monthly Costs (Free Tier)

Service Free Tier After Free Tier
S3 Storage 5 GB (12 months) $0.023/GB/month
S3 Requests 20,000 GET $0.0004 per 1000
CloudFront Data 1 TB (12 months) $0.085/GB
CloudFront Requests 10M (12 months) $0.0075 per 10,000
Route 53 $0.50/hosted zone $0.50/month
Typical Cost: $1-5/month for small-medium website with favicons on AWS Free Tier. After free tier: $5-20/month depending on traffic.

Generate AWS-Ready Favicons

Create favicon packages optimized for Amazon Web Services deployment

Generate Favicons

Related Articles

An unhandled error has occurred. Reload 🗙