<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Akifumi Niida</title>
    <description>The latest articles on Forem by Akifumi Niida (@nid).</description>
    <link>https://forem.com/nid</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F873750%2F11b0c40b-97a0-4cb4-9d04-2b500a7ec743.jpg</url>
      <title>Forem: Akifumi Niida</title>
      <link>https://forem.com/nid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/nid"/>
    <language>en</language>
    <item>
      <title>How Cloudflare Proxy Silently Broke My Lambda ALB Communication</title>
      <dc:creator>Akifumi Niida</dc:creator>
      <pubDate>Sun, 08 Mar 2026 01:10:40 +0000</pubDate>
      <link>https://forem.com/aws-builders/how-cloudflare-proxy-silently-broke-my-lambda-alb-communication-1c64</link>
      <guid>https://forem.com/aws-builders/how-cloudflare-proxy-silently-broke-my-lambda-alb-communication-1c64</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Both &lt;code&gt;api.hoge.com&lt;/code&gt; and &lt;code&gt;backend.hoge.com&lt;/code&gt; had Cloudflare Proxy enabled.&lt;/p&gt;

&lt;p&gt;Requests from the browser were already passing through Cloudflare Edge before reaching Lambda. When Lambda then called &lt;code&gt;backend.hoge.com&lt;/code&gt;, DNS resolved to Cloudflare's Anycast IP — sending the request right back into Cloudflare Edge.&lt;/p&gt;

&lt;p&gt;This created a &lt;strong&gt;Cloudflare loop: Cloudflare → Lambda → Cloudflare&lt;/strong&gt;, resulting in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cloudflare Error 1000
DNS points to prohibited IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    Browser --&amp;gt;|①| CF1[Cloudflare Edge]
    CF1 --&amp;gt;|②| Lambda
    Lambda --&amp;gt;|③ backend.hoge.com = Cloudflare IP| CF2[Cloudflare Edge]
    CF2 --&amp;gt;|❌ Error 1000| ALB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a quick fix, I turned Proxy OFF for &lt;code&gt;backend.hoge.com&lt;/code&gt;. The long-term plan is to move Lambda → ALB communication inside the VPC.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Error
&lt;/h2&gt;

&lt;p&gt;Calling the API from the frontend returned a &lt;code&gt;403 Forbidden&lt;/code&gt;. The response body contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cloudflare Error 1000
DNS points to prohibited IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;API Gateway and Lambda appeared to be working fine, and there were no logs on the ECS side at all. My first instinct was to blame the ALB or WAF.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    Browser --&amp;gt;|HTTPS| APIGW[API Gateway]
    APIGW --&amp;gt; Lambda
    Lambda --&amp;gt;|HTTPS backend.hoge.com| ALB
    ALB --&amp;gt; ECS
    ECS --&amp;gt; RDS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lambda acts as a BFF (Backend for Frontend). The backend runs on ALB + ECS — a legacy constraint we hadn't moved away from yet. Lambda was calling that ALB over HTTPS using the &lt;code&gt;backend.hoge.com&lt;/code&gt; domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;Given the &lt;code&gt;403&lt;/code&gt; status code, my initial suspects were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WAF rules on the ALB&lt;/li&gt;
&lt;li&gt;Security Group restrictions&lt;/li&gt;
&lt;li&gt;Authorization logic in the ECS application&lt;/li&gt;
&lt;li&gt;API Gateway authorizer configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  No logs in ECS
&lt;/h3&gt;

&lt;p&gt;As I dug deeper, something felt off. If the request was reaching the ALB, there should be &lt;em&gt;something&lt;/em&gt; in the ECS logs. But there was nothing — not a single entry.&lt;/p&gt;

&lt;p&gt;That pointed to the request never reaching the ALB in the first place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking with curl
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-v&lt;/span&gt; https://backend.hoge.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at the response headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server: cloudflare
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the response body contained &lt;code&gt;DNS points to prohibited IP&lt;/code&gt;. That's when it clicked — Cloudflare itself was returning the 403, not our application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading the docs
&lt;/h3&gt;

&lt;p&gt;Cloudflare's official documentation covers the causes of Error 1000:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-1xxx-errors/error-1000/" rel="noopener noreferrer"&gt;https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-1xxx-errors/error-1000/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to the docs, this error occurs when an A record points to a Cloudflare-owned IP, or when a request is routed through another reverse proxy and ends up back at Cloudflare a second time.&lt;/p&gt;

&lt;p&gt;Since our ALB was configured via a CNAME (not an A record), I initially dismissed this as "not applicable." I knew how Proxy ON worked in theory, but the pressure of an ongoing incident made me overlook it. If I had checked the docs more carefully, I would have found the root cause much sooner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Root Cause
&lt;/h2&gt;

&lt;p&gt;Our Cloudflare DNS settings looked like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Record&lt;/th&gt;
&lt;th&gt;Proxy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;api.hoge.com&lt;/td&gt;
&lt;td&gt;ON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;backend.hoge.com&lt;/td&gt;
&lt;td&gt;ON&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  It was a Cloudflare loop
&lt;/h3&gt;

&lt;p&gt;Because &lt;code&gt;api.hoge.com&lt;/code&gt; had Proxy ON, browser requests were already passing through Cloudflare Edge before arriving at Lambda.&lt;/p&gt;

&lt;p&gt;When Lambda called &lt;code&gt;backend.hoge.com&lt;/code&gt; — also Proxy ON — DNS returned Cloudflare's Anycast IP. The request looped back into Cloudflare Edge, creating a Cloudflare → Lambda → Cloudflare loop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    Browser --&amp;gt;|①| CF1[Cloudflare Edge api.hoge.com]
    CF1 --&amp;gt;|②| Lambda
    Lambda --&amp;gt;|③ backend.hoge.com = Cloudflare IP| CF2[Cloudflare Edge backend.hoge.com]
    CF2 --&amp;gt;|❌ Error 1000| ALB
    ALB --&amp;gt; ECS
    ECS --&amp;gt; RDS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why does this trigger Error 1000?
&lt;/h3&gt;

&lt;p&gt;Cloudflare returns Error 1000 when it detects a loop, or when the resolved origin IP falls into one of these categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Cloudflare-owned IP (loop prevention)&lt;/li&gt;
&lt;li&gt;RFC 1918 private addresses (&lt;code&gt;10.x.x.x&lt;/code&gt; / &lt;code&gt;172.16.x.x&lt;/code&gt; / &lt;code&gt;192.168.x.x&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Loopback address (&lt;code&gt;127.0.0.1&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, &lt;code&gt;backend.hoge.com&lt;/code&gt; resolved to a Cloudflare IP, so Cloudflare treated it as a request targeting itself and returned &lt;code&gt;DNS points to prohibited IP&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix #1: Quick Fix
&lt;/h2&gt;

&lt;p&gt;I turned Proxy OFF for &lt;code&gt;backend.hoge.com&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Record&lt;/th&gt;
&lt;th&gt;Proxy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;backend.hoge.com&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With Proxy OFF, DNS returns the actual origin CNAME (the ALB domain) instead of a Cloudflare IP. The loop was broken and requests started flowing normally again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    Lambda --&amp;gt;|backend.hoge.com = ALB domain| ALB
    ALB --&amp;gt; ECS
    ECS --&amp;gt; RDS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cloudflare is more than just DNS
&lt;/h3&gt;

&lt;p&gt;Cloudflare combines authoritative DNS, reverse proxy, CDN, and WAF into one. When Proxy is ON, all traffic is routed through Cloudflare Edge before reaching your origin.&lt;/p&gt;

&lt;p&gt;This is great for browser-facing traffic — you get DDoS protection, caching, and WAF for free. But for server-to-server communication, it can cause unexpected behavior like this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proxy ON vs OFF changes the entire traffic path
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    subgraph Proxy OFF
        C1[Client] --&amp;gt;|ALB domain| ALB1[ALB]
    end
    subgraph Proxy ON
        C2[Client] --&amp;gt; CF[Cloudflare Edge] --&amp;gt; ALB2[ALB]
    end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Match Proxy settings to your use case
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Proxy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Browser → API&lt;/td&gt;
&lt;td&gt;ON (CDN + WAF benefits)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Server → Server&lt;/td&gt;
&lt;td&gt;OFF (no benefit, potential loops)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a server calls a Proxy ON domain while the calling service is itself behind Cloudflare, you risk creating exactly this kind of loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix #2: Long-term Fix
&lt;/h2&gt;

&lt;p&gt;The quick fix works, but routing Lambda → ALB traffic through public DNS was never ideal. Moving this communication inside the VPC is the proper solution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    Browser --&amp;gt;|HTTPS| APIGW[API Gateway]
    APIGW --&amp;gt; Lambda
    subgraph VPC
        Lambda --&amp;gt;|VPC-internal| ALB
        ALB --&amp;gt; ECS
        ECS --&amp;gt; RDS
    end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to keep in mind for this migration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda needs to be deployed inside the VPC (if it isn't already)&lt;/li&gt;
&lt;li&gt;Cold start impact from VPC placement is minimal these days — this used to be a real concern, but AWS has significantly improved it&lt;/li&gt;
&lt;li&gt;You'll need to explicitly allow Lambda → ALB traffic in your security groups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach eliminates the Cloudflare dependency entirely for internal traffic, reduces latency, and simplifies the network topology.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Did This Break Suddenly?
&lt;/h2&gt;

&lt;p&gt;The Proxy ON configuration had been in place for a long time. So why did this only start failing now?&lt;/p&gt;

&lt;p&gt;I checked Cloudflare's changelog but couldn't find any official announcement around this time about changes to Error 1000 detection or proxy behavior.&lt;/p&gt;

&lt;p&gt;Searching the Cloudflare community, there are scattered reports of "Error 1000 suddenly appearing" — but in each case, the cause turned out to be a specific configuration change on the user's end (a DNS record update, an IP change on the hosting side, etc.).&lt;/p&gt;

&lt;p&gt;I'm still looking into Cloudflare's Audit Log for anything that might explain this. I'll update this post if I find something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;If a request passes through a Proxy ON domain, and that request then calls another Proxy ON domain, you get a Cloudflare loop — and &lt;strong&gt;Error 1000&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For server-to-server communication, either turn Proxy OFF for internal domains, or better yet, keep that traffic inside the VPC entirely.&lt;/p&gt;

</description>
      <category>lambda</category>
      <category>cloudflare</category>
      <category>aws</category>
    </item>
    <item>
      <title>STOPSIGNAL is now available on Amazon ECS Fargate</title>
      <dc:creator>Akifumi Niida</dc:creator>
      <pubDate>Sun, 14 Dec 2025 08:43:25 +0000</pubDate>
      <link>https://forem.com/aws-builders/stopsignal-is-now-available-on-amazon-ecs-fargate-45fa</link>
      <guid>https://forem.com/aws-builders/stopsignal-is-now-available-on-amazon-ecs-fargate-45fa</guid>
      <description>&lt;h1&gt;
  
  
  Fargate Now Supports “STOP SIGNAL”
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/jp/about-aws/whats-new/2025/12/amazon-ecs-custom-container-stop-signals-fargate/" rel="noopener noreferrer"&gt;https://aws.amazon.com/jp/about-aws/whats-new/2025/12/amazon-ecs-custom-container-stop-signals-fargate/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fargate now sends the STOPSIGNAL command defined in your Dockerfile to containers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why is this great? Previous challenges
&lt;/h1&gt;

&lt;p&gt;Previously, achieving a graceful shutdown on ECS Fargate required creating logic to safely stop after receiving SIGTERM.&lt;br&gt;
&lt;a href="https://aws.amazon.com/jp/blogs/news/graceful-shutdowns-with-ecs/" rel="noopener noreferrer"&gt;https://aws.amazon.com/jp/blogs/news/graceful-shutdowns-with-ecs/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, middleware often has fixed behavior when receiving a stop signal.&lt;br&gt;
For example, nginx stops immediately upon receiving SIGTERM. This required workarounds like writing trap handling to retry SIGTERM as SIGQUIT.&lt;br&gt;
&lt;a href="https://linuxjm.sourceforge.io/html/nginx/man8/nginx.8.html" rel="noopener noreferrer"&gt;https://linuxjm.sourceforge.io/html/nginx/man8/nginx.8.html&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Improvements
&lt;/h2&gt;

&lt;p&gt;By adhering to the OCI standard, specifying &lt;strong&gt;STOPSIGNAL arbitrary_signal&lt;/strong&gt; in the Dockerfile alone guarantees graceful shutdowns on Fargate, Kubernetes, and local Docker, significantly improving portability.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Wasn't This Possible Before? (Analysis)
&lt;/h2&gt;

&lt;p&gt;Fargate likely runs on AWS's proprietary managed host OS (microVM/Firecracker), which may have restricted flexible access. If anyone knows more, please share.&lt;/p&gt;
&lt;h1&gt;
  
  
  What I Tried
&lt;/h1&gt;

&lt;p&gt;The source for verification is here:&lt;br&gt;
&lt;a href="https://github.com/nidcode/ecs-stop-signal-sample" rel="noopener noreferrer"&gt;https://github.com/nidcode/ecs-stop-signal-sample&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I created a simple program like the one below for testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require(‘express’);
const app = express();
const PORT = process.env.PORT || 8080;
const SHUTDOWN_DELAY_MS = parseInt(process.env.SHUTDOWN_DELAY_MS, 10) || 10000;

app.get(‘/’, (req, res) =&amp;gt; {
    res.send(‘Running and waiting for stop signal...’);
});

const server = app.listen(PORT, () =&amp;gt; {
    console.log(`Server running on port ${PORT}`);
});

// --- Signal Handler Definition ---
// Handler for SIGTERM (default stop signal)
process.on(‘SIGTERM’, () =&amp;gt; {
    handleShutdown(‘SIGTERM’);
});

// Handler for SIGINT (custom verification signal)
process.on(‘SIGINT’, () =&amp;gt; {
    handleShutdown(‘SIGINT’);
});

// SIGKILL (force termination) cannot be caught!

function handleShutdown(signal) {
    console.log(`[${signal} RECEIVED] Graceful shutdown initiated.`);

    // 1. Stop accepting new connections to the server
    server.close(() =&amp;gt; {
        console.log(‘HTTP server closed.’);
    });

    // 2. Simulate cleanup processes like writing logs and closing the DB
    console.log(`Starting cleanup. Waiting for ${SHUTDOWN_DELAY_MS / 1000} seconds...`);

    // Assumes cleanup completes within the delay period
    setTimeout(() =&amp;gt; {
        console.log(`[${signal} SUCCESS] Cleanup complete. Exiting cleanly.`);
        process.exit(0);
    }, SHUTDOWN_DELAY_MS);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test Application Behavior&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Receives SIGINT and performs cleanup for 10 seconds&lt;/li&gt;
&lt;li&gt;Records [SIGINT RECEIVED] logs in CloudWatch Logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For verification, I set the ECS task definition stopTimeout to 15 seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Behavior Without STOPSIGNAL
&lt;/h3&gt;

&lt;p&gt;I added some comments to server.js to create a diff, then ran cdk deploy to force task recreation.&lt;/p&gt;

&lt;p&gt;As expected, it receives SIGTERM&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3idltkxef1f1iuaae4ew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3idltkxef1f1iuaae4ew.png" alt=" " width="800" height="215"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting STOPSIGNAL
&lt;/h3&gt;

&lt;p&gt;Simply adding STOPSIGNAL to the Dockerfile is sufficient.&lt;br&gt;
Let's configure SIGINT alongside server.js.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM node:22-slim
...
STOPSIGNAL SIGINT

CMD [“node”, “server.js”]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once deployed successfully, run &lt;code&gt;cdk deploy&lt;/code&gt; again as before to force the tasks to rebuild.&lt;/p&gt;

&lt;p&gt;It caught the SIGINT!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2611250zjzt3yzojq3ip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2611250zjzt3yzojq3ip.png" alt=" " width="800" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This custom stop signal support isn't just an added parameter—it signifies that AWS Fargate now fully complies with the international standard for container runtimes (OCI).&lt;br&gt;
This allows container operators to bring standard images used in other environments directly to Fargate, significantly improving deployment and operational quality (Graceful Shutdown).&lt;/p&gt;

</description>
      <category>aws</category>
      <category>containers</category>
      <category>docker</category>
    </item>
    <item>
      <title>Tips for exposing SPAs and APIs with Cloudfront + S3 + API Gateway</title>
      <dc:creator>Akifumi Niida</dc:creator>
      <pubDate>Tue, 07 Jun 2022 13:30:10 +0000</pubDate>
      <link>https://forem.com/aws-builders/tips-for-exposing-spas-and-apis-with-cloudfront-s3-api-gateway-318i</link>
      <guid>https://forem.com/aws-builders/tips-for-exposing-spas-and-apis-with-cloudfront-s3-api-gateway-318i</guid>
      <description>&lt;p&gt;This is a common pattern, but I would like to share some of the practices that I have arrived at.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F33070%2Fb35f713b-1eb6-dcaa-59a3-01107fb26fef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F33070%2Fb35f713b-1eb6-dcaa-59a3-01107fb26fef.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion first
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;SPA: Change path with extension to /index.html using Cloudfront Functions&lt;/li&gt;
&lt;li&gt;API: Point /api to API Gateway with Behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Until now, when publishing SPA applications such as Vue or Angular with Cloudfront + S3&lt;br&gt;
S3 returns 403 or 404 by making a request to a path that does not exist.&lt;br&gt;
Therefore, we had to create a custom error page on the Cloudfront side and configure it to redirect to /index.html.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common problem 1 (SPA path issue)
&lt;/h3&gt;

&lt;p&gt;Even if there is a 404 that should be caught, it is redirected without care.&lt;br&gt;
For example, it is hard to notice if there is a CSS or JS upload error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Problem 2 (Cloudfront problem of assigning specific paths)
&lt;/h3&gt;

&lt;p&gt;I want to assign /api paths to API Gateway.&lt;br&gt;
This method seems to be good from a security point of view, as it eliminates the need to configure CORS.&lt;/p&gt;

&lt;h2&gt;
  
  
  SPA path problem
&lt;/h2&gt;

&lt;p&gt;Using &lt;code&gt;Cloudfront Functions&lt;/code&gt;, I can distribute requests like &lt;code&gt;/users/xxx&lt;/code&gt; to &lt;code&gt;/index.html&lt;/code&gt; in a good way without waiting for error pages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Cloudfront Functions&lt;/code&gt; will be the ones you code to handle events at the edge.&lt;/li&gt;
&lt;li&gt;There is a &lt;code&gt;lambda@edge&lt;/code&gt; that is similar, but you can read more about the differences &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-functions.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; for the difference.&lt;/li&gt;
&lt;li&gt;It is better to understand that &lt;code&gt;lambda@edge&lt;/code&gt; has limited functionality and can only perform simple processing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, if you want to try Cloudfront Functions quickly, you can try this.&lt;br&gt;
&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-tutorial.html" rel="noopener noreferrer"&gt;Cloudfront Functions tutorial&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  terraform example
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Configuring Cloudfront Functions
&lt;/h4&gt;

&lt;p&gt;Define resources like this. (Parts not directly related to AWS Provider settings, etc., are omitted.)&lt;br&gt;
See &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_function" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for a description of the parameters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write the actual process in &lt;code&gt;code&lt;/code&gt;. Here, it is read from a separate file.&lt;/li&gt;
&lt;li&gt;You can make it public by setting &lt;code&gt;publish = true&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_cloudfront_function" "spa_redirect" {
  name = "${var.product_name}-${var.env}-spa-redirect"
  runtime = "cloudfront-js-1.0"
  comment = "${var.product_name}-${var.env}-spa-redirect"
  publish = true
  code = file("cloudfront_functions/spa-redirect.js")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  function part
&lt;/h4&gt;

&lt;p&gt;It is written in a very old fashioned way, but for some reason it needs to be &lt;code&gt;ECMAScript 5.1 compliant&lt;/code&gt;.&lt;br&gt;
I would be very grateful if you could improve on this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The presence or absence of extensions is determined by the presence or absence of a dot. &lt;code&gt;if(request.uri.indexOf(".")) === -1)&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var index = '/index.html';
function handler(event) {
    var request = event.request;
    // if extension not found (access not real file)
    if(request.uri.indexOf(".")) === -1) {
        request.uri = index;
    }
    return request;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Configuring Cloudfront Functions to adapt to Cloudfront
&lt;/h4&gt;

&lt;p&gt;This is a long list, but all you need to focus on is the &lt;code&gt;function_association&lt;/code&gt; at the bottom.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can set it for each behavior!&lt;/li&gt;
&lt;li&gt;The event_type is at the time of request, so we'll write &lt;code&gt;viewer-request&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For more information on event_type, please refer to &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-cloudfront-trigger-events.html" rel="noopener noreferrer"&gt;here&lt;/a&gt; for more information about event_type.
Cloudfront Functions supports only &lt;code&gt;viewer-request&lt;/code&gt; and &lt;code&gt;viewer-response&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_cloudfront_distribution" "front" {
  origin {
    domain_name = aws_s3_bucket.front.bucket_regional_domain_name
    origin_id = local.s3_origin_id

    s3_origin_config {
      origin_access_identity = aws_cloudfront_origin_access_identity.origin.cloudfront_access_identity_path
    }
  }
  enabled = true
  is_ipv6_enabled = true
  comment = "${var.product_name}-${var.env}"
  default_root_object = "index.html"
  aliases = ["test.${data.terraform_remote_state.network.outputs.domain}"]

  default_cache_behavior {
    allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]]
    cached_methods = ["GET", "HEAD"].
    target_origin_id = local.s3_origin_id

    forwarded_values {
      query_string = true

      cookies {
        forward = "none"
      }
    }

    function_association {
      event_type = "viewer-request"
      function_arn = aws_cloudfront_function.spa_redirect.arn
    }

    viewer_protocol_policy = "allow-all"
    min_ttl = 0
    default_ttl = 0
    max_ttl = 0

  }
... Omitted.
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  I want to separate /api
&lt;/h2&gt;

&lt;p&gt;As mentioned above, you can use cloudfront's behavior to separate them.&lt;br&gt;
Many people create API Gateways using the serverless framework or CDK.&lt;br&gt;
It is convenient to get values from Cloudformation.&lt;/p&gt;

&lt;p&gt;Point.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the endpoint and stage from the cloudformation in the &lt;code&gt;domain_name&lt;/code&gt; of &lt;code&gt;origin&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Specify &lt;code&gt;api/*&lt;/code&gt; in &lt;code&gt;path_pattern&lt;/code&gt; of &lt;code&gt;behavior&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Since &lt;code&gt;/api&lt;/code&gt; is not needed to access the APIGateway, it is removed by lambda@edge (this could also be replaced by cloudfront functions).
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_cloudfront_distribution" "front" {
...
  origin {
    custom_origin_config {
      http_port = "80"
      https_port = "443"
      origin_protocol_policy = "https-only"
      origin_ssl_protocols = ["TLSv1.2"]
    }
    # Extract xxx.execute-api.ap-northeast-1.amazonaws.com and v1 from ServiceEndpoint
    domain_name = split("/", data.aws_cloudformation_stack.api_test.outputs["ServiceEndpoint"])[2])
    origin_id = var.product_name
  }
...
  ordered_cache_behavior {
    path_pattern = "api/*"
    allowed_methods = ["HEAD", "DELETE", "POST", "GET", "OPTIONS", "PUT", "PATCH"].
    cached_methods = ["GET", "HEAD"].
    target_origin_id = var.product_name

    forwarded_values {
      headers = ["Authorization"].
      query_string = true
      cookies {
        forward = "none"
      }
    }

    min_ttl = 0
    default_ttl = 10
    max_ttl = 10
    viewer_protocol_policy = "https-only"
    lambda_function_association {
      event_type = "origin-request"
      lambda_arn = aws_lambda_function.redirect_trim_context.qualified_arn
    }

  }
...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all, obvious! Some of you may think it is, but we hope it will help you if you have any doubts when configuring your infrastructure.&lt;/p&gt;

&lt;p&gt;Reference.&lt;br&gt;
&lt;a href="https://qiita.com/seapolis/items/0afd49d24b12749d93ab" rel="noopener noreferrer"&gt;SPA routing process with AWS CloudFront Functions&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
