<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Lambda on Omegion</title><link>https://omegion.dev/tags/lambda/</link><description>Recent content in Lambda on Omegion</description><generator>Hugo</generator><language>en-us</language><copyright>Omegion</copyright><lastBuildDate>Wed, 26 Aug 2026 13:19:47 +0200</lastBuildDate><atom:link href="https://omegion.dev/tags/lambda/index.xml" rel="self" type="application/rss+xml"/><item><title>How to Reach a Lambda in Another AWS Region Without VPC Peering</title><link>https://omegion.dev/2026/04/how-to-reach-a-lambda-in-another-aws-region-without-vpc-peering/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://omegion.dev/2026/04/how-to-reach-a-lambda-in-another-aws-region-without-vpc-peering/</guid><description>Giving one Lambda a one-time, one-way path into another AWS region with cross-region PrivateLink, instead of opening up region isolation for everyone.</description><content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Our infra isolates AWS regions on purpose. Nothing in <code>eu-west-1</code> talks
directly to anything in <code>us-east-1</code>, and nothing routes back the other way.
It keeps a bad deploy or a bad actor in one region from becoming a problem in
both. A team came to me needing exactly the thing that policy exists to
prevent: a management app running in <code>eu-west-1</code> had to call services that
only exist in <code>us-east-1</code>. It was a one-time need for one app, not a reason
to open the regions up to each other generally, so VPC peering or a Transit
Gateway were both off the table. I needed a path that was narrow enough to
still call it isolation.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ol>
<li>Two AWS regions already set up with their own VPCs, private subnets, and
Lambda functions. I&rsquo;ll call them <code>euw1</code> and <code>use1</code> below.</li>
<li><a href="https://developer.hashicorp.com/terraform" target="_blank" rel="noopener noreferrer">Terraform</a>
 with the
<a href="https://registry.terraform.io/providers/hashicorp/aws/latest" target="_blank" rel="noopener noreferrer">AWS provider</a>

at 5.83 or newer. Cross-region PrivateLink support landed in that version.</li>
<li>Comfort with VPC endpoints, NLBs, and ALBs. I won&rsquo;t re-explain what those
are.</li>
</ol>
<h2 id="the-shape-of-the-fix">The shape of the fix</h2>
<p><a href="https://aws.amazon.com/privatelink/" target="_blank" rel="noopener noreferrer">AWS PrivateLink</a>
 lets a VPC endpoint in
one region connect to an endpoint service in another, over AWS&rsquo;s own
network, no peering or internet exposure involved. That&rsquo;s the narrow path:
one interface endpoint in <code>euw1</code>, pointed at one endpoint service in <code>use1</code>,
carrying traffic for exactly this app and nothing else.</p>
<p>An endpoint service has to sit behind a Network Load Balancer, and an NLB
can&rsquo;t target a Lambda function directly, only instances, IPs, or an ALB. So
the US side ends up as a small stack: NLB in front, forwarding to an ALB
target group, which is the piece that&rsquo;s actually allowed to invoke Lambda.</p>
<pre class="mermaid" id="mermaid-0"></pre>
<script>(window.__mermaidSources ||= {})["mermaid-0"] = "flowchart TD\n  L1[\"Lambda (euw1)\"] --\u003e EP[\"Interface endpoint (euw1)\"]\n  EP -.-\u003e|PrivateLink, cross-region| ES[\"Endpoint service (use1)\"]\n  ES --\u003e NLB[\"NLB (use1)\"]\n  NLB --\u003e ALB[\"ALB (use1)\"]\n  ALB --\u003e L2[\"Lambda (use1)\"]\n  L2 --\u003e SVC[\"US services\"]";</script>

<h2 id="setting-up-the-us-side">Setting up the US side</h2>
<p>Two providers, one per region, since everything below is split across them:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">hcl</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">provider</span> <span class="s2">&#34;aws&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  alias</span>  <span class="o">=</span> <span class="s2">&#34;use1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  region</span> <span class="o">=</span> <span class="s2">&#34;us-east-1&#34;</span>
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">provider</span> <span class="s2">&#34;aws&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  alias</span>  <span class="o">=</span> <span class="s2">&#34;euw1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  region</span> <span class="o">=</span> <span class="s2">&#34;eu-west-1&#34;</span>
</span></span><span class="line"><span class="cl">}</span></span></code></pre></div>
</div>
<p>The NLB, its ALB-type target group, and the endpoint service that sits in
front of it:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">hcl</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_lb_target_group&#34; &#34;nlb_to_alb&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  provider</span>    <span class="o">=</span> <span class="k">aws</span><span class="p">.</span><span class="k">use1</span>
</span></span><span class="line"><span class="cl"><span class="n">  name</span>        <span class="o">=</span> <span class="s2">&#34;us-services-nlb-tg&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  target_type</span> <span class="o">=</span> <span class="s2">&#34;alb&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  port</span>        <span class="o">=</span> <span class="m">443</span>
</span></span><span class="line"><span class="cl"><span class="n">  protocol</span>    <span class="o">=</span> <span class="s2">&#34;TCP&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  vpc_id</span>      <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">use1_vpc_id</span>
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_lb_target_group_attachment&#34; &#34;nlb_to_alb&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  provider</span>          <span class="o">=</span> <span class="k">aws</span><span class="p">.</span><span class="k">use1</span>
</span></span><span class="line"><span class="cl"><span class="n">  target_group_arn</span>  <span class="o">=</span> <span class="k">aws_lb_target_group</span><span class="p">.</span><span class="k">nlb_to_alb</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="cl"><span class="n">  target_id</span>         <span class="o">=</span> <span class="k">aws_lb</span><span class="p">.</span><span class="k">us_internal_alb</span><span class="p">.</span><span class="k">arn</span>
</span></span><span class="line"><span class="cl"><span class="n">  port</span>              <span class="o">=</span> <span class="m">443</span>
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_lb&#34; &#34;us_nlb&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  provider</span>           <span class="o">=</span> <span class="k">aws</span><span class="p">.</span><span class="k">use1</span>
</span></span><span class="line"><span class="cl"><span class="n">  name</span>               <span class="o">=</span> <span class="s2">&#34;us-services-nlb&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  internal</span>           <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="cl"><span class="n">  load_balancer_type</span> <span class="o">=</span> <span class="s2">&#34;network&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  subnets</span>            <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">use1_private_subnet_ids</span>
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_vpc_endpoint_service&#34; &#34;us_services&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  provider</span>                   <span class="o">=</span> <span class="k">aws</span><span class="p">.</span><span class="k">use1</span>
</span></span><span class="line"><span class="cl"><span class="n">  acceptance_required</span>        <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="cl"><span class="n">  network_load_balancer_arns</span> <span class="o">=</span> <span class="p">[</span><span class="k">aws_lb</span><span class="p">.</span><span class="k">us_nlb</span><span class="p">.</span><span class="k">arn</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">  supported_regions</span>          <span class="o">=</span> <span class="p">[</span><span class="s2">&#34;eu-west-1&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">}</span></span></code></pre></div>
</div>
<p><code>us_internal_alb</code> is the ALB with a <code>lambda</code>-type target group in front of
the US Lambda. I&rsquo;m leaving that part out since it&rsquo;s just the standard
ALB-to-Lambda setup, nothing specific to the region-crossing part.</p>
<h2 id="setting-up-the-eu-side">Setting up the EU side</h2>
<p>The interface endpoint is the only new thing on this side, and it needs
<code>service_region</code> set to tell it the service lives elsewhere:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">hcl</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_security_group&#34; &#34;us_services_endpoint&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  provider</span> <span class="o">=</span> <span class="k">aws</span><span class="p">.</span><span class="k">euw1</span>
</span></span><span class="line"><span class="cl"><span class="n">  name</span>     <span class="o">=</span> <span class="s2">&#34;us-services-endpoint-sg&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  vpc_id</span>   <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">euw1_vpc_id</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">ingress</span> {
</span></span><span class="line"><span class="cl"><span class="n">    from_port</span>       <span class="o">=</span> <span class="m">443</span>
</span></span><span class="line"><span class="cl"><span class="n">    to_port</span>         <span class="o">=</span> <span class="m">443</span>
</span></span><span class="line"><span class="cl"><span class="n">    protocol</span>        <span class="o">=</span> <span class="s2">&#34;tcp&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">    security_groups</span> <span class="o">=</span> <span class="p">[</span><span class="k">aws_security_group</span><span class="p">.</span><span class="k">euw1_lambda</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_vpc_endpoint&#34; &#34;us_services&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  provider</span>            <span class="o">=</span> <span class="k">aws</span><span class="p">.</span><span class="k">euw1</span>
</span></span><span class="line"><span class="cl"><span class="n">  vpc_id</span>              <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">euw1_vpc_id</span>
</span></span><span class="line"><span class="cl"><span class="n">  service_name</span>        <span class="o">=</span> <span class="k">aws_vpc_endpoint_service</span><span class="p">.</span><span class="k">us_services</span><span class="p">.</span><span class="k">service_name</span>
</span></span><span class="line"><span class="cl"><span class="n">  service_region</span>      <span class="o">=</span> <span class="s2">&#34;us-east-1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  vpc_endpoint_type</span>   <span class="o">=</span> <span class="s2">&#34;Interface&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  subnet_ids</span>          <span class="o">=</span> <span class="k">var</span><span class="p">.</span><span class="k">euw1_private_subnet_ids</span>
</span></span><span class="line"><span class="cl"><span class="n">  security_group_ids</span>  <span class="o">=</span> <span class="p">[</span><span class="k">aws_security_group</span><span class="p">.</span><span class="k">us_services_endpoint</span><span class="p">.</span><span class="k">id</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">  private_dns_enabled</span> <span class="o">=</span> <span class="kt">false</span>
</span></span><span class="line"><span class="cl">}</span></span></code></pre></div>
</div>
<p>I left <code>private_dns_enabled</code> off. Private DNS on a cross-region endpoint
means resolving the target service&rsquo;s own domain name to the local endpoint,
and I didn&rsquo;t want to lean on that working the same as it does in-region.
The EU Lambda calls the endpoint&rsquo;s own generated DNS name directly instead,
which is one extra environment variable, not a real cost.</p>
<p>The endpoint connection needs to be accepted on the <code>use1</code> side before
traffic flows, because <code>acceptance_required</code> is <code>true</code> above:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">shell</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">❯ aws ec2 describe-vpc-endpoint-connections --region us-east-1 <span class="se">\
</span></span></span><span class="line"><span class="cl">  --filters <span class="nv">Name</span><span class="o">=</span>vpc-endpoint-state,Values<span class="o">=</span>pendingAcceptance
</span></span><span class="line"><span class="cl"><span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;VpcEndpointConnections&#34;</span>: <span class="o">[</span>
</span></span><span class="line"><span class="cl">        <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;ServiceId&#34;</span>: <span class="s2">&#34;vpce-svc-0a1b2c3d4e5f6a7b8&#34;</span>,
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;VpcEndpointId&#34;</span>: <span class="s2">&#34;vpce-0f1e2d3c4b5a69788&#34;</span>,
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;VpcEndpointOwner&#34;</span>: <span class="s2">&#34;123456789012&#34;</span>,
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;VpcEndpointState&#34;</span>: <span class="s2">&#34;pendingAcceptance&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">❯ aws ec2 accept-vpc-endpoint-connections --region us-east-1 <span class="se">\
</span></span></span><span class="line"><span class="cl">  --service-id vpce-svc-0a1b2c3d4e5f6a7b8 <span class="se">\
</span></span></span><span class="line"><span class="cl">  --vpc-endpoint-ids vpce-0f1e2d3c4b5a69788</span></span></code></pre></div>
</div>
<p>Same account on both ends here, so I did that by hand once. A recurring
setup would script it or add the accepter as its own Terraform resource.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The stack is more layers than I&rsquo;d like for one app calling another region,
NLB, ALB, endpoint service, interface endpoint, but each layer is there
because something upstream of it can&rsquo;t do the next hop itself. It stays a
narrow, named exception instead of a general path between regions, which was
the actual requirement. If this app ever turns into &ldquo;several apps need
this,&rdquo; I&rsquo;d stop reaching for one-off endpoints and go read up on Transit
Gateway properly instead.</p>
]]></content:encoded></item></channel></rss>