Server-less HTML5 Export Hosting - CloudFront/S3

Assumptions

Projects I’ve tested with this workflow have the following configuration considerations:

  1. Compatibility rendering, have also been able to take some Forward+ projects back to Compatibility via Project Settings depending on add-ons and graphical complexity
  2. Use of the default Web (Runnable) export option with these settings:
    • Extensions Support - On
    • Thread Support - On
    • Progressive Web App → Enabled - On
    • Ensure Cross Origin Isolation Headers - On
    • Display - Browser
    • Resources → Export Mode → All Files
    • Scripts → GDScript Export Mode - Binary Tokens

In terms of Control/2D-driven projects I’ve had great success with projects spanning Forward+/Compatibility. For 3D-driven projects, I converted the Cogito project from Forward+ to Compatibility before exporting and it works well sans some lighting issues. It seems that no matter what, going Compatibility is the most reliable and even allows playback on mobile device browsers, i.e. Brave on my Pixel 6a.

S3 Setup

Once your project is exported following the settings above, you will then need to upload the exported files to a S3 bucket which will serve as your CloudFront distribution’s origin:

  1. Navigate to S3 in the AWS Web Console, preferably in a region where the bulk of your requests will originate from
  2. Click create bucket and specify a bucket name, all other default settings should remain un-changed
  3. Once the bucket is created, navigate back to the S3 home and click on the bucket in your bucket list
  4. Click on the Permissions tab and scroll down to the Cross-Origin Resource Sharing (CORS) section
  5. Supply the following CORS policy as a placeholder for now:
[
    {
        "AllowedHeaders": [],
        "AllowedMethods": [
            "GET"
        ],
        "AllowedOrigins": [
            "https://dxxxxxxxxxxxxx.cloudfront.net",
            "https://example.com",
            "https://*.example.com"
        ],
        "ExposeHeaders": []
    }
]

Now that the bucket exists and has a CORS configuration, navigate to the Objects tab and upload all of your exported files from your HTML5/Web export specifically.

—NOTE—

Ensure that the HTML5/Web export files are uploaded to the root of the bucket, not in a folder. This simple setup requires this but later on you can optionally refine the bucket’s structure, but for now keep everything in the root of the bucket.

CloudFront Policy Setup

Before we even create our distribution, we must create sufficient policies to control the cache key, what gets passed onto requests towards S3 for cache misses, and how we’re standardizing our responses.

Search for CloudFront in the services searchbar and open CloudFront in a new tab. On the left-hand side under Distributions click on Policies. Start by clicking Create Cache Policy:

Cache Policy

  1. At minimum give the policy a name and optionally a description
  2. Specify 31536000 as the Min/Max/Default TTLs so that your game can be served from cache for up to a year at a time
  3. Under Headers choose “Include the following headers” and only specify the Origin header
  4. Keep the Gzip and Brotli Compression Support boxes checked
  5. Click the Create button

Click on the Origin Request tab and click Create Origin Request Policy:

Origin Request Policy

  1. At minimum give the policy a name and optionally a description
  2. Under Headers choose “Include the following headers” and only specify the Origin header
  3. Click the Create button

Now click on the Response Headers tab and click Create Response Headers Policy:

Response Headers Policy

  1. At minimum give the policy a name and optionally a description
  2. Toggle on Strict-Transport-Security and check its preload, includeSubDomains, and Origin Override boxes
  3. Toggle on X-Content-Type-Options and check its Origin Override button
  4. Toggle on X-Frame-Options, set the Origin to SAMEORIGIN, and check its Origin Override box
  5. Toggle on Referrer-Policy, set it to no-referrer-when-downgrade, and check its Origin Override box
  6. Toggle on Content-Security-Policy, check its Origin Override box, and set it to

default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' blob:; media-src 'self' blob:; font-src 'self' data:; worker-src 'self' blob:; connect-src 'self'; object-src 'none'; frame-ancestors 'self';

  1. Under Custom Headers - Optional, click the Add Header button twice
  2. Specify the Cross-Origin-Opener-Policy header with a value of same-origin, checking the Origin Override box
  3. Specify the Cross-Origin-Embedder-Policy header with a value of require-corp, checking the Origin Override box
  4. Click the Create button

On the left-hand side under Security, click on Origin Access. Click “Create Control Setting”, give it a name, then click Create.

CloudFront Distribution Setup

Now that the necessary policies and Origin Access Control (OAC) exist, you can create the distribution itself. Click on Distributions on the left-hand side and click Create Distribution:

  1. For Origin Domain type in your bucket’s name and select it
  2. For Origin Access choose “Origin access control settings (recommended)”
  3. For Origin Access Control choose the OAC you made earlier
  4. For Viewer Protocol Policy choose “Redirect HTTP to HTTPS”
  5. Under Cache Policy choose the Caching Disabled policy for now
  6. Under Origin Request Policy and Response Headers Policy specify the policies you made earlier
  7. For WAF you will have to choose whether to use one or not, this one depends on you but for development/testing you can choose “Do not enable security protections” for now
  8. For Supported HTTP Versions make sure HTTP/2 and HTTP/3 are checked
  9. For Default Root Object, specify the project’s .html file you uploaded to the bucket
  10. Click Create Distribution

You will see a popup regarding a necessary S3 bucket policy change. You can click the Copy Policy button to copy it to your clipboard and then go back to your S3 tab. Under the bucket’s Permissions tab, under Bucket Policy, click Edit and paste the policy. In the event you missed the pop-up it should be the following:

{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "AllowCloudFrontServicePrincipal",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::YourBucketName/*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "arn:aws:cloudfront::YourAWSAccountNumber:distribution/YourDistributionID"
                }
            }
        }
    ]
}

Back in your CloudFront tab, copy the distribution’s dxxxxxxxxxxxxx.cloudfront.net hostname and go back to your S3 tab. Underneath Permissions → Bucket Policy, scroll down to the CORS settings again, click Edit, and replace:

"AllowedOrigins": [
            "https://dxxxxxxxxxxxxx.cloudfront.net",

With:

"AllowedOrigins": [
            "https://YourDistributionHostname.cloudfront.net",

With the bucket’s CORS settings now allowing your distribution, test accessing your distribution’s hostname in a new tab. If you receive 403 errors either the bucket policy isn’t accurate, the objects weren’t uploaded to the root of the bucket, and/or the Default Root Object .html you specified isn’t accurate.

If you don’t face any HTTP-level 4xx/5xx errors, but face issues with the game starting up or running as expected, you may want to replace your S3 files with a fresh export in Debug mode. Once you delete the existing objects and upload the new Debug project files, going into the browser’s console (F12) will show more verbose Godot-generated logs. At this point it is very unlikely that this is an issue caused by the hosting workflow but you can always confirm by seeing if you get the same error across different browser and when hosting on different platforms.

In the event you face no issues at all and want to keep hosting it, you will want to apply your Cache Policy so that future requests are served by CloudFront’s cache instead of S3:

  1. Go back to your CloudFront tab
  2. Click Distributions on the left-hand side
  3. Click on your distribution
  4. Click on the Behaviors tab
  5. Click the radio button for the Default (*) behavior and click the Edit button
  6. Change the Cache Policy from Caching Disabled to the one you made earlier
  7. Click Save Changes

Now that caching is in place, if you ever need to clear CloudFront’s cache (i.e. you upload new versions of project files to S3, etc.) you can click on CloudFront’s Invalidations tab, click Create Invalidation, and enter /* before clicking the Create Invalidation so that the entire distribution’s cache can be cleared, per the /* wildcard.

Considerations

You now have a functional workflow for playing HTML5/Web exports of your Godot project, congrats! Here are some other workflow considerations you may want to evaluate if you plan on hosting it long-term by yourself:

1 - Custom Hostname

If you have your own domain name, i.e. example.com, you’ll notice that right now only CloudFront’s own dxxxxxxxxxxxxx.cloudfront.net hostname facilitates your site. In order to use your own domain name, you must first request an ACM Certificate in us-east-1. Once issued, you may go back to your distribution’s General tab, click the Edit button in the Settings section, specify your custom domain name for the “Alternate domain name (CNAME)” setting, and choose your ACM Certificate from the “Custom SSL certificate” dropdown.

Once your distribution has custom domain names associated with it, you must then have your public DNS service point these domain names to the CloudFront distribution. If using Route 53 you may create an Alias A record pointing to the distribution by its own hostname dxxxxxxxxxxxxx.cloudfront.net instead of by IP, a feature specific to Route 53. Otherwise you will have to create a CNAME record for your custom domain names pointing them to your distribution’s dxxxxxxxxxxxxx.cloudfront.net hostname. If you try and hardcode an A record with IPs this is unreliable since CloudFront IP addresses are subject to change at any time since it’s a CDN service.

Let’s say you own example.com but want to be able to use just example.com and www.example.com, when requesting the ACM Certificate the initial SAN should be example.com and then you can add a second SAN for either www.example.com or *.example.com, either one would scope www. but *. would allow you to use any subdomain name one subdomain level to the left of example.com. The ACM allows for DNS or email validation of certificate requests and has instructions step-to-step for you to refer to.

Do note that for each custom domain name you add to your distribution, you must update your S3 bucket’s CORS settings accordingly. If you only ever want to use the dxxxxxxxxxxxxx.cloudfront.net hostname for hosting then you can remove the placeholder example.com and *.example.com entries from the bucket’s CORS settings.

2 - S3 Maintenance

If you plan on performing rolling updates during periods of heavy testing, you may want to consider enabling the Versioning feature on the bucket. This will allow you to live-overwrite objects of the same name, keeping their previous versions. It is highly recommended to also create a Lifecycle Rule to expire and delete previous versions of these objects to conserve costs.

You may do so by:

  1. Go back to your S3 tab
  2. Click on Buckets on the left-hand side
  3. Click on your bucket
  4. Click the Management tab
  5. Click Create Lifecycle Rule
  6. Give the rule a name
  7. Under Rule Scope choose “Apply to all objects in the bucket” and check the acknowledgement box
  8. For Lifecycle Rule Actions check the boxes for “Permanently delete noncurrent versions of objects” and “Delete expired object delete markers or incomplete multipart uploads”
  9. For “Permanently delete noncurrent versions of objects” specify 1 day for each field
  10. For “Delete expired object delete markers or incomplete multipart uploads” check the boxes for “Delete expired object delete markers” and “Delete incomplete multipart uploads” and specify 1 day for the entry field
  11. Click Create Rule

S3 Lifecycle Rules run once every day at 00:00 UTC and these settings will ensure that the most recent version of an object will be in production, i.e. what CloudFront will retrieve on cache misses, while keeping the second-most-recent version stored in full in the event you ever need to check it. You can check object versions by going to the Objects tab, clicking on an object, then clicking on the Versions tab.

3 - Custom Website

This guide placed all of Godot’s exported files in the root of the bucket, meaning that only the game’s HTML is loaded upon visiting. If you want to use this CloudFront/S3 workflow for a website in general, not just locked to this game, you will likely want to move all of your game files to a folder in S3 and add your actual website files to the root of the bucket. Once done you will need to edit the distribution’s Default Root Object to specify your actual website’s HTML page, and then have your site use referential paths to load the game as desired, i.e. a href going to /game/game.html where /game is a folder in S3 where game.html and the other Godot export files reside.

4- Logging

If you want to track the changes and usage of your objects you may want to consider enabling S3 Server Access Logs. Always have your logs delivered to a bucket not being the subject of logging in question, i.e. create a separate bucket dedicated to logging. Similarly on the CloudFront side you may want to enable CloudFront Standard Logs for usage logging from that service’s perspective.

5 - Alarms

A best practice when using any cloud service is to understand how to review your bill and get notified when a spike occurs or a threshold is met. AWS offers Cost & Usage Reports and CloudWatch Alarms which can perform billing alerting for you.

Caching

Always remember that this workflow is server-less; there’s no Apache, IIS, etc. at play here. This is just a CDN (CloudFront) serving static from an object storage service (S3). If you ever make changes and not see them reflected, it is because your Cache Policy has long TTLs if following this guide, and you must create a /* Invalidation to clear the distribution’s cache. This Invalidation pathing can be more fine-tuned and have many done at once, i.e. /game/game.html, /assets/logo.png, etc. For long periods of testing you’re better off using the Caching Disabled policy until it’s ready, where you can then change back to your actual policy.

Conclusion

I hope this serves as a good overview of testing HTML5/Web exports in a simple and cost-effective way. You can always spin up a local virtual machine or a CORS proxy service to test, but if you want to truly test over the internet with minimal setup time and costs, going server-less with this approach covers everything from HTTPS, CORS, etc. Any settings not explicitly mentioned in my post should not be touched unless you know what you’re doing. Do note that in us-east-1 that S3 Standard tier storage costs are $0.023/GB/month, but unlike using a public bucket you do not pay for the initial bucket egress to CloudFront. Instead, you pay ~$0.085/GB for CloudFront egress to users after your first monthly TB-worth, and ~$0.0100/10k requests after your first monthly 10mil-worth.

As an example, Cogito is ~75MB in S3 total, and by always keeping the latest and second-most-recent versions that’s ~150MB, or, 0.15GB. That is well within S3’s Free Tier of usage, but in the event your account is no longer eligible that’s ~$0.0035/month. With 10k monthly users that’s 750GB (75MB prod data * 10k) which induces ~$63.75 worth of CloudFront data egress charges and ~0.01 for the 10k requests themselves. S3’s pricing varies by which region your bucket is in and CloudFront’s pricing varies on the distribution’s Price Class and where your Viewers are being served from. For estimates specific to you, look up the AWS Pricing Calculator.

1 Like