Originally published on Medium
There’s a deceptively simple requirement that AWS API Gateway still doesn’t support cleanly:
Use a public, regional API Gateway with a custom domain, and allow private VPC workloads to call that same domain without changing client configuration.
If you’ve tried this, you’ve probably been sent in circles—by documentation, AI tools, and even AWS recommendations—toward solutions that either don’t deploy or don’t work.
This post documents what actually breaks, why most suggested approaches fail, and the pragmatic workaround that finally solved it.
The setup
You have a public API Gateway endpoint exposed as:
https://api.yourdomain.com
That domain is used everywhere:
- Browsers
- External consumers
- Shared SDKs
You also have private workloads (Lambda, ECS, EC2) running in isolated subnets with no NAT gateway. Those workloads need to call the same hostname, not an execute-api URL, and not an environment-specific variant.
Changing the base URL internally would fracture client configuration and defeat the point of having a stable public domain.
Why the obvious solutions fail
The first approach most people try is an interface VPC endpoint for execute-api, combined with a Route 53 private hosted zone that resolves api.yourdomain.com to the endpoint inside the VPC.
DNS-wise, this works. Traffic routes privately. The request never touches the internet.
But the TLS handshake fails.
At that point, nothing reaches API Gateway:
- No API is identified
- No routing occurs
- No authentication runs
TLS negotiation fails before HTTP exists.
“Just use private custom domains”
This recommendation comes up constantly—and it’s simply incorrect for this scenario.
API Gateway private custom domains only work with private API endpoints. Domain associations explicitly require the API itself to be private. You cannot attach a private custom domain to a public regional API.
CloudFormation rejects it.
The console won’t allow it.
There is no workaround.
This is a product limitation, not a configuration error.
“Use the execute-api endpoint internally”
That works technically, but it forces:
- Different base URLs per environment
- API IDs and stage names in clients
- Special-case logic in shared SDKs
For systems designed around a single public domain, this is architectural debt.
The real problem
Once you strip away the noise, the issue is very small and very specific.
When you access API Gateway through a VPC endpoint, the endpoint presents a TLS certificate for:
*.execute-api.{region}.amazonaws.com
Even if you connect using:
https://api.yourdomain.com
The certificate chain is valid.
The issuer is trusted.
The only failure is hostname mismatch.
And because TLS fails, API Gateway never sees the request at all.
The pragmatic workaround
If you accept that:
- You are connecting to an AWS-owned service
- Over a VPC endpoint
- Using AWS-issued certificates
- With DNS fully under your control
…then the fix is to explicitly accept execute-api certificates when the only TLS error is a name mismatch.
This preserves:
- One public domain
- One client configuration
- Private routing without NAT
- Full encryption in transit
Applying this in .NET
Configuring the HTTP client
This is the missing piece you called out earlier.
The TLS behavior must be applied when registering the HttpClient.
services.AddHttpClient("ApiClient")
.ConfigureForVpcEndpoint();
Any SDK or service using this client will now tolerate API Gateway’s certificate when accessed via a VPC endpoint.
The HTTP client handler
public static IHttpClientBuilder ConfigureForVpcEndpoint(
this IHttpClientBuilder builder)
{
return builder.ConfigurePrimaryHttpMessageHandler(() =>
new SocketsHttpHandler
{
SslOptions = new SslClientAuthenticationOptions
{
RemoteCertificateValidationCallback =
ValidateApiGatewayCertificate
}
});
}```
{% endraw %}
## Certificate validation
{% raw %}
```csharp
private static bool ValidateApiGatewayCertificate(
object sender,
X509Certificate? certificate,
X509Chain? chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch &&
certificate != null)
{
var subject = certificate.Subject;
if (subject.Contains("execute-api", StringComparison.OrdinalIgnoreCase) &&
subject.Contains("amazonaws.com", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
This does not disable TLS validation. It narrowly allows a known, trusted AWS certificate that fails hostname validation only because API Gateway cannot present your custom-domain certificate over a VPC endpoint.
Why this isn’t as scary as it looks
This is safe because it’s constrained:
- The trust chain is still validated
- Only AWS API Gateway certificates are accepted
- DNS is private and controlled
Traffic never leaves AWS’s network
This is not a blanket “ignore SSL errors” hack—it’s compensating for a missing feature.
The real takeaway
There is currently no supported way to combine:
- A public regional API Gateway
- A custom domain
- Private VPC access
- A single, stable hostname
- Correct TLS without client changes
Until AWS fills that gap, this workaround is the cleanest solution I’ve found—and the only one that doesn’t require re-architecting clients around internal-only URLs.
Sometimes the hardest problems aren’t misconfigurations.
They’re the seams where cloud abstractions stop.
About the author: Dan Guisinger is a consultant specializing in system and security architecture on AWS.

Top comments (0)