[Update] Amazon CloudFront now supports WebSocket for VPC origins
This page has been translated by machine translation. View original
I want to completely eliminate public subnets using CloudFront VPC Origins, but I can't because I want to use WebSocket
Hello, I'm Nonpi (@non____97)
Have you ever wanted to completely eliminate public subnets using CloudFront VPC Origins, but couldn't because you wanted to use WebSocket? I have.
By using CloudFront VPC Origins, you can use internal ALBs, NLBs, and EC2 instances as CloudFront origins without using Global Accelerator.
This not only eliminates direct access routes to the origin, but also makes it easy to communicate with only a specified CloudFront distribution, and helps reduce the cost of public IPv4 addresses — all of which are welcome benefits.
However, at the time of the VPC Origins GA, WebSocket communication was not supported.
Therefore, when some paths at the origin used WebSocket, it was necessary to place origin resources such as ALB/NLB in a public subnet. This was problematic.
This time, Amazon CloudFront added WebSocket support for VPC Origins.
This means that even origins handling WebSocket traffic can now be configured in private subnets.
I actually tried it out.
Tried It Out
Verification Environment
The verification environment is as follows.

In a VPC with no public subnets, an internal ALB that distributes traffic to ECS Fargate running an application that communicates via WebSocket is specified as a VPC Origin.
By the way, using WebSocket with CloudFront is mentioned in the following official AWS documentation.
All resources were deployed with AWS CDK.
There is also an L2 Construct called aws_cloudfront_origins, making implementation very straightforward.
The code used is stored in the following GitHub repository.
Resource Verification
Let's check the created resources.
Looking at the VPC resource map, you can see that there are no subnets assigned a route table with a route to an IGW — i.e., no public subnets.

The VPC Origin settings are as follows.
There are no special settings "for WebSocket on the origin."


The origin settings on the CloudFront distribution side are the same.


Operation Verification
Now let's verify the operation.
I opened three browser windows, and also connected via WebSocket from the terminal, exchanging messages.

> pnpm dlx wscat -c wss://d1fhfqyui9h81a.cloudfront.net/websocket
Connected (press CTRL+C to quit)
< {"type":"welcome","id":"a83b55ae-92ba-4b01-8c0e-b4079569f24a","clientCount":4}
> こんにちは a83b55ae-92ba-4b01-8c0e-b4079569f24a です
< {"type":"echo","from":"a83b55ae-92ba-4b01-8c0e-b4079569f24a","message":"こんにちは a83b55ae-92ba-4b01-8c0e-b4079569f24a です"}
< {"type":"broadcast","from":"cdeed963-7774-4afb-a565-b9344ddfc327","message":"元気ですか?"}
Without any issues, the posted strings were reflected in real time across each client.
Also, after the connection timeout set on the ALB elapsed since the last message was posted, the connection was disconnected.

Some of the logs output by the application running on ECS Fargate for the series of operations are as follows.
{
"level": "info",
"time": "2026-05-25T13:02:54.569Z",
"pid": 6,
"hostname": "ip-10-10-8-174.ec2.internal",
"id": "11e254eb-5557-40ca-8832-3c37f5ed37ea",
"url": "/websocket",
"userAgent": "<user agent>",
"forwardedFor": "<client IPv6 address>, 130.176.119.40",
"cloudfrontId": "snx71Wifama59uLy3vfwcHaHwHmRrcVUi-e8vZtyU1FpHh069dBYQA==",
"clientCount": 1,
"msg": "ws connected"
}
{
"level": "info",
"time": "2026-05-25T13:03:08.958Z",
"pid": 6,
"hostname": "ip-10-10-8-174.ec2.internal",
"id": "cdeed963-7774-4afb-a565-b9344ddfc327",
"url": "/websocket",
"userAgent": "<user agent>",
"forwardedFor": "<client IPv6 address>, 130.176.116.164",
"cloudfrontId": "MraeHrTsMC2y5IHzWfDEVIKfI0tO2Pe47ev-C75yV822i2ANbcsmUw==",
"clientCount": 2,
"msg": "ws connected"
}
{
"level": "info",
"time": "2026-05-25T13:03:17.351Z",
"pid": 6,
"hostname": "ip-10-10-8-174.ec2.internal",
"id": "3c237116-9d54-4647-8e56-464c1025a9f5",
"url": "/websocket",
"userAgent": "<user agent>",
"forwardedFor": "<client IPv6 address>, 130.176.118.52",
"cloudfrontId": "WEHsZ_fwFs8GOmrQbrighqlVkRAaRtURlpeKO0a9QPbvly5e8Pjdbw==",
"clientCount": 3,
"msg": "ws connected"
}
{
"level": "info",
"time": "2026-05-25T13:03:46.975Z",
"pid": 6,
"hostname": "ip-10-10-8-174.ec2.internal",
"id": "11e254eb-5557-40ca-8832-3c37f5ed37ea",
"bytes": 31,
"isBinary": false,
"msg": "ws message received"
}
{
"level": "info",
"time": "2026-05-25T13:03:46.976Z",
"pid": 6,
"hostname": "ip-10-10-8-174.ec2.internal",
"id": "11e254eb-5557-40ca-8832-3c37f5ed37ea",
"broadcastCount": 2,
"msg": "ws message broadcasted"
}
{
"level": "info",
"time": "2026-05-25T13:04:04.623Z",
"pid": 6,
"hostname": "ip-10-10-8-174.ec2.internal",
"id": "cdeed963-7774-4afb-a565-b9344ddfc327",
"bytes": 31,
"isBinary": false,
"msg": "ws message received"
}
{
"level": "info",
"time": "2026-05-25T13:04:04.623Z",
"pid": 6,
"hostname": "ip-10-10-8-174.ec2.internal",
"id": "cdeed963-7774-4afb-a565-b9344ddfc327",
"broadcastCount": 2,
"msg": "ws message broadcasted"
}
{
"level": "info",
"time": "2026-05-25T13:08:50.946Z",
"pid": 6,
"hostname": "ip-10-10-8-174.ec2.internal",
"id": "71ca940a-8e32-4471-9938-9b9af4c18687",
"code": 1006,
"reason": "",
"clientCount": 0,
"msg": "ws closed"
}
Also, when configured to output all headers at connection time, the following log was output.
{
"level": "info",
"time": "2026-05-25T13:42:28.172Z",
"pid": 8,
"hostname": "ip-10-10-8-98.ec2.internal",
"id": "4ddd7969-9270-4ff6-b7e5-eadb48066e43",
"method": "GET",
"httpVersion": "1.1",
"url": "/websocket",
"headers": {
"x-forwarded-for": "<client IPv6 address>, 130.176.114.52",
"x-forwarded-proto": "http",
"x-forwarded-port": "80",
"host": "d1fhfqyui9h81a.cloudfront.net",
"x-amzn-trace-id": "Root=1-6a1451c4-4e3233e1196d4b12222f32ce",
"upgrade": "websocket",
"connection": "upgrade",
"sec-websocket-key": "Rnhou3qbnI2nVKLvsTWapg==",
"sec-websocket-extensions": "permessage-deflate; client_max_window_bits",
"cache-control": "no-cache",
"pragma": "no-cache",
"user-agent": "<user agent>",
"via": "1.1 ece5d4a731ece5ff46c564ab2b946ede.cloudfront.net (CloudFront)",
"x-amz-cf-id": "3QinF8qURdlfjvILoXcZZqalpUXpxOCEI4nLyjxNpODDTK1thF1D7w==",
"sec-websocket-version": "13",
"origin": "https://d1fhfqyui9h81a.cloudfront.net",
"accept-language": "ja,en-US;q=0.9,en;q=0.8",
"accept-encoding": "gzip, deflate, br, zstd"
},
"remoteAddress": "10.10.8.96",
"remotePort": 10026,
"remoteFamily": "IPv4",
"clientCount": 1,
"msg": "ws connected"
}
The 130.176.114.52 in the X-Forwarded-For header is the IP address of the CloudFront origin.
> whois 130.176.114.52
% IANA WHOIS server
% for more information on IANA, visit http://www.iana.org
% This query returned 1 object
refer: whois.arin.net
inetnum: 130.0.0.0 - 130.255.255.255
organisation: Administered by ARIN
status: LEGACY
whois: whois.arin.net
changed: 1993-05
source: IANA
# whois.arin.net
NetRange: 130.175.0.0 - 130.176.255.255
CIDR: 130.176.0.0/16, 130.175.0.0/16
NetName: AMAZO-4
NetHandle: NET-130-175-0-0-1
Parent: NET130 (NET-130-0-0-0-0)
NetType: Direct Allocation
OriginAS:
Organization: Amazon.com, Inc. (AMAZO-4)
RegDate: 2021-01-28
Updated: 2022-06-22
Ref: https://rdap.arin.net/registry/ip/130.175.0.0
OrgName: Amazon.com, Inc.
OrgId: AMAZO-4
Address: Amazon Web Services, Inc.
Address: P.O. Box 81226
City: Seattle
StateProv: WA
PostalCode: 98108-1226
Country: US
RegDate: 2005-09-29
Updated: 2026-04-17
Comment: For details of this service please see
Comment: http://ec2.amazonaws.com
Ref: https://rdap.arin.net/registry/entity/AMAZO-4
OrgRoutingHandle: IPROU3-ARIN
OrgRoutingName: IP Routing
OrgRoutingPhone: +1-206-555-0000
OrgRoutingEmail: aws-routing-poc@amazon.com
OrgRoutingRef: https://rdap.arin.net/registry/entity/IPROU3-ARIN
OrgNOCHandle: AANO1-ARIN
OrgNOCName: Amazon AWS Network Operations
OrgNOCPhone: +1-206-555-0000
OrgNOCEmail: amzn-noc-contact@amazon.com
OrgNOCRef: https://rdap.arin.net/registry/entity/AANO1-ARIN
OrgRoutingHandle: ARMP-ARIN
OrgRoutingName: AWS RPKI Management POC
OrgRoutingPhone: +1-206-555-0000
OrgRoutingEmail: aws-rpki-routing-poc@amazon.com
OrgRoutingRef: https://rdap.arin.net/registry/entity/ARMP-ARIN
OrgDNSHandle: DNS1131-ARIN
OrgDNSName: DNS
OrgDNSPhone: +1-202-555-0000
OrgDNSEmail: ipmanagement+dns@amazon.com
OrgDNSRef: https://rdap.arin.net/registry/entity/DNS1131-ARIN
OrgAbuseHandle: AEA8-ARIN
OrgAbuseName: Amazon EC2 Abuse
OrgAbusePhone: +1-206-555-0000
OrgAbuseEmail: trustandsafety@support.aws.com
OrgAbuseRef: https://rdap.arin.net/registry/entity/AEA8-ARIN
OrgTechHandle: ANO24-ARIN
OrgTechName: Amazon EC2 Network Operations
OrgTechPhone: +1-206-555-0000
OrgTechEmail: amzn-noc-contact@amazon.com
OrgTechRef: https://rdap.arin.net/registry/entity/ANO24-ARIN
With CloudFront VPC Origins now supporting WebSocket, public subnets can be completely eliminated even when using WebSocket
I introduced the update where CloudFront added WebSocket support for VPC Origins.
With CloudFront VPC Origins now supporting WebSocket, public subnets can be completely eliminated even when handling WebSocket traffic.
From both a cost and security perspective, eliminating public subnets is desirable whenever possible. If you were unable to adopt VPC Origins because you were using WebSocket, now is a good time to reconsider.
The following AWS Blogs article introducing the steps to migrate from public origins to VPC Origins should also be helpful.
The recommended approach appears to be the method using CloudFront continuous deployment, as introduced in the following article.
I hope this article helps someone.
That's all from Nonpi (@non____97) of the Cloud Business Division, Consulting Department!
