<?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>DEV Community: John  Ajera</title>
    <description>The latest articles on DEV Community by John  Ajera (@jajera).</description>
    <link>https://dev.to/jajera</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%2F2461160%2Fefda9ea8-49f8-4d07-b358-445b8e7d5a20.png</url>
      <title>DEV Community: John  Ajera</title>
      <link>https://dev.to/jajera</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jajera"/>
    <language>en</language>
    <item>
      <title>Why does my AWS SSO session die so fast? (And how to change it in the console)</title>
      <dc:creator>John  Ajera</dc:creator>
      <pubDate>Fri, 17 Apr 2026 21:13:08 +0000</pubDate>
      <link>https://dev.to/jajera/why-does-my-aws-sso-session-die-so-fast-and-how-to-change-it-in-the-console-455k</link>
      <guid>https://dev.to/jajera/why-does-my-aws-sso-session-die-so-fast-and-how-to-change-it-in-the-console-455k</guid>
      <description>&lt;h2&gt;
  
  
  Why does my AWS SSO session die so fast? (And how to change it in the console)
&lt;/h2&gt;

&lt;p&gt;Ever open the &lt;strong&gt;AWS access portal&lt;/strong&gt;, click into an account, get a few things done, grab coffee, come back—and &lt;strong&gt;your session is gone&lt;/strong&gt;? You sign in again through &lt;strong&gt;IAM Identity Center&lt;/strong&gt; (what people still call &lt;strong&gt;SSO&lt;/strong&gt;), mutter something about timeouts, and move on.&lt;/p&gt;

&lt;p&gt;I used to assume AWS was just being &lt;strong&gt;strict&lt;/strong&gt; for security. Partly true. The other part is boring: &lt;strong&gt;the permission set is probably still on the default session length&lt;/strong&gt;, which is often &lt;strong&gt;one hour&lt;/strong&gt;. Once you know that, fixing it is a few clicks in the &lt;strong&gt;console&lt;/strong&gt;. No &lt;strong&gt;Terraform&lt;/strong&gt;, no &lt;strong&gt;CLI&lt;/strong&gt;—just the UI.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Overview
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What you are changing:&lt;/strong&gt; &lt;strong&gt;Session duration&lt;/strong&gt; on an &lt;strong&gt;IAM Identity Center permission set&lt;/strong&gt; (how long &lt;strong&gt;console&lt;/strong&gt; and &lt;strong&gt;CLI&lt;/strong&gt; sessions issued through that set stay valid before you must re-authenticate through the portal).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why it felt short:&lt;/strong&gt; &lt;strong&gt;Default&lt;/strong&gt; duration for a new permission set is commonly &lt;strong&gt;1 hour&lt;/strong&gt; unless someone already raised it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What this article is not:&lt;/strong&gt; &lt;strong&gt;IAM roles&lt;/strong&gt; in accounts, &lt;strong&gt;temporary credentials&lt;/strong&gt; from &lt;code&gt;sts:AssumeRole&lt;/code&gt; in automation, or &lt;strong&gt;Control Tower&lt;/strong&gt; landing zone knobs—only &lt;strong&gt;Identity Center permission sets&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You can sign in to the &lt;strong&gt;AWS Management Console&lt;/strong&gt; for the &lt;strong&gt;organization&lt;/strong&gt; that owns &lt;strong&gt;IAM Identity Center&lt;/strong&gt; (often the &lt;strong&gt;management&lt;/strong&gt; or &lt;strong&gt;delegated admin&lt;/strong&gt; account where the directory lives).&lt;/li&gt;
&lt;li&gt;Your user (or role) has permission to &lt;strong&gt;edit permission sets&lt;/strong&gt; (for example an &lt;strong&gt;IAM Identity Center&lt;/strong&gt; administrator).&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Open the permission set
&lt;/h3&gt;

&lt;h4&gt;
  
  
  3.1 Go to IAM Identity Center
&lt;/h4&gt;

&lt;p&gt;In the console, open &lt;strong&gt;&lt;a href="https://console.aws.amazon.com/singlesignon/" rel="noopener noreferrer"&gt;IAM Identity Center&lt;/a&gt;&lt;/strong&gt; (search &lt;strong&gt;IAM Identity Center&lt;/strong&gt; in the top search bar if the console layout moves again).&lt;/p&gt;

&lt;h4&gt;
  
  
  3.2 Open Permission sets
&lt;/h4&gt;

&lt;p&gt;Choose &lt;strong&gt;Permission sets&lt;/strong&gt; in the left navigation. Select the &lt;strong&gt;permission set&lt;/strong&gt; you actually use for day-to-day access (for example &lt;strong&gt;AdministratorAccess&lt;/strong&gt;, &lt;strong&gt;PowerUser&lt;/strong&gt;, or a custom name your org created).&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Edit session duration
&lt;/h3&gt;

&lt;h4&gt;
  
  
  4.1 Edit settings
&lt;/h4&gt;

&lt;p&gt;On the permission set details page, choose &lt;strong&gt;Edit&lt;/strong&gt; (sometimes shown as &lt;strong&gt;Edit configuration&lt;/strong&gt; depending on the console version).&lt;/p&gt;

&lt;h4&gt;
  
  
  4.2 Find session duration
&lt;/h4&gt;

&lt;p&gt;Look for &lt;strong&gt;Session duration&lt;/strong&gt; (wording may be &lt;strong&gt;Session length&lt;/strong&gt; or similar). You will see a value in &lt;strong&gt;hours&lt;/strong&gt; (or a dropdown bounded by what &lt;strong&gt;Identity Center&lt;/strong&gt; allows for that permission set).&lt;/p&gt;

&lt;p&gt;Pick something that matches how you work—for example &lt;strong&gt;8 hours&lt;/strong&gt; for a normal workday—without ignoring your org’s security policy. &lt;strong&gt;Shorter&lt;/strong&gt; is generally safer for &lt;strong&gt;high-privilege&lt;/strong&gt; sets; &lt;strong&gt;longer&lt;/strong&gt; is more convenient and widens the window if a session is misused.&lt;/p&gt;

&lt;h4&gt;
  
  
  4.3 Save
&lt;/h4&gt;

&lt;p&gt;Save the change. &lt;strong&gt;Existing&lt;/strong&gt; sessions do not always pick up the new limit immediately; people may need to &lt;strong&gt;sign out&lt;/strong&gt; of the access portal and &lt;strong&gt;sign in again&lt;/strong&gt; (or wait for the current session to expire) before the new duration applies.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Summary
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Short timeouts&lt;/strong&gt; after portal login are often just the &lt;strong&gt;permission set default&lt;/strong&gt; (commonly &lt;strong&gt;one hour&lt;/strong&gt;), not a mystery AWS punishment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM Identity Center&lt;/strong&gt; → &lt;strong&gt;Permission sets&lt;/strong&gt; → your set → &lt;strong&gt;Edit&lt;/strong&gt; → &lt;strong&gt;Session duration&lt;/strong&gt; → save. &lt;strong&gt;Re-login&lt;/strong&gt; if you do not see the new behavior yet.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  6. References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/howtosessionduration.html" rel="noopener noreferrer"&gt;Configure session duration for AWS IAM Identity Center&lt;/a&gt; (AWS Documentation)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>iam</category>
      <category>identitycenter</category>
      <category>sso</category>
    </item>
    <item>
      <title>Unenroll a Control Tower sandbox member with the AWS CLI</title>
      <dc:creator>John  Ajera</dc:creator>
      <pubDate>Thu, 16 Apr 2026 09:28:40 +0000</pubDate>
      <link>https://dev.to/jajera/unenroll-a-control-tower-sandbox-member-with-the-aws-cli-22ij</link>
      <guid>https://dev.to/jajera/unenroll-a-control-tower-sandbox-member-with-the-aws-cli-22ij</guid>
      <description>&lt;h2&gt;
  
  
  Unenroll a Control Tower sandbox member with the AWS CLI
&lt;/h2&gt;

&lt;p&gt;You enrolled a &lt;strong&gt;member account&lt;/strong&gt; into &lt;strong&gt;AWS Control Tower&lt;/strong&gt; (for example a &lt;strong&gt;sandbox&lt;/strong&gt; workload account) and later want it &lt;strong&gt;not&lt;/strong&gt; governed by the landing zone anymore. The console path is &lt;strong&gt;Organization&lt;/strong&gt; then &lt;strong&gt;Unmanage&lt;/strong&gt;. This article is the &lt;strong&gt;CLI&lt;/strong&gt; path: terminate the &lt;strong&gt;Control Tower&lt;/strong&gt; &lt;strong&gt;Service Catalog&lt;/strong&gt; provisioned product that represents &lt;strong&gt;account enrollment&lt;/strong&gt;, without closing the AWS account.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Overview
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goal:&lt;/strong&gt; &lt;strong&gt;Unenroll&lt;/strong&gt; (unmanage) one &lt;strong&gt;member account&lt;/strong&gt; so &lt;strong&gt;Control Tower&lt;/strong&gt; removes its controls and &lt;strong&gt;Service Catalog&lt;/strong&gt; enrollment, while &lt;strong&gt;keeping&lt;/strong&gt; the account open in &lt;strong&gt;AWS Organizations&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mechanism:&lt;/strong&gt; &lt;strong&gt;Terminate&lt;/strong&gt; the &lt;strong&gt;&lt;code&gt;CONTROL_TOWER_ACCOUNT&lt;/code&gt;&lt;/strong&gt; provisioned product whose name looks like &lt;strong&gt;&lt;code&gt;Enroll-Account-&amp;lt;12-digit-account-id&amp;gt;&lt;/code&gt;&lt;/strong&gt;, with &lt;strong&gt;&lt;code&gt;--retain-physical-resources&lt;/code&gt;&lt;/strong&gt; so the &lt;strong&gt;account&lt;/strong&gt; itself is not deleted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where it runs:&lt;/strong&gt; &lt;strong&gt;Management account&lt;/strong&gt; credentials with permission to &lt;strong&gt;Service Catalog&lt;/strong&gt; APIs (and typically &lt;strong&gt;Organizations&lt;/strong&gt; read if you verify parent OU afterward).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Region:&lt;/strong&gt; Use your &lt;strong&gt;Control Tower home region&lt;/strong&gt; (often where &lt;strong&gt;Account Factory&lt;/strong&gt; and enrollment products live). Examples below use &lt;strong&gt;&lt;code&gt;ap-southeast-2&lt;/code&gt;&lt;/strong&gt;; substitute yours if different.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Management account&lt;/strong&gt; access (for example &lt;strong&gt;IAM Identity Center&lt;/strong&gt; admin role on the org payer).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS CLI&lt;/strong&gt; v2 configured for that account (&lt;code&gt;aws sts get-caller-identity&lt;/code&gt; shows the management id).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;member account id&lt;/strong&gt; you are unmanaging (12 digits).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patience:&lt;/strong&gt; termination is &lt;strong&gt;asynchronous&lt;/strong&gt;; poll &lt;strong&gt;&lt;code&gt;describe-record&lt;/code&gt;&lt;/strong&gt; until &lt;strong&gt;&lt;code&gt;SUCCEEDED&lt;/code&gt;&lt;/strong&gt; or diagnose &lt;strong&gt;&lt;code&gt;FAILED&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Confirm identity and region
&lt;/h3&gt;

&lt;h4&gt;
  
  
  3.1 Caller identity
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-management-profile

aws sts get-caller-identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm &lt;strong&gt;&lt;code&gt;Account&lt;/code&gt;&lt;/strong&gt; is the &lt;strong&gt;organization management&lt;/strong&gt; account.&lt;/p&gt;

&lt;h4&gt;
  
  
  3.2 Control Tower home region
&lt;/h4&gt;

&lt;p&gt;If you are unsure which region holds enrollment products, try the region where you normally open &lt;strong&gt;Control Tower&lt;/strong&gt; and &lt;strong&gt;Account Factory&lt;/strong&gt;, then continue in &lt;strong&gt;Section 4&lt;/strong&gt; using that &lt;strong&gt;&lt;code&gt;--region&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Find the enrollment provisioned product
&lt;/h3&gt;

&lt;h4&gt;
  
  
  4.1 Scan provisioned products
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;scan-provisioned-products&lt;/code&gt;&lt;/strong&gt; returns up to &lt;strong&gt;20&lt;/strong&gt; items per call (use &lt;strong&gt;&lt;code&gt;PageToken&lt;/code&gt;&lt;/strong&gt; if you have many). Filter by eye or script for &lt;strong&gt;&lt;code&gt;Enroll-Account-&amp;lt;your-member-id&amp;gt;&lt;/code&gt;&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ap-southeast-2

aws servicecatalog scan-provisioned-products &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--page-size&lt;/span&gt; 20 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.2 Identify the fields you need
&lt;/h4&gt;

&lt;p&gt;From the matching object, note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Id&lt;/code&gt;&lt;/strong&gt; — the &lt;strong&gt;provisioned product id&lt;/strong&gt; (example shape: &lt;strong&gt;&lt;code&gt;pp-xxxxxxxxxxxxx&lt;/code&gt;&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Name&lt;/code&gt;&lt;/strong&gt; — often &lt;strong&gt;&lt;code&gt;Enroll-Account-405087531484&lt;/code&gt;&lt;/strong&gt; style.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Type&lt;/code&gt;&lt;/strong&gt; — should include &lt;strong&gt;&lt;code&gt;CONTROL_TOWER_ACCOUNT&lt;/code&gt;&lt;/strong&gt; for the enrollment product you want to end.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If nothing appears in the first page, paginate with &lt;strong&gt;&lt;code&gt;--page-token&lt;/code&gt;&lt;/strong&gt; from the response, or confirm &lt;strong&gt;region&lt;/strong&gt; and that the account was enrolled through &lt;strong&gt;Control Tower&lt;/strong&gt; (not only moved under an OU in &lt;strong&gt;Organizations&lt;/strong&gt; without enrollment).&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Terminate the provisioned product (unenroll)
&lt;/h3&gt;

&lt;p&gt;Use a &lt;strong&gt;unique&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;--terminate-token&lt;/code&gt;&lt;/strong&gt; per attempt (idempotency). &lt;strong&gt;&lt;code&gt;--retain-physical-resources&lt;/code&gt;&lt;/strong&gt; keeps the &lt;strong&gt;AWS account&lt;/strong&gt;; Control Tower still tears down &lt;strong&gt;CT-managed&lt;/strong&gt; resources it owns for that enrollment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ap-southeast-2
&lt;span class="nv"&gt;PP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pp-jxuf4ldvkwxxc

aws servicecatalog terminate-provisioned-product &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--provisioned-product-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PP_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--terminate-token&lt;/span&gt; &lt;span class="s2"&gt;"unenroll-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--retain-physical-resources&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes &lt;strong&gt;&lt;code&gt;RecordDetail.RecordId&lt;/code&gt;&lt;/strong&gt; (example shape: &lt;strong&gt;&lt;code&gt;rec-xxxxxxxxxxxxx&lt;/code&gt;&lt;/strong&gt;).&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Wait for the record to finish
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;describe-record&lt;/code&gt;&lt;/strong&gt; uses &lt;strong&gt;&lt;code&gt;--id&lt;/code&gt;&lt;/strong&gt; set to the &lt;strong&gt;record id&lt;/strong&gt; (not &lt;strong&gt;&lt;code&gt;--record-id&lt;/code&gt;&lt;/strong&gt; on the CLI).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ap-southeast-2
&lt;span class="nv"&gt;RECORD_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rec-tupynci5numpi

aws servicecatalog describe-record &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RECORD_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'RecordDetail.{Status:Status,Errors:RecordErrors}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Poll until &lt;strong&gt;&lt;code&gt;Status&lt;/code&gt;&lt;/strong&gt; is &lt;strong&gt;&lt;code&gt;SUCCEEDED&lt;/code&gt;&lt;/strong&gt;. If &lt;strong&gt;&lt;code&gt;FAILED&lt;/code&gt;&lt;/strong&gt;, read &lt;strong&gt;&lt;code&gt;RecordErrors&lt;/code&gt;&lt;/strong&gt; and fix permissions or open an &lt;strong&gt;AWS Support&lt;/strong&gt; case for &lt;strong&gt;Service Catalog&lt;/strong&gt; / &lt;strong&gt;Control Tower&lt;/strong&gt; as appropriate.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Verify organization placement
&lt;/h3&gt;

&lt;p&gt;After a successful &lt;strong&gt;unmanage&lt;/strong&gt;, the account usually moves under the &lt;strong&gt;organization root&lt;/strong&gt; (no longer under a &lt;strong&gt;Control Tower&lt;/strong&gt;-registered OU for that enrollment). Confirm with &lt;strong&gt;Organizations&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;MEMBER_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;405087531484

aws organizations list-parents &lt;span class="nt"&gt;--child-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MEMBER_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expect &lt;strong&gt;&lt;code&gt;Type&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;ROOT&lt;/code&gt;&lt;/strong&gt; for the parent you care about when the account has been moved out of &lt;strong&gt;Control Tower&lt;/strong&gt;-managed OUs per product behavior.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Also expect:&lt;/strong&gt; &lt;strong&gt;Control Tower&lt;/strong&gt; removes &lt;strong&gt;&lt;code&gt;AWSControlTowerExecution&lt;/code&gt;&lt;/strong&gt; from the member when it cleans up. If you manage that role elsewhere (for example &lt;strong&gt;Terraform&lt;/strong&gt; in a factory repo), run your pipeline again so intent matches reality.&lt;/p&gt;




&lt;h3&gt;
  
  
  8. Console alternative
&lt;/h3&gt;

&lt;p&gt;If you prefer the UI: &lt;a href="https://docs.aws.amazon.com/controltower/latest/userguide/unmanage-account.html" rel="noopener noreferrer"&gt;Unenroll an account&lt;/a&gt; — &lt;strong&gt;Control Tower&lt;/strong&gt; console, &lt;strong&gt;Organization&lt;/strong&gt;, expand the OU, select the account, &lt;strong&gt;Unmanage&lt;/strong&gt;, wait until status shows &lt;strong&gt;Not enrolled&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  9. Summary: Copy-paste
&lt;/h3&gt;

&lt;p&gt;Replace &lt;strong&gt;&lt;code&gt;REGION&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;PP_ID&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;AWS_PROFILE&lt;/code&gt;&lt;/strong&gt;, and optionally &lt;strong&gt;&lt;code&gt;MEMBER_ID&lt;/code&gt;&lt;/strong&gt; for checks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-management-profile
&lt;span class="nv"&gt;REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ap-southeast-2

aws sts get-caller-identity

aws servicecatalog scan-provisioned-products &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--page-size&lt;/span&gt; 20 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table

&lt;span class="nv"&gt;PP_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pp-REPLACE_ME
aws servicecatalog terminate-provisioned-product &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--provisioned-product-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PP_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--terminate-token&lt;/span&gt; &lt;span class="s2"&gt;"unenroll-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--retain-physical-resources&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; json

&lt;span class="nv"&gt;RECORD_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rec-REPLACE_ME
aws servicecatalog describe-record &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RECORD_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; json

&lt;span class="nv"&gt;MEMBER_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;405087531484
aws organizations list-parents &lt;span class="nt"&gt;--child-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MEMBER_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  10. Troubleshooting
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;What to try&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;scan-provisioned-products&lt;/code&gt;&lt;/strong&gt; returns no &lt;strong&gt;&lt;code&gt;Enroll-Account-…&lt;/code&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Wrong &lt;strong&gt;&lt;code&gt;REGION&lt;/code&gt;&lt;/strong&gt;; use &lt;strong&gt;Control Tower home&lt;/strong&gt; region. Or the account was never &lt;strong&gt;enrolled&lt;/strong&gt; via &lt;strong&gt;Control Tower&lt;/strong&gt; (only placed in an OU with &lt;strong&gt;Organizations&lt;/strong&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;terminate-provisioned-product&lt;/code&gt;&lt;/strong&gt; access denied&lt;/td&gt;
&lt;td&gt;Principal lacks &lt;strong&gt;Service Catalog&lt;/strong&gt; terminate permissions in the management account.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;describe-record&lt;/code&gt;&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;FAILED&lt;/code&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Read &lt;strong&gt;&lt;code&gt;RecordErrors&lt;/code&gt;&lt;/strong&gt;; may be &lt;strong&gt;Control Tower&lt;/strong&gt; internal constraint, &lt;strong&gt;SCP&lt;/strong&gt;, or &lt;strong&gt;account state&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;ParamValidation&lt;/code&gt;&lt;/strong&gt; on &lt;strong&gt;&lt;code&gt;describe-record&lt;/code&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Use &lt;strong&gt;&lt;code&gt;--id&lt;/code&gt;&lt;/strong&gt;, not &lt;strong&gt;&lt;code&gt;--record-id&lt;/code&gt;&lt;/strong&gt;, for &lt;strong&gt;&lt;code&gt;record-id&lt;/code&gt;&lt;/strong&gt; style values.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Account should stay in a specific OU after unenroll&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Unmanage&lt;/strong&gt; moves the account to &lt;strong&gt;root&lt;/strong&gt; per &lt;strong&gt;AWS&lt;/strong&gt; docs; move it again with &lt;strong&gt;&lt;code&gt;aws organizations move-account&lt;/code&gt;&lt;/strong&gt; if your org design requires a different OU.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  11. References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/controltower/latest/userguide/unmanage-account.html" rel="noopener noreferrer"&gt;Unenroll an account&lt;/a&gt; (Control Tower User Guide)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/controltower/latest/userguide/unenroll-with-sc.html" rel="noopener noreferrer"&gt;Unenroll an account in Service Catalog&lt;/a&gt; (terminate provisioned product)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/servicecatalog/latest/dg/API_TerminateProvisionedProduct.html" rel="noopener noreferrer"&gt;TerminateProvisionedProduct&lt;/a&gt; (API reference)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/servicecatalog/latest/dg/API_DescribeRecord.html" rel="noopener noreferrer"&gt;DescribeRecord&lt;/a&gt; (API reference)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/organizations/latest/APIReference/API_MoveAccount.html" rel="noopener noreferrer"&gt;MoveAccount&lt;/a&gt; (Organizations API, if you relocate after unenroll)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>controltower</category>
      <category>organizations</category>
      <category>cli</category>
    </item>
    <item>
      <title>IAM Identity Center account assignments for Terraform member accounts</title>
      <dc:creator>John  Ajera</dc:creator>
      <pubDate>Wed, 15 Apr 2026 19:27:14 +0000</pubDate>
      <link>https://dev.to/jajera/iam-identity-center-account-assignments-for-terraform-member-accounts-4p30</link>
      <guid>https://dev.to/jajera/iam-identity-center-account-assignments-for-terraform-member-accounts-4p30</guid>
      <description>&lt;h2&gt;
  
  
  IAM Identity Center account assignments for Terraform member accounts
&lt;/h2&gt;

&lt;p&gt;Once &lt;strong&gt;sandbox&lt;/strong&gt; and &lt;strong&gt;development&lt;/strong&gt; &lt;strong&gt;member accounts&lt;/strong&gt; exist in &lt;strong&gt;AWS Organizations&lt;/strong&gt; (for example from Terraform as in a &lt;strong&gt;companion article&lt;/strong&gt; on that layout), they do &lt;strong&gt;not&lt;/strong&gt; automatically appear in the &lt;strong&gt;AWS access portal&lt;/strong&gt;. &lt;strong&gt;IAM Identity Center&lt;/strong&gt; (successor to AWS SSO) shows an account only when there is an &lt;strong&gt;account assignment&lt;/strong&gt;: a &lt;strong&gt;user or group&lt;/strong&gt; mapped to a &lt;strong&gt;permission set&lt;/strong&gt; in that &lt;strong&gt;account&lt;/strong&gt;. This guide explains that model and shows a compact &lt;strong&gt;Terraform&lt;/strong&gt; pattern: look up the &lt;strong&gt;SSO instance&lt;/strong&gt;, resolve an &lt;strong&gt;existing permission set&lt;/strong&gt; and &lt;strong&gt;group&lt;/strong&gt; by name, and create &lt;strong&gt;&lt;code&gt;aws_ssoadmin_account_assignment&lt;/code&gt;&lt;/strong&gt; for each member account. It assumes a &lt;strong&gt;Control Tower–friendly&lt;/strong&gt; landing zone where Identity Center is already enabled in the &lt;strong&gt;organization management&lt;/strong&gt; account; it does &lt;strong&gt;not&lt;/strong&gt; rehash &lt;strong&gt;Organizations&lt;/strong&gt; account creation itself.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Overview
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problem:&lt;/strong&gt; New &lt;strong&gt;member accounts&lt;/strong&gt; have &lt;strong&gt;root&lt;/strong&gt; users and &lt;strong&gt;OrganizationAccountAccessRole&lt;/strong&gt;, but &lt;strong&gt;directory users&lt;/strong&gt; do not see the accounts in the &lt;strong&gt;access portal&lt;/strong&gt; until &lt;strong&gt;assignments&lt;/strong&gt; exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approach:&lt;/strong&gt; Terraform calls &lt;strong&gt;SSO Admin&lt;/strong&gt; and &lt;strong&gt;Identity Store&lt;/strong&gt; in the &lt;strong&gt;region where Identity Center is enabled&lt;/strong&gt;, then creates one &lt;strong&gt;assignment per account&lt;/strong&gt; (sandbox and dev) for the same &lt;strong&gt;group&lt;/strong&gt; and &lt;strong&gt;permission set&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How this article reads:&lt;/strong&gt; The &lt;strong&gt;Terraform&lt;/strong&gt; below uses &lt;strong&gt;fixed illustration values&lt;/strong&gt; (region, permission set &lt;strong&gt;name&lt;/strong&gt;, group &lt;strong&gt;display name&lt;/strong&gt;) and &lt;strong&gt;no&lt;/strong&gt; &lt;code&gt;count&lt;/code&gt;, toggles, or &lt;code&gt;precondition&lt;/code&gt; blocks so the flow is easy to scan. In a real repo you usually add &lt;strong&gt;variables&lt;/strong&gt;, optional &lt;strong&gt;&lt;code&gt;count&lt;/code&gt;&lt;/strong&gt;, and &lt;strong&gt;guardrails&lt;/strong&gt;; a short note after the snippet calls that out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope:&lt;/strong&gt; &lt;strong&gt;Group&lt;/strong&gt; principals and an &lt;strong&gt;existing&lt;/strong&gt; permission set &lt;strong&gt;by name&lt;/strong&gt;; creating permission sets or SCIM groups is out of scope here.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IAM Identity Center&lt;/strong&gt; enabled in the org (&lt;strong&gt;management&lt;/strong&gt; account or documented &lt;strong&gt;delegated admin&lt;/strong&gt;); you know the &lt;strong&gt;region&lt;/strong&gt; where the portal was turned on (often the home region you use for admin work).&lt;/li&gt;
&lt;li&gt;At least one &lt;strong&gt;permission set&lt;/strong&gt; already defined (for example &lt;strong&gt;&lt;code&gt;AdministratorAccess&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;AWSPowerUserAccess&lt;/code&gt;&lt;/strong&gt;) and one &lt;strong&gt;group&lt;/strong&gt; with a known &lt;strong&gt;display name&lt;/strong&gt; in the Identity Center directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform&lt;/strong&gt; &lt;strong&gt;1.5+&lt;/strong&gt; and &lt;strong&gt;hashicorp/aws&lt;/strong&gt; provider &lt;strong&gt;5.x&lt;/strong&gt; (examples below use patterns compatible with that stack).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;IAM principal&lt;/strong&gt; that runs Terraform must be allowed &lt;strong&gt;SSO Admin&lt;/strong&gt; and &lt;strong&gt;Identity Store&lt;/strong&gt; actions used by the provider (see &lt;strong&gt;§6&lt;/strong&gt;). The same principal typically already has &lt;strong&gt;Organizations&lt;/strong&gt; access if this module also manages accounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Member account ids&lt;/strong&gt; from Terraform outputs (or the console) for &lt;strong&gt;sandbox&lt;/strong&gt; and &lt;strong&gt;dev&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Background: &lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html" rel="noopener noreferrer"&gt;IAM Identity Center&lt;/a&gt; and Terraform &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_account_assignment" rel="noopener noreferrer"&gt;&lt;code&gt;aws_ssoadmin_account_assignment&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. How the portal decides what to list
&lt;/h3&gt;

&lt;p&gt;Flow (ASCII):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  User opens access portal URL
           |
           v
  IAM Identity Center  ---- evaluates  ----&amp;gt;  Account assignments
  (directory + permission sets)              (group X + permission set P + account A)
           |
           v
  User sees account tiles only where an assignment exists for that user or their groups
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Account assignment&lt;/strong&gt; ties three things together: &lt;strong&gt;principal&lt;/strong&gt; (here a &lt;strong&gt;group&lt;/strong&gt;), &lt;strong&gt;permission set&lt;/strong&gt; (IAM role shape in the target account), and &lt;strong&gt;target&lt;/strong&gt; (&lt;strong&gt;AWS account&lt;/strong&gt; id). Without that row, the account stays invisible in the portal for directory users even if the account is healthy in Organizations.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Terraform shape
&lt;/h3&gt;

&lt;p&gt;SSO Admin and Identity Store APIs are invoked in the &lt;strong&gt;region where IAM Identity Center is enabled&lt;/strong&gt;. The snippet uses an &lt;strong&gt;aliased&lt;/strong&gt; provider with a &lt;strong&gt;literal region&lt;/strong&gt; so it is obvious what to change first if your portal runs elsewhere (for example &lt;strong&gt;&lt;code&gt;us-east-1&lt;/code&gt;&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Replace for your org:&lt;/strong&gt; &lt;code&gt;ap-southeast-2&lt;/code&gt;, &lt;strong&gt;&lt;code&gt;AdministratorAccess&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;AWSControlTowerAdmins&lt;/code&gt;&lt;/strong&gt;, and ensure &lt;strong&gt;&lt;code&gt;aws_organizations_account.sandbox_1&lt;/code&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;code&gt;dev_1&lt;/code&gt;&lt;/strong&gt; match your module (or substitute &lt;strong&gt;12-digit account ids&lt;/strong&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;alias&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"identity_center"&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ap-southeast-2"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_ssoadmin_instances"&lt;/span&gt; &lt;span class="s2"&gt;"this"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identity_center&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_ssoadmin_permission_set"&lt;/span&gt; &lt;span class="s2"&gt;"assignment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identity_center&lt;/span&gt;

  &lt;span class="nx"&gt;instance_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ssoadmin_instances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AdministratorAccess"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_identitystore_group"&lt;/span&gt; &lt;span class="s2"&gt;"assignment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identity_center&lt;/span&gt;

  &lt;span class="nx"&gt;identity_store_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ssoadmin_instances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identity_store_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;alternate_identifier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;unique_attribute&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;attribute_path&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DisplayName"&lt;/span&gt;
      &lt;span class="nx"&gt;attribute_value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWSControlTowerAdmins"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ssoadmin_account_assignment"&lt;/span&gt; &lt;span class="s2"&gt;"sandbox_1"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identity_center&lt;/span&gt;

  &lt;span class="nx"&gt;instance_arn&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ssoadmin_instances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;permission_set_arn&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ssoadmin_permission_set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;principal_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_identitystore_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;group_id&lt;/span&gt;
  &lt;span class="nx"&gt;principal_type&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GROUP"&lt;/span&gt;
  &lt;span class="nx"&gt;target_id&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_organizations_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sandbox_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;target_type&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS_ACCOUNT"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ssoadmin_account_assignment"&lt;/span&gt; &lt;span class="s2"&gt;"dev_1"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;identity_center&lt;/span&gt;

  &lt;span class="nx"&gt;instance_arn&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ssoadmin_instances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;permission_set_arn&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ssoadmin_permission_set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;principal_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_identitystore_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;group_id&lt;/span&gt;
  &lt;span class="nx"&gt;principal_type&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"GROUP"&lt;/span&gt;
  &lt;span class="nx"&gt;target_id&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_organizations_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;target_type&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS_ACCOUNT"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;principal_type&lt;/code&gt;&lt;/strong&gt; can be &lt;strong&gt;&lt;code&gt;USER&lt;/code&gt;&lt;/strong&gt; if you resolve a user and pass that &lt;strong&gt;principal id&lt;/strong&gt; instead; &lt;strong&gt;groups&lt;/strong&gt; are a common default for &lt;strong&gt;team&lt;/strong&gt; access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Production note:&lt;/strong&gt; Real modules usually &lt;strong&gt;parameterize&lt;/strong&gt; the region, permission set &lt;strong&gt;name&lt;/strong&gt;, and group &lt;strong&gt;display name&lt;/strong&gt; (for example with &lt;strong&gt;variables&lt;/strong&gt;), and sometimes wrap assignments in &lt;strong&gt;&lt;code&gt;count&lt;/code&gt;&lt;/strong&gt; or use &lt;strong&gt;&lt;code&gt;lifecycle.precondition&lt;/code&gt;&lt;/strong&gt; so you never plan with an &lt;strong&gt;empty&lt;/strong&gt; group name or enable SSO changes by mistake. The literal version above is only for &lt;strong&gt;reading&lt;/strong&gt; the happy path.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. IAM permissions for the Terraform principal
&lt;/h3&gt;

&lt;p&gt;Your role or user needs API access for &lt;strong&gt;read&lt;/strong&gt; paths (instance, permission set, group lookup) and &lt;strong&gt;write&lt;/strong&gt; paths (&lt;strong&gt;CreateAccountAssignment&lt;/strong&gt;, &lt;strong&gt;DeleteAccountAssignment&lt;/strong&gt; on destroy). Typical action families include &lt;strong&gt;SSO Admin&lt;/strong&gt; (&lt;code&gt;sso:ListInstances&lt;/code&gt;, &lt;code&gt;sso:DescribePermissionSet&lt;/code&gt;, &lt;code&gt;sso:CreateAccountAssignment&lt;/code&gt;, …) and &lt;strong&gt;Identity Store&lt;/strong&gt; (&lt;code&gt;identitystore:GetGroupId&lt;/code&gt;, &lt;code&gt;identitystore:DescribeGroup&lt;/code&gt;, …). &lt;strong&gt;Tighten&lt;/strong&gt; ARNs and actions in a real policy; start from the &lt;strong&gt;Terraform plan&lt;/strong&gt; error messages if something is missing.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Summary: Copy-paste
&lt;/h3&gt;

&lt;p&gt;After pasting the resources into your root module and fixing the &lt;strong&gt;illustration&lt;/strong&gt; strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YourManagementProfile
terraform plan
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm in &lt;strong&gt;IAM Identity Center → Multi-account permissions → AWS accounts&lt;/strong&gt; (wording varies by console revision) that assignments exist for &lt;strong&gt;sandbox&lt;/strong&gt; and &lt;strong&gt;dev&lt;/strong&gt;. Ask a member of the &lt;strong&gt;group&lt;/strong&gt; to open the &lt;strong&gt;access portal&lt;/strong&gt; and verify &lt;strong&gt;two&lt;/strong&gt; new account tiles (after &lt;strong&gt;propagation&lt;/strong&gt;, which can take a short time).&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Troubleshooting
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AccessDenied&lt;/code&gt; on &lt;code&gt;sso:ListInstances&lt;/code&gt;:&lt;/strong&gt; The caller’s policy omits &lt;strong&gt;SSO Admin&lt;/strong&gt; permissions, or Terraform is using the &lt;strong&gt;wrong region&lt;/strong&gt; for the &lt;strong&gt;aliased&lt;/strong&gt; provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;GetGroupId&lt;/code&gt; validation / empty attribute:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;attribute_value&lt;/code&gt;&lt;/strong&gt; for the group was empty or wrong; set it to the exact &lt;strong&gt;display name&lt;/strong&gt; from &lt;strong&gt;IAM Identity Center → Groups&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permission set not found:&lt;/strong&gt; The &lt;strong&gt;&lt;code&gt;name&lt;/code&gt;&lt;/strong&gt; in &lt;strong&gt;&lt;code&gt;aws_ssoadmin_permission_set&lt;/code&gt;&lt;/strong&gt; does not match any permission set &lt;strong&gt;name&lt;/strong&gt; in your directory (names are not always identical to &lt;strong&gt;AWS managed&lt;/strong&gt; policy names in the console).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group not found / ambiguous display name:&lt;/strong&gt; &lt;strong&gt;DisplayName&lt;/strong&gt; must be unique enough for the lookup; prefer the exact string from &lt;strong&gt;Groups&lt;/strong&gt; in the console.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Account still missing in portal:&lt;/strong&gt; Assignment targets the wrong &lt;strong&gt;account id&lt;/strong&gt;; user is not in the assigned &lt;strong&gt;group&lt;/strong&gt;; or &lt;strong&gt;propagation&lt;/strong&gt; not finished yet.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  8. References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html" rel="noopener noreferrer"&gt;What is IAM Identity Center?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/howtocreatepermissionset.html" rel="noopener noreferrer"&gt;Manage IAM Identity Center permission sets and assignments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_account_assignment" rel="noopener noreferrer"&gt;Terraform aws_ssoadmin_account_assignment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/identitystore_group" rel="noopener noreferrer"&gt;Terraform data aws_identitystore_group&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssoadmin_instances" rel="noopener noreferrer"&gt;Terraform data aws_ssoadmin_instances&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>sso</category>
      <category>organizations</category>
    </item>
    <item>
      <title>Terraform member accounts for a Control Tower sandbox OU and a custom dev OU</title>
      <dc:creator>John  Ajera</dc:creator>
      <pubDate>Wed, 15 Apr 2026 19:06:36 +0000</pubDate>
      <link>https://dev.to/jajera/terraform-member-accounts-for-a-control-tower-sandbox-ou-and-a-custom-dev-ou-3ff2</link>
      <guid>https://dev.to/jajera/terraform-member-accounts-for-a-control-tower-sandbox-ou-and-a-custom-dev-ou-3ff2</guid>
      <description>&lt;h2&gt;
  
  
  Terraform member accounts for a Control Tower sandbox OU and a custom dev OU
&lt;/h2&gt;

&lt;p&gt;If you already run a landing zone where the &lt;strong&gt;sandbox&lt;/strong&gt; organizational unit (OU) exists under &lt;strong&gt;AWS Control Tower&lt;/strong&gt;, you may still want &lt;strong&gt;additional member accounts&lt;/strong&gt; created and updated with &lt;strong&gt;Infrastructure as Code&lt;/strong&gt; instead of only the console. This guide walks through a small Terraform root module pattern: one account in the &lt;strong&gt;existing&lt;/strong&gt; sandbox OU (by id) and one in a &lt;strong&gt;Terraform-managed&lt;/strong&gt; &lt;strong&gt;Development&lt;/strong&gt; OU, with &lt;strong&gt;remote state in S3&lt;/strong&gt;. The real repository may add optional guards or integrations beyond Organizations account creation; this post focuses on that core path. &lt;strong&gt;Continuous delivery&lt;/strong&gt; (for example GitHub Actions and OIDC) is left for a separate article.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Overview
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goal:&lt;/strong&gt; Repeatable &lt;strong&gt;AWS Organizations&lt;/strong&gt; member accounts for &lt;strong&gt;sandbox&lt;/strong&gt; and &lt;strong&gt;development&lt;/strong&gt;, tagged and placed under the right OUs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where it runs:&lt;/strong&gt; The &lt;strong&gt;management account&lt;/strong&gt; (or another principal with &lt;strong&gt;Organizations&lt;/strong&gt; APIs and trust to create accounts).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State:&lt;/strong&gt; Encrypted &lt;strong&gt;S3&lt;/strong&gt; backend with a &lt;strong&gt;lock&lt;/strong&gt; compatible with modern Terraform S3 native state locking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credentials:&lt;/strong&gt; Use whatever your team standardizes on for interactive or &lt;strong&gt;manual&lt;/strong&gt; applies (for example an &lt;strong&gt;IAM role&lt;/strong&gt; in the management account via &lt;strong&gt;SSO&lt;/strong&gt; or &lt;strong&gt;assume role&lt;/strong&gt;). The snippets below do not depend on a specific client tool beyond the Terraform CLI.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;An &lt;strong&gt;AWS Organization&lt;/strong&gt; with permission to &lt;strong&gt;create accounts&lt;/strong&gt; and &lt;strong&gt;OUs&lt;/strong&gt; from the account where you run Terraform.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;organizational unit id&lt;/strong&gt; for the sandbox OU where the first account should live (in a Control Tower setup this OU already exists; you copy its id from the console or CLI).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two unique root user email addresses&lt;/strong&gt; that are &lt;strong&gt;not&lt;/strong&gt; already used as the root email of any AWS account (plus-addressing on a single mailbox is fine).&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;S3 bucket&lt;/strong&gt; for Terraform state (create it out of band or in another stack); the &lt;strong&gt;IAM principal&lt;/strong&gt; you use for &lt;code&gt;terraform apply&lt;/code&gt; needs &lt;strong&gt;read/write&lt;/strong&gt; to that bucket and key (and any &lt;strong&gt;KMS&lt;/strong&gt; key policy if you use SSE-KMS).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terraform&lt;/strong&gt; &lt;strong&gt;1.5+&lt;/strong&gt; and the &lt;strong&gt;hashicorp/aws&lt;/strong&gt; provider (&lt;strong&gt;5.x&lt;/strong&gt; in the example below).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful background: &lt;a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_introduction.html" rel="noopener noreferrer"&gt;AWS Organizations User Guide&lt;/a&gt; and the Terraform resources &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/organizations_account" rel="noopener noreferrer"&gt;&lt;code&gt;aws_organizations_account&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/organizations_organizational_unit" rel="noopener noreferrer"&gt;&lt;code&gt;aws_organizations_organizational_unit&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Architecture
&lt;/h3&gt;

&lt;p&gt;Terraform runs in the &lt;strong&gt;management account&lt;/strong&gt; (from your workstation or any runner with the right credentials). It reads the organization root, creates (or refreshes) a &lt;strong&gt;Development&lt;/strong&gt; OU, creates two &lt;strong&gt;member accounts&lt;/strong&gt;, and persists state in &lt;strong&gt;S3&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Flow (ASCII):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Operator / runner                    Management account              AWS Organizations
  ----------------                     ------------------              -----------------

  Terraform CLI  ------------------&amp;gt;  Terraform run  -------------&amp;gt;  S3 (remote state)
  (plan / apply)                           |
                                           (Organizations API)
                                                |
                                                v
                                         Organization root
                                              |
                      +-----------------------+-----------------------+
                      |                                               |
                Sandbox OU                                     Development OU
              (already exists,                         (created by Terraform)
               e.g. Control Tower)
                      |                                               |
                      v                                               v
              Sandbox member account                         Dev member account
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. Terraform building blocks
&lt;/h3&gt;

&lt;h4&gt;
  
  
  4.1 Provider and versions
&lt;/h4&gt;

&lt;p&gt;Pin Terraform and the AWS provider; set the &lt;strong&gt;region&lt;/strong&gt; your team uses for the provider (Organizations APIs are global in behavior, but the provider still needs a region).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.5.0"&lt;/span&gt;

  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/aws"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 5.98.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.2 Data sources
&lt;/h4&gt;

&lt;p&gt;Read the &lt;strong&gt;current account&lt;/strong&gt; and the &lt;strong&gt;organization&lt;/strong&gt; so you can anchor the &lt;strong&gt;root&lt;/strong&gt; id for new OUs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_caller_identity"&lt;/span&gt; &lt;span class="s2"&gt;"current"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_organizations_organization"&lt;/span&gt; &lt;span class="s2"&gt;"current"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.3 Locals: sandbox OU id and tags
&lt;/h4&gt;

&lt;p&gt;The &lt;strong&gt;sandbox&lt;/strong&gt; account is placed under an OU that &lt;strong&gt;already exists&lt;/strong&gt; (for example one created by &lt;strong&gt;Control Tower&lt;/strong&gt;). That OU’s id is &lt;strong&gt;environment-specific&lt;/strong&gt;; store it in &lt;strong&gt;locals&lt;/strong&gt; (or a variable) instead of hard-coding in multiple resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# Example: existing sandbox OU — replace with your OU id from Organizations or Control Tower.&lt;/span&gt;
  &lt;span class="nx"&gt;sandbox_ou_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ou-xxxx-xxxxxxxx"&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;owner&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt;
    &lt;span class="nx"&gt;service&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"core"&lt;/span&gt;
    &lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;Development&lt;/strong&gt; OU, by contrast, is &lt;strong&gt;created by Terraform&lt;/strong&gt; in the next subsection.&lt;/p&gt;

&lt;h4&gt;
  
  
  4.3.1 Reusing the Control Tower sandbox OU (tradeoffs)
&lt;/h4&gt;

&lt;p&gt;There is &lt;strong&gt;no universal right answer&lt;/strong&gt;; it is a &lt;strong&gt;tradeoff&lt;/strong&gt; your team agrees on.&lt;/p&gt;

&lt;p&gt;When &lt;strong&gt;reusing&lt;/strong&gt; the existing sandbox OU tends to make sense:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Alignment with landing zone:&lt;/strong&gt; Control Tower (or your design) already defined a &lt;strong&gt;sandbox&lt;/strong&gt; OU for &lt;strong&gt;experimental&lt;/strong&gt; workloads; putting Terraform-created members there keeps the &lt;strong&gt;same guardrails and enrollment&lt;/strong&gt; model as other sandbox accounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less OU sprawl:&lt;/strong&gt; You avoid a parallel “sandbox” tree that confuses auditors or operators.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed:&lt;/strong&gt; The OU &lt;strong&gt;already exists&lt;/strong&gt;; Terraform only needs a &lt;strong&gt;stable id&lt;/strong&gt; (&lt;code&gt;locals.sandbox_ou_id&lt;/code&gt;), not another OU resource.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a &lt;strong&gt;different&lt;/strong&gt; OU might be a better fit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolation:&lt;/strong&gt; You want a &lt;strong&gt;dedicated OU&lt;/strong&gt; for &lt;strong&gt;platform&lt;/strong&gt; or &lt;strong&gt;CI-owned&lt;/strong&gt; sandboxes so &lt;strong&gt;Service Catalog / Account Factory&lt;/strong&gt; sandboxes stay separate from &lt;strong&gt;Terraform-vended&lt;/strong&gt; ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Different SCPs:&lt;/strong&gt; The Control Tower sandbox OU carries &lt;strong&gt;service control policies&lt;/strong&gt; you do not want on these accounts; a &lt;strong&gt;separate OU&lt;/strong&gt; can carry &lt;strong&gt;different&lt;/strong&gt; SCPs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational clarity:&lt;/strong&gt; You prefer &lt;strong&gt;one automation path per OU&lt;/strong&gt; so it is obvious &lt;strong&gt;how&lt;/strong&gt; an account was created.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither choice is inherently wrong. The &lt;strong&gt;existing sandbox OU&lt;/strong&gt; is a &lt;strong&gt;reasonable default&lt;/strong&gt; for &lt;strong&gt;consistency&lt;/strong&gt; and &lt;strong&gt;shared&lt;/strong&gt; sandbox policy; another OU is equally valid when &lt;strong&gt;separation&lt;/strong&gt; or &lt;strong&gt;policy&lt;/strong&gt; needs differ. Document the choice in your &lt;strong&gt;runbook&lt;/strong&gt; so future readers know &lt;strong&gt;why&lt;/strong&gt; the id is what it is.&lt;/p&gt;

&lt;h4&gt;
  
  
  4.4 Development organizational unit
&lt;/h4&gt;

&lt;p&gt;Create an OU under the organization &lt;strong&gt;root&lt;/strong&gt; (first element of &lt;code&gt;roots&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_organizations_organizational_unit"&lt;/span&gt; &lt;span class="s2"&gt;"development"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Development"&lt;/span&gt;
  &lt;span class="nx"&gt;parent_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_organizations_organization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roots&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Development"&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.5 Member accounts
&lt;/h4&gt;

&lt;p&gt;Each member account needs a &lt;strong&gt;unique root email&lt;/strong&gt;, the standard &lt;strong&gt;&lt;code&gt;OrganizationAccountAccessRole&lt;/code&gt;&lt;/strong&gt; name (so administrators can assume into the account from the org), a &lt;strong&gt;parent OU&lt;/strong&gt;, and &lt;strong&gt;tags&lt;/strong&gt;. Ignore &lt;strong&gt;iam user access to billing&lt;/strong&gt; if your org policy manages that outside Terraform.&lt;/p&gt;

&lt;p&gt;Below, &lt;strong&gt;&lt;code&gt;sandbox_1&lt;/code&gt;&lt;/strong&gt; uses &lt;strong&gt;&lt;code&gt;local.sandbox_ou_id&lt;/code&gt;&lt;/strong&gt;; &lt;strong&gt;&lt;code&gt;dev_1&lt;/code&gt;&lt;/strong&gt; uses the &lt;strong&gt;Development&lt;/strong&gt; OU resource id. The lifecycle block here is the &lt;strong&gt;minimal&lt;/strong&gt; pattern for teaching; a production repo might add other &lt;code&gt;lifecycle&lt;/code&gt; rules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_organizations_account"&lt;/span&gt; &lt;span class="s2"&gt;"sandbox_1"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"example-sandbox-1"&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sandbox_account_email&lt;/span&gt;
  &lt;span class="nx"&gt;role_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"OrganizationAccountAccessRole"&lt;/span&gt;
  &lt;span class="nx"&gt;parent_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sandbox_ou_id&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"example-sandbox-1"&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sandbox"&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ignore_changes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;iam_user_access_to_billing&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_organizations_account"&lt;/span&gt; &lt;span class="s2"&gt;"dev_1"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"example-dev-1"&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev_account_email&lt;/span&gt;
  &lt;span class="nx"&gt;role_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"OrganizationAccountAccessRole"&lt;/span&gt;
  &lt;span class="nx"&gt;parent_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_organizations_organizational_unit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;development&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"example-dev-1"&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ignore_changes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;iam_user_access_to_billing&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.6 Remote state backend
&lt;/h4&gt;

&lt;p&gt;Point the backend at your bucket, key, and region. &lt;strong&gt;&lt;code&gt;use_lockfile&lt;/code&gt;&lt;/strong&gt; enables S3-native locking in supported Terraform versions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"your-tf-state-bucket"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"account-factory/terraform.tfstate"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ap-southeast-2"&lt;/span&gt;
    &lt;span class="nx"&gt;encrypt&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;use_lockfile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.7 Variables and tfvars
&lt;/h4&gt;

&lt;p&gt;At minimum, pass the two &lt;strong&gt;root emails&lt;/strong&gt;. Optionally override &lt;strong&gt;region&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS region for the provider."&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ap-southeast-2"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"sandbox_account_email"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Root email address for the sandbox AWS account."&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"dev_account_email"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Root email address for the development AWS account."&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example &lt;strong&gt;&lt;code&gt;terraform.tfvars&lt;/code&gt;&lt;/strong&gt; (keep out of version control if it is sensitive):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;sandbox_account_email&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"you+sandbox@example.com"&lt;/span&gt;
&lt;span class="nx"&gt;dev_account_email&lt;/span&gt;     &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"you+dev@example.com"&lt;/span&gt;
&lt;span class="c1"&gt;# region = "ap-southeast-2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4.8 Outputs
&lt;/h4&gt;

&lt;p&gt;Expose &lt;strong&gt;organization&lt;/strong&gt; metadata, &lt;strong&gt;OU&lt;/strong&gt; ids, and &lt;strong&gt;account&lt;/strong&gt; ids and ARNs for downstream stacks or runbooks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"organization_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_organizations_organization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"development_ou_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_organizations_organizational_unit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;development&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"sandbox_account_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_organizations_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sandbox_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"dev_account_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_organizations_account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5. Summary: Copy-paste
&lt;/h3&gt;

&lt;p&gt;From the module root, with credentials for the &lt;strong&gt;management account&lt;/strong&gt; (for example &lt;strong&gt;&lt;code&gt;AWS_PROFILE&lt;/code&gt;&lt;/strong&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YourManagementProfile
terraform init
terraform plan
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep &lt;strong&gt;root emails&lt;/strong&gt; stable; changing an account’s root email later is a manual process. Review &lt;strong&gt;&lt;code&gt;terraform plan&lt;/code&gt;&lt;/strong&gt; output before &lt;strong&gt;&lt;code&gt;apply&lt;/code&gt;&lt;/strong&gt;, and use your team’s normal change control for production organizations.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Troubleshooting
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;EmailAlreadyExists&lt;/code&gt; / account creation fails:&lt;/strong&gt; The &lt;strong&gt;root email&lt;/strong&gt; is already tied to another AWS account. Pick a &lt;strong&gt;new&lt;/strong&gt; unique address (plus-addressing helps).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AccessDenied&lt;/code&gt; on Organizations:&lt;/strong&gt; The caller is not in the &lt;strong&gt;management account&lt;/strong&gt; or the principal lacks &lt;strong&gt;Organizations&lt;/strong&gt; permissions for the operations in your plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 backend errors:&lt;/strong&gt; Bucket name, &lt;strong&gt;key&lt;/strong&gt;, &lt;strong&gt;region&lt;/strong&gt;, or &lt;strong&gt;KMS&lt;/strong&gt; policy mismatch; the principal needs &lt;strong&gt;list/get/put&lt;/strong&gt; on the state &lt;strong&gt;prefix&lt;/strong&gt; and lock object if used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong sandbox OU:&lt;/strong&gt; The &lt;strong&gt;sandbox&lt;/strong&gt; account lands in the wrong OU when &lt;strong&gt;&lt;code&gt;local.sandbox_ou_id&lt;/code&gt;&lt;/strong&gt; is incorrect; re-read it from &lt;strong&gt;Organizations&lt;/strong&gt; or your landing zone console.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  7. References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_introduction.html" rel="noopener noreferrer"&gt;AWS Organizations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/organizations_account" rel="noopener noreferrer"&gt;Terraform aws_organizations_account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/organizations_organizational_unit" rel="noopener noreferrer"&gt;Terraform aws_organizations_organizational_unit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/language/settings/backends/s3" rel="noopener noreferrer"&gt;Terraform S3 backend&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>organizations</category>
      <category>iac</category>
    </item>
    <item>
      <title>GitHub Webhook Secrets and the Terraform Public Registry</title>
      <dc:creator>John  Ajera</dc:creator>
      <pubDate>Tue, 14 Apr 2026 20:15:29 +0000</pubDate>
      <link>https://dev.to/jajera/github-webhook-secrets-and-the-terraform-public-registry-5ef1</link>
      <guid>https://dev.to/jajera/github-webhook-secrets-and-the-terraform-public-registry-5ef1</guid>
      <description>&lt;h2&gt;
  
  
  GitHub Webhook Secrets and the Terraform Public Registry
&lt;/h2&gt;

&lt;p&gt;New &lt;strong&gt;semver tags&lt;/strong&gt; in GitHub can fail to appear on &lt;a href="https://registry.terraform.io/" rel="noopener noreferrer"&gt;registry.terraform.io&lt;/a&gt; when the &lt;strong&gt;Terraform Registry&lt;/strong&gt; webhook’s &lt;strong&gt;Secret&lt;/strong&gt; in GitHub no longer matches what the registry uses to verify deliveries. GitHub signs each delivery with that secret; the registry must validate the signature with the same value. This article states that model, what breaks when the two sides diverge, and how to recover using HashiCorp’s documented steps.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Overview
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model:&lt;/strong&gt; GitHub computes an HMAC signature for webhook payloads (see &lt;a href="https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries" rel="noopener noreferrer"&gt;Validating webhook deliveries&lt;/a&gt;). The registry endpoint must verify it using the &lt;strong&gt;same&lt;/strong&gt; secret tied to your module’s integration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure mode:&lt;/strong&gt; Updating &lt;strong&gt;Secret&lt;/strong&gt; only under &lt;strong&gt;GitHub → Settings → Webhooks&lt;/strong&gt; changes what GitHub signs with. The registry does not read that field from your repo; it keeps the secret from the integration that created or last reconciled the hook. Signatures then fail and new versions do not publish.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovery:&lt;/strong&gt; Keep a &lt;strong&gt;single&lt;/strong&gt; webhook to &lt;strong&gt;&lt;code&gt;https://registry.terraform.io/hooks/github&lt;/code&gt;&lt;/strong&gt;, run &lt;strong&gt;Resync Module&lt;/strong&gt; from the registry UI, then confirm deliveries: push a &lt;strong&gt;new&lt;/strong&gt; semver tag &lt;strong&gt;or&lt;/strong&gt; use GitHub’s &lt;strong&gt;Redeliver&lt;/strong&gt; on a &lt;strong&gt;failed&lt;/strong&gt; delivery for the tag push you still want published (same payload, signed again with the &lt;strong&gt;current&lt;/strong&gt; secret). If publishing still fails, use &lt;a href="https://developer.hashicorp.com/terraform/registry#getting-help" rel="noopener noreferrer"&gt;registry support&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide is for the &lt;strong&gt;public&lt;/strong&gt; Terraform Registry and &lt;strong&gt;public&lt;/strong&gt; GitHub modules. &lt;strong&gt;Terraform Enterprise&lt;/strong&gt; and other private registries use different hosts and procedures.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Module already published on the &lt;a href="https://registry.terraform.io/" rel="noopener noreferrer"&gt;Terraform Registry&lt;/a&gt; from a &lt;strong&gt;public&lt;/strong&gt; GitHub repository&lt;/li&gt;
&lt;li&gt;Permission to use &lt;strong&gt;Manage Module&lt;/strong&gt; on the module page and to edit &lt;strong&gt;Webhooks&lt;/strong&gt; on the GitHub repository (&lt;strong&gt;Settings → Webhooks&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;semver&lt;/strong&gt; tag to test with (push a &lt;strong&gt;new&lt;/strong&gt; one, or reuse an existing tag whose registry publish failed and still needs to succeed), per &lt;a href="https://developer.hashicorp.com/terraform/registry/modules/publish" rel="noopener noreferrer"&gt;module publishing&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. What not to do
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Do not&lt;/strong&gt; paste a new random value into the webhook &lt;strong&gt;Secret&lt;/strong&gt; in GitHub alone, expecting the public registry to pick it up. There is no documented self-serve screen on the registry where you paste the same value to pair it. Unilateral edits in GitHub desynchronize signing and verification.&lt;/p&gt;

&lt;p&gt;If you already did that, treat the next sections as recovery, not prevention.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Recovery: one webhook, then Resync
&lt;/h3&gt;

&lt;p&gt;Per the &lt;a href="https://developer.hashicorp.com/terraform/registry/faq" rel="noopener noreferrer"&gt;Terraform registry FAQ&lt;/a&gt;, &lt;strong&gt;Resync&lt;/strong&gt; for a module makes the registry &lt;strong&gt;ensure the repository has a webhook&lt;/strong&gt; for automatic publishing and reconciles version gaps. The FAQ also recommends fixing webhook problems before relying on resync alone.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;strong&gt;Settings → Webhooks&lt;/strong&gt; on the GitHub repository.&lt;/li&gt;
&lt;li&gt;Leave &lt;strong&gt;exactly one&lt;/strong&gt; active webhook whose payload URL is &lt;strong&gt;&lt;code&gt;https://registry.terraform.io/hooks/github&lt;/code&gt;&lt;/strong&gt;. If several point there, &lt;strong&gt;delete the extras&lt;/strong&gt; (the FAQ calls out duplicate hooks as a cause of publishing issues).&lt;/li&gt;
&lt;li&gt;On &lt;a href="https://registry.terraform.io/" rel="noopener noreferrer"&gt;registry.terraform.io&lt;/a&gt;, open the module → &lt;strong&gt;Manage Module&lt;/strong&gt; → &lt;strong&gt;Resync Module&lt;/strong&gt;. Wait for completion; avoid starting overlapping resyncs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm the hook:&lt;/strong&gt; Open the webhook → &lt;strong&gt;Recent Deliveries&lt;/strong&gt; on GitHub.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Push a new semver tag&lt;/strong&gt; and check that the new delivery succeeds (typically &lt;strong&gt;HTTP 200&lt;/strong&gt;), then confirm the version on the module page, &lt;strong&gt;or&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redeliver&lt;/strong&gt; a &lt;strong&gt;failed&lt;/strong&gt; delivery whose payload is still the &lt;strong&gt;tag push&lt;/strong&gt; for the semver version you want. GitHub sends the &lt;strong&gt;same&lt;/strong&gt; event body again; signatures use the webhook &lt;strong&gt;Secret&lt;/strong&gt; as it is &lt;strong&gt;after&lt;/strong&gt; your fixes, so this can publish that version without another tag. Use the delivery row that matches the correct tag ref.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Redeliver instead of a new tag
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Yes — that is an acceptable approach&lt;/strong&gt; when the semver tag &lt;strong&gt;already exists&lt;/strong&gt; and the only problem was the webhook (for example signature failure after a bad secret edit). &lt;strong&gt;Redeliver&lt;/strong&gt; is a normal GitHub feature for retrying a delivery; you are replaying the same &lt;strong&gt;push&lt;/strong&gt; event the registry would have seen on a successful first attempt, not inventing a new release artifact in Git.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prefer a new tag&lt;/strong&gt; when you need a &lt;strong&gt;new&lt;/strong&gt; module version anyway, when there is &lt;strong&gt;no&lt;/strong&gt; failed delivery to replay (nothing reached GitHub’s hook list), or when you want a straight-line test from “tag push” to “delivery” without hunting history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prefer redeliver&lt;/strong&gt; when bumping the version number would be &lt;strong&gt;artificial&lt;/strong&gt; (the tag is already the release you mean) and you only need the registry to &lt;strong&gt;accept the same event&lt;/strong&gt; again after fixing the hook.&lt;/p&gt;

&lt;p&gt;If a single hook and resync do not restore publishing, &lt;a href="https://developer.hashicorp.com/terraform/registry#getting-help" rel="noopener noreferrer"&gt;contact registry support&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Configuration checks
&lt;/h3&gt;

&lt;p&gt;Use these when deliveries succeed but versions still look wrong, or as routine verification:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Payload URL:&lt;/strong&gt; &lt;strong&gt;&lt;code&gt;https://registry.terraform.io/hooks/github&lt;/code&gt;&lt;/strong&gt; (HTTPS, that host, that path).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tags:&lt;/strong&gt; &lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;Semantic version&lt;/a&gt; identifiers only, optional leading &lt;strong&gt;&lt;code&gt;v&lt;/code&gt;&lt;/strong&gt;. Other ref names do not create registry versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Events:&lt;/strong&gt; The hook must receive events for the activity that publishes versions (for example &lt;strong&gt;push&lt;/strong&gt; events that include tag pushes). Overly narrow event filters can skip the tag you care about.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  6. Summary: Copy-Paste
&lt;/h3&gt;

&lt;p&gt;Push a new version tag when you prefer a fresh event (adjust remote and tag):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git tag v1.0.1
git push origin v1.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open repository webhooks (replace &lt;code&gt;OWNER&lt;/code&gt; and &lt;code&gt;REPO&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/OWNER/REPO/settings/hooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After opening the Terraform Registry webhook there, use &lt;strong&gt;Recent Deliveries&lt;/strong&gt; → a failed row → &lt;strong&gt;Redeliver&lt;/strong&gt; when you have already fixed the hook and want to retry the &lt;strong&gt;same&lt;/strong&gt; tag event without pushing another tag.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Troubleshooting
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;th&gt;What to try&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No new registry version after a tag push&lt;/td&gt;
&lt;td&gt;Missing or failing webhook, duplicate hooks, or signature failure after a secret-only GitHub edit&lt;/td&gt;
&lt;td&gt;One hook to &lt;code&gt;registry.terraform.io/hooks/github&lt;/code&gt;, &lt;strong&gt;Resync Module&lt;/strong&gt;, then push a &lt;strong&gt;new&lt;/strong&gt; semver tag &lt;strong&gt;or&lt;/strong&gt; &lt;strong&gt;Redeliver&lt;/strong&gt; the failed delivery for that tag; re-check &lt;strong&gt;Recent Deliveries&lt;/strong&gt; and the module page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Recent Deliveries&lt;/strong&gt; show errors after changing &lt;strong&gt;Secret&lt;/strong&gt; in GitHub&lt;/td&gt;
&lt;td&gt;GitHub and registry no longer share the same secret&lt;/td&gt;
&lt;td&gt;Dedupe hooks, &lt;strong&gt;Resync Module&lt;/strong&gt;; if still failing, &lt;strong&gt;registry support&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delivery succeeds but no version&lt;/td&gt;
&lt;td&gt;Invalid tag shape or delay&lt;/td&gt;
&lt;td&gt;Fix tag to semver; wait; &lt;strong&gt;Resync&lt;/strong&gt; if needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple registry webhooks&lt;/td&gt;
&lt;td&gt;Stale or conflicting registrations&lt;/td&gt;
&lt;td&gt;Delete duplicates, &lt;strong&gt;Resync&lt;/strong&gt; per &lt;a href="https://developer.hashicorp.com/terraform/registry/faq" rel="noopener noreferrer"&gt;FAQ&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  8. References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.hashicorp.com/terraform/registry/modules/publish" rel="noopener noreferrer"&gt;Publish modules&lt;/a&gt; — naming, tags, &lt;strong&gt;Resync Module&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.hashicorp.com/terraform/registry/faq" rel="noopener noreferrer"&gt;Terraform registry FAQ&lt;/a&gt; — &lt;strong&gt;Resync&lt;/strong&gt;, duplicate webhooks, versions not appearing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries" rel="noopener noreferrer"&gt;Validating webhook deliveries&lt;/a&gt; — GitHub signatures and the webhook secret&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/rest/repos/webhooks?apiVersion=2022-11-28#redeliver-a-delivery-for-a-repository-webhook" rel="noopener noreferrer"&gt;Redeliver a delivery for a repository webhook&lt;/a&gt; — API equivalent of the &lt;strong&gt;Redeliver&lt;/strong&gt; control in the delivery UI&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>github</category>
      <category>webhooks</category>
      <category>registry</category>
    </item>
    <item>
      <title>AWS IAM Identity Center: Custom Access Portal URL</title>
      <dc:creator>John  Ajera</dc:creator>
      <pubDate>Tue, 14 Apr 2026 12:11:29 +0000</pubDate>
      <link>https://dev.to/jajera/aws-iam-identity-center-custom-access-portal-url-963</link>
      <guid>https://dev.to/jajera/aws-iam-identity-center-custom-access-portal-url-963</guid>
      <description>&lt;h2&gt;
  
  
  AWS IAM Identity Center: Custom Access Portal URL
&lt;/h2&gt;

&lt;p&gt;After you enable &lt;strong&gt;IAM Identity Center&lt;/strong&gt;, the default &lt;strong&gt;AWS access portal&lt;/strong&gt; URL uses an opaque subdomain under &lt;code&gt;awsapps.com&lt;/code&gt;. You can replace that prefix once with a &lt;strong&gt;custom access portal URL&lt;/strong&gt; so sign-in links are easier to recognize and communicate. This guide walks through what that means, how to set it in the console, and what to verify afterward.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Overview
&lt;/h3&gt;

&lt;p&gt;This article covers how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand the difference between the &lt;strong&gt;default&lt;/strong&gt; and &lt;strong&gt;custom&lt;/strong&gt; access portal URL (&lt;code&gt;https://…awsapps.com/start&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Customize the subdomain &lt;strong&gt;once&lt;/strong&gt; from the IAM Identity Center console (no separate AWS charge for this step)&lt;/li&gt;
&lt;li&gt;Confirm sign-in still works and refresh &lt;strong&gt;bookmarks, runbooks, and onboarding docs&lt;/strong&gt; that referenced the old URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; cover bringing your own DNS name (for example &lt;code&gt;sso.example.com&lt;/code&gt;); the portal stays on &lt;strong&gt;&lt;code&gt;*.awsapps.com&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Management account&lt;/strong&gt; (or delegated admin where your org’s Identity Center instance lives) with permission to change Identity Center settings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM Identity Center enabled&lt;/strong&gt; for the organization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Console access&lt;/strong&gt; in the &lt;strong&gt;Identity Center Region&lt;/strong&gt; (the region shown as primary for your instance; for example &lt;code&gt;ap-southeast-2&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;stable subdomain label&lt;/strong&gt; you are willing to keep: AWS documents that &lt;strong&gt;you cannot edit the access portal URL after you customize it&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. What changes when you customize the URL
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Default vs custom
&lt;/h4&gt;

&lt;p&gt;By default, the portal looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://xxxxxxxxxx.awsapps.com/start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After customization it becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://your-subdomain.awsapps.com/start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only the &lt;strong&gt;first label&lt;/strong&gt; (the part before &lt;code&gt;.awsapps.com&lt;/code&gt;) changes. The path &lt;code&gt;/start&lt;/code&gt; and the fact that traffic uses &lt;strong&gt;HTTPS to &lt;code&gt;awsapps.com&lt;/code&gt;&lt;/strong&gt; stay the same.&lt;/p&gt;

&lt;h4&gt;
  
  
  One-time operation
&lt;/h4&gt;

&lt;p&gt;AWS states clearly: &lt;strong&gt;if you change the AWS access portal URL, you cannot edit it later.&lt;/strong&gt; If the &lt;strong&gt;Customize&lt;/strong&gt; control does not appear under the portal URL in the dashboard, the URL has already been customized. Treat the choice like a &lt;strong&gt;permanent hostname&lt;/strong&gt; for your organization.&lt;/p&gt;

&lt;h4&gt;
  
  
  Not the same as “Instance name”
&lt;/h4&gt;

&lt;p&gt;The &lt;strong&gt;Instance name&lt;/strong&gt; in &lt;strong&gt;Settings summary&lt;/strong&gt; is a console-friendly label. The &lt;strong&gt;access portal URL&lt;/strong&gt; is what users type or bookmark. You can set both; they serve different purposes.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Customize the access portal URL (console)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://console.aws.amazon.com/singlesignon/" rel="noopener noreferrer"&gt;IAM Identity Center console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;Region&lt;/strong&gt; where your Identity Center instance is registered if the console prompts you (must match your instance’s primary region).&lt;/li&gt;
&lt;li&gt;In the navigation pane, open &lt;strong&gt;Dashboard&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Settings summary&lt;/strong&gt;, find the &lt;strong&gt;AWS access portal URL&lt;/strong&gt; and choose &lt;strong&gt;Customize&lt;/strong&gt; (only shown if customization is still available).&lt;/li&gt;
&lt;li&gt;Enter your desired &lt;strong&gt;subdomain&lt;/strong&gt; and save.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When the operation completes, use the new URL to open the access portal and confirm the sign-in page loads.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. After you save
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tell your users&lt;/strong&gt; the new portal URL and ask them to &lt;strong&gt;update bookmarks&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update internal documentation&lt;/strong&gt;, wiki pages, and &lt;strong&gt;new-hire instructions&lt;/strong&gt; that still point at the old hostname.&lt;/li&gt;
&lt;li&gt;If you use &lt;strong&gt;CLI or IDE profiles&lt;/strong&gt; that reference the portal URL (for example AWS CLI &lt;code&gt;aws configure sso&lt;/code&gt; / &lt;code&gt;sso_start_url&lt;/code&gt;), align those configs with the &lt;strong&gt;new&lt;/strong&gt; URL on each machine.&lt;/li&gt;
&lt;li&gt;If anything in your &lt;strong&gt;IdP or application configuration&lt;/strong&gt; hard-coded the old portal URL, plan a coordinated update (uncommon for the bare portal hostname, but easy to miss in custom integrations).&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  6. Summary: Copy-Paste
&lt;/h3&gt;

&lt;p&gt;Customizing the subdomain is a &lt;strong&gt;console workflow&lt;/strong&gt;; there is no documented AWS CLI parameter to set the access portal hostname after the fact. You can still &lt;strong&gt;list&lt;/strong&gt; your instance from the CLI (replace the region with your Identity Center region):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws sso-admin list-instances &lt;span class="nt"&gt;--region&lt;/span&gt; ap-southeast-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example sign-in URL pattern after customization (replace &lt;code&gt;your-subdomain&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://your-subdomain.awsapps.com/start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  7. Troubleshooting
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;What to try&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Customize&lt;/strong&gt; does not appear&lt;/td&gt;
&lt;td&gt;The portal URL may already be customized. AWS does not offer a console option to change it again.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Console shows the wrong account or empty Identity Center&lt;/td&gt;
&lt;td&gt;Use the &lt;strong&gt;organization management account&lt;/strong&gt; (or the account where the instance was created) and the correct &lt;strong&gt;region&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Users see errors after the change&lt;/td&gt;
&lt;td&gt;Confirm they use the &lt;strong&gt;new&lt;/strong&gt; &lt;code&gt;https://…awsapps.com/start&lt;/code&gt; URL, clear old bookmarks, and refresh SSO/CLI &lt;code&gt;sso_start_url&lt;/code&gt; values.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  8. References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/howtochangeURL.html" rel="noopener noreferrer"&gt;Customizing the AWS access portal URL&lt;/a&gt; (IAM Identity Center User Guide)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/using-the-portal.html" rel="noopener noreferrer"&gt;Setting up and using the AWS access portal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/iam/identity-center/pricing/" rel="noopener noreferrer"&gt;IAM Identity Center pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>iam</category>
      <category>identitycenter</category>
      <category>security</category>
    </item>
    <item>
      <title>Configuring AWS Business Support+</title>
      <dc:creator>John  Ajera</dc:creator>
      <pubDate>Mon, 13 Apr 2026 23:21:19 +0000</pubDate>
      <link>https://dev.to/jajera/configuring-aws-business-support-3kdb</link>
      <guid>https://dev.to/jajera/configuring-aws-business-support-3kdb</guid>
      <description>&lt;h2&gt;
  
  
  Configuring AWS Business Support+
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/premiumsupport/plans/business-plus/" rel="noopener noreferrer"&gt;AWS Business Support+&lt;/a&gt; is AWS’s paid support tier that combines &lt;strong&gt;24/7 expert access&lt;/strong&gt;, &lt;strong&gt;AI-assisted troubleshooting&lt;/strong&gt;, &lt;strong&gt;Trusted Advisor&lt;/strong&gt; and health-oriented guidance, and &lt;strong&gt;targeted initial response&lt;/strong&gt; commitments for business-critical issues (see &lt;a href="https://aws.amazon.com/premiumsupport/plans/business-plus/" rel="noopener noreferrer"&gt;official plan page&lt;/a&gt; for current feature wording and pricing). This guide focuses on &lt;strong&gt;how to turn it on and who can do it&lt;/strong&gt;: console enrollment, &lt;strong&gt;organization-wide versus single-account&lt;/strong&gt; subscription, &lt;strong&gt;IAM&lt;/strong&gt; for the Support Plans console, and &lt;strong&gt;Organizations SCP&lt;/strong&gt; pitfalls when regions are restricted.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Overview
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Open the &lt;strong&gt;&lt;a href="https://console.aws.amazon.com/support/plans/home" rel="noopener noreferrer"&gt;AWS Support Plans console&lt;/a&gt;&lt;/strong&gt; and upgrade from &lt;strong&gt;Basic&lt;/strong&gt; to &lt;strong&gt;Business Support+&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;organization-wide&lt;/strong&gt; (management account, Organizations all features) or &lt;strong&gt;account-only&lt;/strong&gt; enrollment&lt;/li&gt;
&lt;li&gt;Grant &lt;strong&gt;IAM&lt;/strong&gt; access to change plans (&lt;code&gt;supportplans:*&lt;/code&gt; or AWS managed policies) and fix &lt;strong&gt;SCP&lt;/strong&gt; denies if Support Plans fails in member accounts&lt;/li&gt;
&lt;li&gt;Know how to &lt;strong&gt;downgrade&lt;/strong&gt; (console path) and where to &lt;strong&gt;compare pricing&lt;/strong&gt; before you confirm&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Root user&lt;/strong&gt; or an IAM principal allowed to change support plans (see &lt;a href="https://docs.aws.amazon.com/awssupport/latest/user/security-support-plans.html" rel="noopener noreferrer"&gt;Manage access to AWS Support Plans&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;organization-level&lt;/strong&gt; Business Support+: the account must be the &lt;strong&gt;Organizations management account&lt;/strong&gt;, with &lt;strong&gt;&lt;a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_org_support-all-features.html" rel="noopener noreferrer"&gt;all features enabled&lt;/a&gt;&lt;/strong&gt; for the organization&lt;/li&gt;
&lt;li&gt;Billing in good standing; paid support has a &lt;strong&gt;minimum one-month&lt;/strong&gt; commitment (&lt;a href="https://aws.amazon.com/premiumsupport/faqs/" rel="noopener noreferrer"&gt;Support FAQs&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Subscribe from the Support Plans console
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Open the console
&lt;/h4&gt;

&lt;p&gt;Sign in and go to &lt;a href="https://console.aws.amazon.com/support/plans/home" rel="noopener noreferrer"&gt;AWS Support Plans&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Optional but recommended before you subscribe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Choose &lt;strong&gt;Compare all Support plans and features&lt;/strong&gt; to line up Business Support+ against other tiers&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Pricing calculator&lt;/strong&gt; (in the console) to estimate support charges from expected monthly AWS usage&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Upgrade from Basic to Business Support+
&lt;/h4&gt;

&lt;p&gt;Steps follow &lt;a href="https://docs.aws.amazon.com/awssupport/latest/user/changing-support-plans.html" rel="noopener noreferrer"&gt;Changing AWS Support plans&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;strong&gt;AWS Business Support+&lt;/strong&gt; section, choose &lt;strong&gt;Get started&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Choose scope:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;My organization&lt;/strong&gt; — only if you use &lt;strong&gt;AWS Organizations&lt;/strong&gt; with &lt;strong&gt;all-feature mode&lt;/strong&gt;; only the &lt;strong&gt;management account&lt;/strong&gt; can enroll the whole org. New accounts created in the org are &lt;strong&gt;automatically&lt;/strong&gt; on Business Support+. In the console, &lt;strong&gt;My organization&lt;/strong&gt; is &lt;strong&gt;disabled&lt;/strong&gt; when it does not apply (for example, you are signed in to a &lt;strong&gt;member account&lt;/strong&gt;, Organizations is not in use, or &lt;strong&gt;all features&lt;/strong&gt; are not enabled for the organization)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My account&lt;/strong&gt; — subscribe &lt;strong&gt;this account&lt;/strong&gt; only&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Review plan details and pricing (&lt;a href="https://aws.amazon.com/premiumsupport/pricing/" rel="noopener noreferrer"&gt;AWS Support pricing&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Accept the subscription terms (checkbox)&lt;/li&gt;
&lt;li&gt;Choose &lt;strong&gt;Confirm upgrade&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;After enrollment:&lt;/strong&gt; confirm the tier in &lt;strong&gt;&lt;a href="https://console.aws.amazon.com/support/home" rel="noopener noreferrer"&gt;AWS Support Center&lt;/a&gt;&lt;/strong&gt; (the console shows your current plan) and verify you can open a &lt;strong&gt;support case&lt;/strong&gt; with the severity options you expect.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. IAM: who may view or change the plan
&lt;/h3&gt;

&lt;p&gt;Users need &lt;code&gt;supportplans&lt;/code&gt; API permissions. Common actions (&lt;a href="https://docs.aws.amazon.com/awssupport/latest/user/security-support-plans.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;supportplans:GetSupportPlan&lt;/code&gt; — view current plan&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;supportplans:StartSupportPlanUpdate&lt;/code&gt; — start an upgrade or downgrade request&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;supportplans:GetSupportPlanUpdateStatus&lt;/code&gt; — poll update status&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS managed policies (names may evolve; confirm in IAM):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AWSSupportPlansFullAccess&lt;/code&gt;&lt;/strong&gt; — change plans&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AWSSupportPlansReadOnlyAccess&lt;/code&gt;&lt;/strong&gt; — view only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example &lt;strong&gt;read-only&lt;/strong&gt; custom policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"supportplans:Get*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"supportplans:List*"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example &lt;strong&gt;full&lt;/strong&gt; access (delegate carefully):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"supportplans:*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5. Organizations: SCPs and global services
&lt;/h3&gt;

&lt;p&gt;If member accounts see errors &lt;strong&gt;even with correct IAM&lt;/strong&gt;, an &lt;strong&gt;SCP&lt;/strong&gt; that denies by &lt;strong&gt;Region&lt;/strong&gt; can block &lt;strong&gt;Support Plans&lt;/strong&gt; because it is a &lt;strong&gt;global&lt;/strong&gt; console/API.&lt;/p&gt;

&lt;p&gt;Per &lt;a href="https://docs.aws.amazon.com/awssupport/latest/user/security-support-plans.html" rel="noopener noreferrer"&gt;Manage access to AWS Support Plans&lt;/a&gt;, add &lt;strong&gt;&lt;code&gt;supportplans:*&lt;/code&gt;&lt;/strong&gt; to the &lt;strong&gt;&lt;code&gt;NotAction&lt;/code&gt;&lt;/strong&gt; list on any broad Region-deny SCP (exact structure depends on your org’s policy layout). If you use &lt;strong&gt;AWS Control Tower&lt;/strong&gt; Region deny controls, read AWS guidance on &lt;strong&gt;drift&lt;/strong&gt; before hand-editing SCPs, because repairs can revert custom exceptions.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Downgrades and higher tiers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Downgrade from Business Support+:&lt;/strong&gt; on &lt;a href="https://console.aws.amazon.com/support/plans/home" rel="noopener noreferrer"&gt;Manage Support Plans&lt;/a&gt;, use &lt;strong&gt;Review downgrade&lt;/strong&gt; in the &lt;strong&gt;Basic Support&lt;/strong&gt; section (&lt;a href="https://docs.aws.amazon.com/awssupport/latest/user/changing-support-plans.html" rel="noopener noreferrer"&gt;Changing AWS Support plans&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise Support or Unified Operations:&lt;/strong&gt; use &lt;strong&gt;Contact sales&lt;/strong&gt; in the console; Enterprise downgrades go through your &lt;strong&gt;TAM&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Account leaves the org&lt;/strong&gt; after org-level Business Support+: AWS documents that the account &lt;strong&gt;drops to Basic&lt;/strong&gt; support&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  7. Optional follow-on configuration
&lt;/h3&gt;

&lt;p&gt;These are not strictly “subscription” steps but are how teams &lt;strong&gt;use&lt;/strong&gt; paid support day to day:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://docs.aws.amazon.com/awssupport/latest/user/aws-support-app-for-slack.html" rel="noopener noreferrer"&gt;AWS Support App in Slack&lt;/a&gt;&lt;/strong&gt; — create and update cases from Slack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://docs.aws.amazon.com/awssupport/latest/user/about-support-api.html" rel="noopener noreferrer"&gt;AWS Support API&lt;/a&gt;&lt;/strong&gt; — automate case operations (separate IAM/service permissions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusted Advisor&lt;/strong&gt; and &lt;strong&gt;AWS Health&lt;/strong&gt; — visible in console once the plan includes them; use &lt;a href="https://aws.amazon.com/premiumsupport/technology/trusted-advisor/" rel="noopener noreferrer"&gt;Trusted Advisor&lt;/a&gt; and &lt;a href="https://aws.amazon.com/premiumsupport/technology/aws-health/" rel="noopener noreferrer"&gt;AWS Health&lt;/a&gt; docs for check types and alerts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Promotional &lt;strong&gt;trial&lt;/strong&gt; or &lt;strong&gt;pricing&lt;/strong&gt; offers change over time; use the &lt;a href="https://aws.amazon.com/premiumsupport/plans/business-plus/" rel="noopener noreferrer"&gt;Business Support+ plan page&lt;/a&gt; and in-console &lt;strong&gt;offer&lt;/strong&gt; links for current eligibility and refund rules.&lt;/p&gt;




&lt;h3&gt;
  
  
  8. Summary: Copy-Paste
&lt;/h3&gt;

&lt;p&gt;Console entry points (sign in first):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://console.aws.amazon.com/support/plans/home
https://console.aws.amazon.com/support/home
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IAM: attach &lt;strong&gt;&lt;code&gt;AWSSupportPlansFullAccess&lt;/code&gt;&lt;/strong&gt; to a named role used only for billing/support changes, or merge the JSON examples in section 4 into your least-privilege model.&lt;/p&gt;

&lt;p&gt;Organizations: if Region-deny SCPs exist, ensure &lt;strong&gt;&lt;code&gt;supportplans:*&lt;/code&gt;&lt;/strong&gt; is excluded from the deny per AWS documentation.&lt;/p&gt;




&lt;h3&gt;
  
  
  9. Troubleshooting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Access Denied on Support Plans:&lt;/strong&gt; Attach &lt;strong&gt;&lt;code&gt;AWSSupportPlansFullAccess&lt;/code&gt;&lt;/strong&gt; (or equivalent &lt;code&gt;supportplans:*&lt;/code&gt;) to the role or user; confirm you are not using a &lt;strong&gt;member account&lt;/strong&gt; when only the &lt;strong&gt;management account&lt;/strong&gt; may enroll the org.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Member account cannot open the page:&lt;/strong&gt; Check &lt;strong&gt;SCP&lt;/strong&gt; Region denies and add &lt;strong&gt;&lt;code&gt;supportplans:*&lt;/code&gt;&lt;/strong&gt; to &lt;strong&gt;&lt;code&gt;NotAction&lt;/code&gt;&lt;/strong&gt; as documented; verify &lt;strong&gt;Control Tower&lt;/strong&gt; drift if that platform manages SCPs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong scope after the fact:&lt;/strong&gt; Org-wide subscription is a &lt;strong&gt;management-account&lt;/strong&gt; decision; single-account subscription does not extend to siblings. Adjust scope only by following AWS &lt;strong&gt;change plan&lt;/strong&gt; / &lt;strong&gt;downgrade&lt;/strong&gt; flows and org enrollment rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Charges surprise you:&lt;/strong&gt; Revisit the console &lt;strong&gt;Pricing calculator&lt;/strong&gt; and &lt;a href="https://aws.amazon.com/premiumsupport/pricing/" rel="noopener noreferrer"&gt;Support pricing&lt;/a&gt;; support fees are &lt;strong&gt;separate&lt;/strong&gt; from service usage on the bill.&lt;/p&gt;




&lt;h3&gt;
  
  
  10. References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/awssupport/latest/user/changing-support-plans.html" rel="noopener noreferrer"&gt;Change AWS Support plans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/awssupport/latest/user/aws-support-plans.html" rel="noopener noreferrer"&gt;AWS Support plans (User Guide)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/awssupport/latest/user/security-support-plans.html" rel="noopener noreferrer"&gt;Manage access to AWS Support Plans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/awssupport/latest/user/managed-policies-aws-support-plans.html" rel="noopener noreferrer"&gt;AWS managed policies for AWS Support Plans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_org_support-all-features.html" rel="noopener noreferrer"&gt;Enabling all features for an organization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/premiumsupport/plans/business-plus/" rel="noopener noreferrer"&gt;AWS Business Support+&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/premiumsupport/faqs/" rel="noopener noreferrer"&gt;AWS Support FAQs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>support</category>
      <category>iam</category>
      <category>organizations</category>
    </item>
    <item>
      <title>Greenfield EKS: Choosing Standard EKS vs EKS Auto Mode Without Legacy Baggage</title>
      <dc:creator>John  Ajera</dc:creator>
      <pubDate>Tue, 07 Apr 2026 23:35:59 +0000</pubDate>
      <link>https://dev.to/jajera/greenfield-eks-choosing-standard-eks-vs-eks-auto-mode-without-legacy-baggage-39f3</link>
      <guid>https://dev.to/jajera/greenfield-eks-choosing-standard-eks-vs-eks-auto-mode-without-legacy-baggage-39f3</guid>
      <description>&lt;h2&gt;
  
  
  Greenfield EKS: Choosing Standard EKS vs EKS Auto Mode Without Legacy Baggage
&lt;/h2&gt;

&lt;p&gt;If you are standing up &lt;strong&gt;your first&lt;/strong&gt; &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html" rel="noopener noreferrer"&gt;Amazon EKS&lt;/a&gt; cluster and you are &lt;strong&gt;not&lt;/strong&gt; dragging along years of Terraform modules, custom AMIs, or a mandated node strategy from another team, the choice between &lt;strong&gt;Standard EKS&lt;/strong&gt; (you own how nodes are provisioned and wired) and &lt;strong&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/automode.html" rel="noopener noreferrer"&gt;EKS Auto Mode&lt;/a&gt;&lt;/strong&gt; (AWS runs more of that for you) is mostly about &lt;strong&gt;defaults&lt;/strong&gt;: speed and delegated operations versus transparency and fine-grained control. This article is a practical decision guide for that &lt;strong&gt;greenfield&lt;/strong&gt; scenario—what differs, how to think about &lt;strong&gt;cost&lt;/strong&gt; and &lt;strong&gt;maintenance&lt;/strong&gt;, and how to separate &lt;strong&gt;the EKS operating model&lt;/strong&gt; from &lt;strong&gt;optional add-ons&lt;/strong&gt; you install on top.&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%2F5k4027o7x1ulwq0tee4s.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%2F5k4027o7x1ulwq0tee4s.png" alt="Diagram of Amazon EKS: Kubernetes control plane managed by AWS and worker nodes running in your VPC" width="800" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source: &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html" rel="noopener noreferrer"&gt;What is Amazon EKS?&lt;/a&gt; (AWS Documentation).&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Overview
&lt;/h3&gt;

&lt;p&gt;This guide helps you decide, for a &lt;strong&gt;new&lt;/strong&gt; cluster with &lt;strong&gt;no legacy constraints&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What &lt;strong&gt;Standard EKS&lt;/strong&gt; and &lt;strong&gt;EKS Auto Mode&lt;/strong&gt; each optimize for in the first weeks and months&lt;/li&gt;
&lt;li&gt;How to compare &lt;strong&gt;cost&lt;/strong&gt; at a high level (control plane, compute, Auto Mode management fees, and people time)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who runs what&lt;/strong&gt; (nodes, scaling, ingress/LB), including an &lt;strong&gt;AWS-documented checklist&lt;/strong&gt; of what Auto Mode manages as built-in infrastructure&lt;/li&gt;
&lt;li&gt;A short &lt;strong&gt;rubric&lt;/strong&gt; for “default to Auto Mode” versus “start explicit with managed node groups”&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Familiarity with &lt;strong&gt;containers&lt;/strong&gt; and the idea of a &lt;strong&gt;Kubernetes control plane&lt;/strong&gt; versus &lt;strong&gt;worker nodes&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Access to current &lt;strong&gt;&lt;a href="https://aws.amazon.com/eks/pricing" rel="noopener noreferrer"&gt;Amazon EKS pricing&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/automode.html" rel="noopener noreferrer"&gt;EKS Auto Mode documentation&lt;/a&gt;&lt;/strong&gt; for numbers and feature details that change over time&lt;/li&gt;
&lt;li&gt;No assumption that you already run &lt;strong&gt;Karpenter&lt;/strong&gt;, &lt;strong&gt;managed node groups&lt;/strong&gt;, or a corporate standard—this article assumes &lt;strong&gt;greenfield&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Name the two starting paths
&lt;/h3&gt;

&lt;p&gt;Same managed control plane; &lt;strong&gt;who owns the node story&lt;/strong&gt; is what splits the paths.&lt;/p&gt;

&lt;h4&gt;
  
  
  Side-by-side
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Standard EKS&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;EKS Auto Mode&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;You choose&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How nodes are created and scaled: &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html" rel="noopener noreferrer"&gt;managed node groups&lt;/a&gt;, self-managed nodes, or later &lt;a href="https://karpenter.sh/" rel="noopener noreferrer"&gt;Karpenter&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Less DIY wiring; AWS runs more of &lt;strong&gt;lifecycle + integration&lt;/strong&gt; (&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/automode.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mental model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;“These are &lt;strong&gt;my&lt;/strong&gt; instances / ASGs, &lt;strong&gt;my&lt;/strong&gt; scaling and upgrade path”&lt;/td&gt;
&lt;td&gt;“&lt;strong&gt;AWS-shaped&lt;/strong&gt; automation; fewer knobs, less assembly”&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;APIs you will see&lt;/strong&gt; (examples)&lt;/td&gt;
&lt;td&gt;EC2, MNG, launch templates, plus whatever provisioner you pick&lt;/td&gt;
&lt;td&gt;Platform-oriented CRDs such as &lt;code&gt;NodePool&lt;/code&gt; / &lt;code&gt;NodeClaim&lt;/code&gt;, &lt;code&gt;NodeClass&lt;/code&gt;, &lt;code&gt;CNINode&lt;/code&gt;, LB-related types—&lt;strong&gt;exact set&lt;/strong&gt; varies by version&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Standard EKS — responsibility flow
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------------------+
| AWS: Kubernetes API           |  (managed control plane)
+-------------------------------+
              |
              v
+-------------------------------+
| You: pick node strategy       |
| MNG / self-managed / Karpenter|
+-------------------------------+
              |
              v
+-------------------------------+
| Workloads / Pods              |
+-------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  EKS Auto Mode — responsibility flow
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------------------+
| AWS: Kubernetes API           |  (managed control plane)
+-------------------------------+
              |
              v
+-------------------------------+
| AWS: node lifecycle +         |
| integrations (opinionated)    |
+-------------------------------+
              |
              v
+-------------------------------+
| Workloads / Pods              |
+-------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Same for both:&lt;/strong&gt; observability, backups, secrets, RBAC, network policy, ingress, and mesh are &lt;strong&gt;still yours&lt;/strong&gt;. Picking a mode ≠ production-ready.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  4. Cost at a glance
&lt;/h3&gt;

&lt;p&gt;Pricing moves; always verify against the &lt;a href="https://aws.amazon.com/eks/pricing/" rel="noopener noreferrer"&gt;Amazon EKS pricing page&lt;/a&gt;. The figures below use &lt;strong&gt;US West (Oregon)&lt;/strong&gt; On-Demand rates at the time of writing and a &lt;strong&gt;small greenfield&lt;/strong&gt; scenario: &lt;strong&gt;one cluster, three worker nodes&lt;/strong&gt; (&lt;code&gt;m5a.xlarge&lt;/code&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  Estimated monthly AWS bill
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Line item&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;th&gt;Standard EKS&lt;/th&gt;
&lt;th&gt;Auto Mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Control plane&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0.10 / cluster / hr&lt;/td&gt;
&lt;td&gt;~$73&lt;/td&gt;
&lt;td&gt;~$73&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;EC2 instances&lt;/strong&gt; (3 x &lt;code&gt;m5a.xlarge&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;$0.172 / instance / hr&lt;/td&gt;
&lt;td&gt;~$377&lt;/td&gt;
&lt;td&gt;~$377&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auto Mode management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0.02064 / instance / hr&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;~$45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS invoice total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$450&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$495&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Source for *&lt;/em&gt;$0.02064 / hr** (Auto Mode) and &lt;strong&gt;$0.172 / hr&lt;/strong&gt; (EC2) for &lt;code&gt;m5a.xlarge&lt;/code&gt;: AWS &lt;strong&gt;Example 4: EKS Auto Mode&lt;/strong&gt; on &lt;a href="https://aws.amazon.com/eks/pricing/" rel="noopener noreferrer"&gt;Amazon EKS pricing&lt;/a&gt; (US West Oregon, On-Demand). Auto Mode fees are &lt;strong&gt;per instance type&lt;/strong&gt;; use that page or the &lt;a href="https://calculator.aws/#/createCalculator/EKS" rel="noopener noreferrer"&gt;AWS Pricing Calculator for EKS&lt;/a&gt; for other shapes.*&lt;/p&gt;

&lt;p&gt;Auto Mode adds roughly &lt;strong&gt;10-12%&lt;/strong&gt; to the AWS bill in this scenario. The management fee is billed per-second (one-minute minimum) and applies regardless of EC2 purchase option (On-Demand, Reserved, Savings Plan, or Spot).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Watch out for extended support.&lt;/strong&gt; If you let a Kubernetes version drift past &lt;strong&gt;standard support&lt;/strong&gt; (14 months), the cluster fee jumps to &lt;strong&gt;$0.60 / hr (~$438 / month)&lt;/strong&gt;. Plan upgrades regardless of mode.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  What the invoice does not show
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hidden cost&lt;/th&gt;
&lt;th&gt;Standard EKS&lt;/th&gt;
&lt;th&gt;Auto Mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Engineer time&lt;/strong&gt; building node automation, LB controller Helm charts, upgrade runbooks&lt;/td&gt;
&lt;td&gt;Higher -- you build and maintain the glue&lt;/td&gt;
&lt;td&gt;Lower -- AWS ships more of that glue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Incident cost&lt;/strong&gt; when nodes misbehave, AMIs drift, or scaling stalls&lt;/td&gt;
&lt;td&gt;Yours to debug end-to-end&lt;/td&gt;
&lt;td&gt;Shared with AWS; fewer levers but also fewer moving parts you wrote&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Idle capacity&lt;/strong&gt; from over-provisioned groups or generous defaults&lt;/td&gt;
&lt;td&gt;Risk if you set scaling bounds loosely&lt;/td&gt;
&lt;td&gt;Risk if Auto Mode defaults are generous for your workload&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; the ~$45 / month management fee in this example is roughly &lt;strong&gt;one hour of a platform engineer's loaded cost&lt;/strong&gt;. If Auto Mode saves more than that in reduced toil per month, it pays for itself. If your team already has solid automation and rarely touches nodes, Standard keeps the invoice leaner.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  5. Who runs what—and what the first year feels like
&lt;/h3&gt;

&lt;p&gt;The choice shows up in &lt;strong&gt;division of labor&lt;/strong&gt;: who keeps &lt;strong&gt;nodes&lt;/strong&gt;, &lt;strong&gt;scaling&lt;/strong&gt;, and &lt;strong&gt;traffic into the cluster&lt;/strong&gt; healthy. That is what fills your calendar in months &lt;strong&gt;six through twelve&lt;/strong&gt;—upgrade planning and Helm charts on one side, AWS platform changes and support cases on the other—not an abstract “mode” badge.&lt;/p&gt;

&lt;h4&gt;
  
  
  Where the work lands
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Standard EKS (typical greenfield)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;EKS Auto Mode&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nodes and scaling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You pick the mechanism—&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html" rel="noopener noreferrer"&gt;managed node groups&lt;/a&gt;, self-managed nodes, or &lt;a href="https://karpenter.sh/" rel="noopener noreferrer"&gt;Karpenter&lt;/a&gt;—and you own &lt;strong&gt;upgrades&lt;/strong&gt;, &lt;strong&gt;behavior&lt;/strong&gt;, and &lt;strong&gt;capacity tuning&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;AWS delivers a more &lt;strong&gt;integrated&lt;/strong&gt; node and scaling experience; you align with &lt;strong&gt;Kubernetes objects and practices AWS documents for Auto Mode&lt;/strong&gt; instead of assembling the same stack yourself (&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/automode.html" rel="noopener noreferrer"&gt;Auto Mode&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ingress and load balancers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Teams usually &lt;strong&gt;install and operate&lt;/strong&gt; something like &lt;strong&gt;AWS Load Balancer Controller&lt;/strong&gt;—chart upgrades, compatibility with new cluster versions, incidents when labels or annotations drift&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;More of that integration is AWS-operated&lt;/strong&gt; for Auto Mode, so you typically spend &lt;strong&gt;less&lt;/strong&gt; time babysitting that slice—still read &lt;strong&gt;AWS release notes&lt;/strong&gt; when the platform changes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Standard EKS in practice
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clarity and skill-building:&lt;/strong&gt; Obvious ownership (“we chose MNG / Karpenter / …”), strong &lt;strong&gt;learning&lt;/strong&gt; for engineers who will live in AWS and Kubernetes, and a concrete story when &lt;strong&gt;auditors&lt;/strong&gt; ask what creates instances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recurring toil:&lt;/strong&gt; &lt;strong&gt;Upgrade choreography&lt;/strong&gt; across the control plane version, &lt;strong&gt;node AMIs&lt;/strong&gt;, and &lt;strong&gt;add-on compatibility&lt;/strong&gt;; deliberate choices for &lt;strong&gt;scaling bounds&lt;/strong&gt; and instance families; ongoing ownership of &lt;strong&gt;ingress/LB&lt;/strong&gt; tooling unless you outsource it elsewhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  EKS Auto Mode in practice
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Less assembly, faster baseline:&lt;/strong&gt; Shorter path to a &lt;strong&gt;working data plane&lt;/strong&gt;, fewer Terraform or CloudFormation resources tied to &lt;strong&gt;node plumbing&lt;/strong&gt;, and less day-to-day ownership of the &lt;strong&gt;LB integration&lt;/strong&gt; layer described above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tradeoff:&lt;/strong&gt; &lt;strong&gt;Fewer levers&lt;/strong&gt; when behavior surprises you; success depends on &lt;strong&gt;solid observability&lt;/strong&gt; (logs, metrics, tracing) and comfort escalating or adapting when &lt;strong&gt;AWS-owned&lt;/strong&gt; behavior changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  What Auto Mode treats as managed infrastructure (AWS checklist)
&lt;/h4&gt;

&lt;p&gt;AWS describes these as &lt;strong&gt;built-in cluster capabilities&lt;/strong&gt;, not as separate EKS console add-ons you install and version yourself. This is the high-level list from &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/automode.html" rel="noopener noreferrer"&gt;Automate cluster infrastructure with EKS Auto Mode&lt;/a&gt; and the &lt;strong&gt;Automated components&lt;/strong&gt; section there—use the docs for the latest detail and limits.&lt;/p&gt;

&lt;h5&gt;
  
  
  Compute and nodes
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Data-plane &lt;strong&gt;EC2 instances&lt;/strong&gt; (Bottlerocket-based variants): AMI selection, locked-down OS (SELinux enforcing, read-only root), &lt;strong&gt;no direct SSH or SSM&lt;/strong&gt; to nodes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security and lifecycle:&lt;/strong&gt; patching and upgrades with minimal disruption; &lt;strong&gt;maximum node lifetime&lt;/strong&gt; (default up to &lt;strong&gt;21 days&lt;/strong&gt;) so nodes are replaced regularly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accelerated workloads:&lt;/strong&gt; GPU-related kernel drivers and plugins (for example &lt;strong&gt;NVIDIA&lt;/strong&gt; and &lt;strong&gt;AWS Neuron&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot:&lt;/strong&gt; handling of &lt;strong&gt;Spot interruption notices&lt;/strong&gt; and EC2 instance health signals&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Scaling
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Karpenter&lt;/strong&gt;-style autoscaling: reacts to unschedulable Pods, adds capacity, and removes or consolidates nodes when demand drops&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Load balancing
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Elastic Load Balancing&lt;/strong&gt; tied to Kubernetes &lt;strong&gt;Service&lt;/strong&gt; and &lt;strong&gt;Ingress&lt;/strong&gt; objects: provisions and manages &lt;strong&gt;ALB&lt;/strong&gt; and &lt;strong&gt;NLB&lt;/strong&gt; resources and scales them with cluster demand&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Networking
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pod and Service networking&lt;/strong&gt;, including &lt;strong&gt;IPv4/IPv6&lt;/strong&gt; and use of &lt;strong&gt;secondary CIDRs&lt;/strong&gt; where needed for Pod IP space&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network policy&lt;/strong&gt; enforcement for Pods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluster DNS&lt;/strong&gt; (local DNS for the cluster)&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Storage
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EBS CSI&lt;/strong&gt;-class block storage as a managed capability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ephemeral storage&lt;/strong&gt; defaults on nodes (volume type, size, encryption, and cleanup behavior on termination—per AWS documentation)&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Identity
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EKS Pod Identity:&lt;/strong&gt; you &lt;strong&gt;do not&lt;/strong&gt; install the &lt;strong&gt;EKS Pod Identity Agent&lt;/strong&gt; yourself on Auto Mode clusters (AWS documents that it is not required in this model)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Neither mode delivers “production in a box.”&lt;/strong&gt; Metrics, secret rotation, mesh, policy engines, backups, and business-specific operators remain &lt;strong&gt;your&lt;/strong&gt; software lifecycle unless you adopt separate managed services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid the “busy cluster” trap.&lt;/strong&gt; A Standard environment can &lt;em&gt;look&lt;/em&gt; heavier because it has &lt;strong&gt;more add-ons&lt;/strong&gt; (monitoring, GitOps, security tools). That says what &lt;strong&gt;you installed&lt;/strong&gt;, not that &lt;strong&gt;Standard&lt;/strong&gt; is inherently more capable than &lt;strong&gt;Auto Mode&lt;/strong&gt;. Judge the choice on &lt;strong&gt;who operates nodes and built-in integrations&lt;/strong&gt;, not on how crowded the UI feels.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Decision rubric (greenfield, no baggage)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Lean toward EKS Auto Mode&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The team is &lt;strong&gt;small&lt;/strong&gt; and the priority is &lt;strong&gt;shipping&lt;/strong&gt; a standard container platform quickly&lt;/li&gt;
&lt;li&gt;Workloads are &lt;strong&gt;typical&lt;/strong&gt; Linux containers without exotic kernel, sysctl, or custom AMI requirements &lt;strong&gt;today&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;You are comfortable adopting &lt;strong&gt;AWS-shaped&lt;/strong&gt; APIs for nodes and integrations for the next phase of growth&lt;/li&gt;
&lt;li&gt;You prefer &lt;strong&gt;fewer&lt;/strong&gt; moving parts &lt;strong&gt;you&lt;/strong&gt; must patch and tune in the first year&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Lean toward Standard EKS (often with managed node groups first)&lt;/strong&gt; when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You want &lt;strong&gt;maximum transparency&lt;/strong&gt; into every layer for &lt;strong&gt;training&lt;/strong&gt;, &lt;strong&gt;compliance groundwork&lt;/strong&gt;, or &lt;strong&gt;multi-cloud&lt;/strong&gt; discipline&lt;/li&gt;
&lt;li&gt;You already know you need &lt;strong&gt;specific&lt;/strong&gt; instance families, purchase options, or &lt;strong&gt;strict&lt;/strong&gt; cost caps encoded &lt;strong&gt;explicitly&lt;/strong&gt; from day one&lt;/li&gt;
&lt;li&gt;You expect to &lt;strong&gt;standardize&lt;/strong&gt; on an in-house or community &lt;strong&gt;node provisioning&lt;/strong&gt; story (for example Karpenter you fully control) and want to &lt;strong&gt;learn&lt;/strong&gt; that model without an additional management layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Default suggestion for a true greenfield product team:&lt;/strong&gt; if your main risk is &lt;strong&gt;slow delivery&lt;/strong&gt; and &lt;strong&gt;operational overload&lt;/strong&gt;, &lt;strong&gt;EKS Auto Mode&lt;/strong&gt; is often the better &lt;strong&gt;starting&lt;/strong&gt; default; if your main risk is &lt;strong&gt;understanding and owning&lt;/strong&gt; every dependency for regulatory or career reasons, &lt;strong&gt;Standard EKS&lt;/strong&gt; with &lt;strong&gt;managed node groups&lt;/strong&gt; is a clean teaching path. You can revisit the choice after the team has real production traffic and metrics.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Troubleshooting: common misconceptions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Auto Mode is more Kubernetes.”&lt;/strong&gt; It is &lt;strong&gt;more AWS-managed automation&lt;/strong&gt; exposed through Kubernetes APIs, not a superset of every optional addon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Standard is cheaper.”&lt;/strong&gt; &lt;strong&gt;Invoice lines&lt;/strong&gt; can be lower; &lt;strong&gt;total cost&lt;/strong&gt; may not be once you count &lt;strong&gt;engineering time&lt;/strong&gt; and &lt;strong&gt;incidents&lt;/strong&gt; for a small team.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“We picked a mode, so we are secure and observable.”&lt;/strong&gt; &lt;strong&gt;RBAC&lt;/strong&gt;, &lt;strong&gt;network policy&lt;/strong&gt;, &lt;strong&gt;secrets&lt;/strong&gt;, &lt;strong&gt;auditing&lt;/strong&gt;, and &lt;strong&gt;monitoring&lt;/strong&gt; are still &lt;strong&gt;your&lt;/strong&gt; design choices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;“Our Standard cluster has more moving parts, so it must be better.”&lt;/strong&gt; Often that is &lt;strong&gt;more optional software you added&lt;/strong&gt;—not proof that Standard beats Auto Mode; see &lt;strong&gt;section 5&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  8. References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html" rel="noopener noreferrer"&gt;What is Amazon EKS?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/eks/pricing/" rel="noopener noreferrer"&gt;Amazon EKS pricing&lt;/a&gt; (see &lt;strong&gt;Example 4: EKS Auto Mode&lt;/strong&gt; for per-instance-type Auto Mode fees used in section 4)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/automode.html" rel="noopener noreferrer"&gt;EKS Auto Mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/auto-reference.html" rel="noopener noreferrer"&gt;Learn how EKS Auto Mode works&lt;/a&gt; (deeper component topics)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/eks/resources/best-practices/" rel="noopener noreferrer"&gt;Amazon EKS best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html" rel="noopener noreferrer"&gt;Managed node groups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://karpenter.sh/" rel="noopener noreferrer"&gt;Karpenter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>eks</category>
      <category>aws</category>
      <category>kubernetes</category>
      <category>automode</category>
    </item>
    <item>
      <title>Argo CD and AWS CodeConnections: The Upside, the Redeploy Pain, and How I Fixed It</title>
      <dc:creator>John  Ajera</dc:creator>
      <pubDate>Sat, 28 Mar 2026 10:46:16 +0000</pubDate>
      <link>https://dev.to/jajera/argo-cd-and-aws-codeconnections-the-upside-the-redeploy-pain-and-how-i-fixed-it-2k1m</link>
      <guid>https://dev.to/jajera/argo-cd-and-aws-codeconnections-the-upside-the-redeploy-pain-and-how-i-fixed-it-2k1m</guid>
      <description>&lt;h2&gt;
  
  
  Argo CD and AWS CodeConnections: The Upside, the Redeploy Pain, and How I Fixed It
&lt;/h2&gt;

&lt;p&gt;I run &lt;a href="https://argo-cd.readthedocs.io/" rel="noopener noreferrer"&gt;Argo CD&lt;/a&gt; on &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html" rel="noopener noreferrer"&gt;Amazon EKS&lt;/a&gt; using the managed &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/argo-cd.html" rel="noopener noreferrer"&gt;Argo CD capability&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/codeconnections/latest/userguide/welcome.html" rel="noopener noreferrer"&gt;AWS CodeConnections&lt;/a&gt; for Git. CodeConnections has been a clear win for day-to-day operations. Then I had to &lt;strong&gt;recreate&lt;/strong&gt; the connection (new resource, new identity in the URL). Every Application went to &lt;strong&gt;Sync: Unknown&lt;/strong&gt; until I updated URLs in &lt;strong&gt;two&lt;/strong&gt; places—Git &lt;strong&gt;and&lt;/strong&gt; the live cluster—and fixed &lt;strong&gt;ApplicationSets&lt;/strong&gt; so they stopped writing the old URL back. This article leads with &lt;strong&gt;why I still choose CodeConnections&lt;/strong&gt;, then &lt;strong&gt;what breaks on redeploy&lt;/strong&gt;, then &lt;strong&gt;what I did&lt;/strong&gt; when it inevitably happened, in that order.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Why CodeConnections is worth it for Argo CD on EKS
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;No SSH keys or personal tokens in the cluster.&lt;/strong&gt; Argo pulls Git using IAM: the capability role is allowed to use the connection (&lt;code&gt;UseConnection&lt;/code&gt;, &lt;code&gt;GetConnection&lt;/code&gt;). You are not copying PATs into Secrets or rotating leaked keys because someone printed &lt;code&gt;kubectl get secret&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One connection, many repos.&lt;/strong&gt; The HTTPS URL includes your account, region, a &lt;strong&gt;connection UUID&lt;/strong&gt;, and then &lt;code&gt;owner/repo&lt;/code&gt;. Same connection, different path segment for each repository. Setup details and Terraform patterns are in &lt;a href="https://dev.to/jajera/argo-cd-on-eks-git-repo-access-with-aws-codeconnections-and-terraform-3ejl"&gt;Argo CD on EKS: Git repo access with AWS CodeConnections and Terraform&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fits how enterprises already govern access.&lt;/strong&gt; Connections are AWS resources; approval and auditing live next to the rest of your cloud controls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That does not mean zero tradeoffs.&lt;/strong&gt; Managed Argo CD often applies &lt;strong&gt;one&lt;/strong&gt; Git credential broadly (for example every &lt;code&gt;github.com&lt;/code&gt; fetch through Kustomize can inherit it). If that bites you, vendoring or URL strategy fixes it—see &lt;a href="https://dev.to/jajera/why-your-kustomize-remote-bases-break-on-managed-argo-cd-and-how-to-fix-it-26i6"&gt;Why Your Kustomize Remote Bases Break on Managed Argo CD (and How to Fix It)&lt;/a&gt;. The tradeoff this article focuses on is different: &lt;strong&gt;replacing&lt;/strong&gt; the connection.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. What actually hurts when you update or redeploy the connection
&lt;/h3&gt;

&lt;p&gt;The Git URL Argo uses is not abstract. It embeds a &lt;strong&gt;connection UUID&lt;/strong&gt; in the path. &lt;strong&gt;A new connection is a new UUID.&lt;/strong&gt; Anything that still points at the old path keeps asking AWS to authorize the wrong resource, so repo fetch fails and sync never reconciles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pain point one — Git only is not enough.&lt;/strong&gt; Your GitOps repo is the source of truth, but Kubernetes already has &lt;code&gt;Application&lt;/code&gt; and &lt;code&gt;ApplicationSet&lt;/code&gt; objects applied &lt;strong&gt;yesterday&lt;/strong&gt;. Their &lt;code&gt;spec.source.repoURL&lt;/code&gt; (and generator URLs on sets) stay on the old string until something updates them. Pushing Git does not retroactively patch those CRs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pain point two — ApplicationSets fight you.&lt;/strong&gt; Many sets declare &lt;code&gt;repoURL&lt;/code&gt; twice: on the &lt;strong&gt;git&lt;/strong&gt; generator and again on the &lt;strong&gt;template&lt;/strong&gt;. If you patch child Applications but leave the set on the old URL, the controller reconciles and &lt;strong&gt;puts the old URL back&lt;/strong&gt; on the apps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pain point three — Terraform layout.&lt;/strong&gt; If CodeConnections and the EKS cluster share one tangled module and state, &lt;strong&gt;recreating&lt;/strong&gt; the connection can feel like you are planning half the platform when you only wanted a new Git pipe. I now prefer the connection (and its IAM attachment) in something &lt;strong&gt;standalone&lt;/strong&gt;, with outputs the cluster stack consumes—so the next rotation is a smaller blast radius.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this is not.&lt;/strong&gt; If Argo shows &lt;strong&gt;forbidden&lt;/strong&gt; listing some API group (for example heavy CRD surfaces from controllers like ACK), that is usually &lt;strong&gt;cluster RBAC / EKS access policies&lt;/strong&gt; for the Argo identity, not the CodeConnections URL. Fix that on its own; do not confuse it with a UUID swap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not mass-delete Applications&lt;/strong&gt; to “fix” a bad URL. Workloads may still be fine; you risk prune tearing down real resources. Fix the URLs.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kubectl&lt;/code&gt; against the cluster, with permission to read and patch &lt;code&gt;applications&lt;/code&gt; and &lt;code&gt;applicationsets&lt;/code&gt; in the Argo CD namespace (below I use &lt;code&gt;argocd&lt;/code&gt;, the usual default)&lt;/li&gt;
&lt;li&gt;Rights to &lt;strong&gt;commit and push&lt;/strong&gt; every Git repo that hardcodes the CodeConnections URL&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;new&lt;/strong&gt; clone URL or at least the new UUID from the AWS Console or Terraform output&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. If it happens to you: what worked for me
&lt;/h3&gt;

&lt;p&gt;These steps assume you already know the &lt;strong&gt;old&lt;/strong&gt; and &lt;strong&gt;new&lt;/strong&gt; connection UUID (search your shell history, Terraform state, or an old Application YAML). The host pattern is documented in the CodeConnections and EKS guides linked at the end.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step A — Fix Git first, everywhere
&lt;/h4&gt;

&lt;p&gt;Search across &lt;strong&gt;all&lt;/strong&gt; repos that participate in GitOps—not only the “main” infra repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rg &lt;span class="s1"&gt;'codeconnections\.'&lt;/span&gt; &lt;span class="nt"&gt;--glob&lt;/span&gt; &lt;span class="s1"&gt;'*.yaml'&lt;/span&gt; &lt;span class="nt"&gt;--glob&lt;/span&gt; &lt;span class="s1"&gt;'*.yml'&lt;/span&gt; &lt;span class="nt"&gt;--glob&lt;/span&gt; &lt;span class="s1"&gt;'*.tf'&lt;/span&gt; &lt;span class="nt"&gt;--glob&lt;/span&gt; &lt;span class="s1"&gt;'*.md'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update bootstrap &lt;code&gt;Application&lt;/code&gt; manifests, every &lt;code&gt;ApplicationSet&lt;/code&gt; &lt;strong&gt;generator&lt;/strong&gt; and &lt;strong&gt;template&lt;/strong&gt; &lt;code&gt;repoURL&lt;/code&gt;, any child &lt;code&gt;Application&lt;/code&gt; checked in with a literal URL, and docs or scripts that build repository secrets. Commit and push.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step B — Patch Applications in the cluster
&lt;/h4&gt;

&lt;p&gt;Still in a broken state, the cluster cannot always sync from Git, so you patch live objects once. Set shell variables to your real values (namespace if not &lt;code&gt;argocd&lt;/code&gt;, old UUID, new UUID):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;argocd
&lt;span class="nv"&gt;OLD_UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
&lt;span class="nv"&gt;NEW_UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ffffffff-eeee-dddd-cccc-bbbbbbbbbbbb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;List apps whose single &lt;code&gt;spec.source.repoURL&lt;/code&gt; still contains the old UUID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get applications &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; json | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;--arg&lt;/span&gt; u &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OLD_UUID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'.items[] | select(.spec.source.repoURL // "" | contains($u)) | .metadata.name'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each name, the safest approach is to take the existing URL from the object and swap the UUID in the shell, then merge-patch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;my-app
&lt;span class="nv"&gt;oldurl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get application &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.spec.source.repoURL}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;newurl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;oldurl&lt;/span&gt;&lt;span class="p"&gt;//&lt;/span&gt;&lt;span class="nv"&gt;$OLD_UUID&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$NEW_UUID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
kubectl patch application &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt; merge &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;spec&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;source&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;repoURL&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$newurl&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use &lt;strong&gt;&lt;code&gt;spec.sources&lt;/code&gt;&lt;/strong&gt; (multi-source), repeat the idea per entry that points at CodeConnections.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step C — Patch ApplicationSets (do not skip this)
&lt;/h4&gt;

&lt;p&gt;Confirm the old UUID still appears on &lt;strong&gt;spec&lt;/strong&gt; (ignore &lt;strong&gt;status&lt;/strong&gt; for a moment):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get applicationsets &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | rg &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OLD_UUID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To replace the UUID everywhere under each set’s JSON (review before you run this in production—I exported one set first and eyeballed it):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;name &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;kubectl get applicationsets &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; json | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;--arg&lt;/span&gt; u &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OLD_UUID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'.items[] | select(.. | strings? | contains($u)) | .metadata.name'&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;kubectl get applicationset &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; json | &lt;span class="se"&gt;\&lt;/span&gt;
    jq &lt;span class="nt"&gt;--arg&lt;/span&gt; o &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OLD_UUID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--arg&lt;/span&gt; n &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NEW_UUID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s1"&gt;'del(.status) | walk(if type == "string" then gsub($o; $n) else . end)'&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
    kubectl replace &lt;span class="nt"&gt;-f&lt;/span&gt; -
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;walk&lt;/code&gt; replaces &lt;strong&gt;every&lt;/strong&gt; occurrence of the old UUID string in that JSON. Pick a UUID substring unique to the connection so you do not hit unrelated fields.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step D — Refresh and converge
&lt;/h4&gt;

&lt;p&gt;Hard refresh apps in the UI or CLI, then let GitOps catch up. If Git still cannot be fetched until &lt;strong&gt;one&lt;/strong&gt; app is fixed, patch your &lt;strong&gt;bootstrap&lt;/strong&gt; Application (the one that applies the rest of the tree) to the new URL first, then repeat the rest—classic chicken-and-egg.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Troubleshooting
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Application stuck deleting
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl patch application my-app &lt;span class="nt"&gt;-n&lt;/span&gt; argocd &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s1"&gt;'{"metadata":{"finalizers":null}}'&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;merge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  I deleted an app and it came back
&lt;/h4&gt;

&lt;p&gt;An &lt;strong&gt;ApplicationSet&lt;/strong&gt; owns it (&lt;code&gt;ownerReferences&lt;/code&gt;). Fix the set and Git; deleting the app alone will not stick.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/codeconnections/latest/userguide/welcome.html" rel="noopener noreferrer"&gt;AWS CodeConnections User Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/argo-cd.html" rel="noopener noreferrer"&gt;Amazon EKS: Argo CD capability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/application-specification/" rel="noopener noreferrer"&gt;Argo CD – Application spec&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/applicationset/" rel="noopener noreferrer"&gt;Argo CD – ApplicationSet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/jajera/argo-cd-on-eks-git-repo-access-with-aws-codeconnections-and-terraform-3ejl"&gt;Argo CD on EKS: Git repo access with AWS CodeConnections and Terraform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/jajera/why-your-kustomize-remote-bases-break-on-managed-argo-cd-and-how-to-fix-it-26i6"&gt;Why Your Kustomize Remote Bases Break on Managed Argo CD (and How to Fix It)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>argocd</category>
      <category>codeconnections</category>
      <category>eks</category>
      <category>gitops</category>
    </item>
    <item>
      <title>Host a Static Site on EC2 with Terraform (VPC, Optional ALB)</title>
      <dc:creator>John  Ajera</dc:creator>
      <pubDate>Sat, 28 Mar 2026 08:54:20 +0000</pubDate>
      <link>https://dev.to/jajera/host-a-static-site-on-ec2-with-terraform-vpc-optional-alb-1ba9</link>
      <guid>https://dev.to/jajera/host-a-static-site-on-ec2-with-terraform-vpc-optional-alb-1ba9</guid>
      <description>&lt;h2&gt;
  
  
  Host a Static Site on EC2 with Terraform (VPC, Optional ALB, Session Manager)
&lt;/h2&gt;

&lt;p&gt;For static sites, &lt;strong&gt;S3 + CloudFront&lt;/strong&gt; is usually the better default. This post points at a small Terraform demo and pulls a few excerpts from &lt;strong&gt;&lt;code&gt;main.tf&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;variables.tf&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;iam.tf&lt;/code&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;code&gt;user_data.tftpl&lt;/code&gt;&lt;/strong&gt;. Full layout: &lt;a href="https://github.com/jdevto/tf-aws-ec2-static-demo" rel="noopener noreferrer"&gt;tf-aws-ec2-static-demo&lt;/a&gt; (local path &lt;code&gt;~/workspace/jdevto/tf-aws-ec2-static-demo&lt;/code&gt; if you keep it beside this blog repo). &lt;strong&gt;S3 + CloudFront&lt;/strong&gt; with Terraform: &lt;a href="https://github.com/jdevto/blog/blob/main/articles/art0018.md" rel="noopener noreferrer"&gt;art0018&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;The demo provisions a VPC, &lt;strong&gt;nginx&lt;/strong&gt; on &lt;strong&gt;Amazon Linux 2023&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;index.html&lt;/code&gt;&lt;/strong&gt; (AZ + private IP from &lt;strong&gt;IMDSv2&lt;/strong&gt;), and &lt;strong&gt;&lt;code&gt;robots.txt&lt;/code&gt;&lt;/strong&gt;. &lt;strong&gt;&lt;code&gt;use_alb=false&lt;/code&gt; (default):&lt;/strong&gt; one instance in one &lt;strong&gt;public&lt;/strong&gt; subnet; clients hit &lt;strong&gt;:80&lt;/strong&gt; on the instance public IP (CIDR from &lt;strong&gt;&lt;code&gt;allowed_http_cidr&lt;/code&gt;&lt;/strong&gt;). &lt;strong&gt;&lt;code&gt;use_alb=true&lt;/code&gt;:&lt;/strong&gt; &lt;strong&gt;internet ALB&lt;/strong&gt; across &lt;strong&gt;&lt;code&gt;az_count&lt;/code&gt; ≥ 2&lt;/strong&gt; public subnets; &lt;strong&gt;&lt;code&gt;az_count&lt;/code&gt;&lt;/strong&gt; instances in &lt;strong&gt;private&lt;/strong&gt; subnets (one per AZ), &lt;strong&gt;NAT&lt;/strong&gt; for egress, no instance public IP; instances register to one target group with &lt;strong&gt;HTTP /&lt;/strong&gt; health check expecting &lt;strong&gt;200&lt;/strong&gt;; instance SG allows &lt;strong&gt;:80&lt;/strong&gt; from the &lt;strong&gt;ALB&lt;/strong&gt; SG and from the &lt;strong&gt;VPC CIDR&lt;/strong&gt;. &lt;strong&gt;&lt;code&gt;enable_ssm=true&lt;/code&gt; (default):&lt;/strong&gt; &lt;strong&gt;Session Manager&lt;/strong&gt; with &lt;strong&gt;AmazonSSMManagedInstanceCore&lt;/strong&gt;—no SSH in the template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_count&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use_alb&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;az_count&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;instance_count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use_alb&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;az_count&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"az_count"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;az_count&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;az_count&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use_alb&lt;/span&gt; &lt;span class="err"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;az_count&lt;/span&gt; &lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"az_count must be between 1 and 6, and at least 2 when use_alb is true (ALB requirement)."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why EC2?
&lt;/h3&gt;

&lt;p&gt;Learning stack (Terraform + VPC + optional ELB), policy that prefers VPC-hosted sites, temporary lift-and-shift, or a box that might grow non-static behavior later. None of that makes EC2 the default for a &lt;strong&gt;pure&lt;/strong&gt; static site.&lt;/p&gt;

&lt;h3&gt;
  
  
  VPC
&lt;/h3&gt;

&lt;p&gt;Every instance is in a &lt;strong&gt;VPC&lt;/strong&gt; and a &lt;strong&gt;subnet&lt;/strong&gt; (&lt;a href="https://aws.amazon.com/ec2/faqs/#ec2-classic" rel="noopener noreferrer"&gt;EC2-Classic&lt;/a&gt; is gone for new accounts). The demo creates its own VPC—public subnets always; &lt;strong&gt;private subnets + NAT&lt;/strong&gt; when &lt;strong&gt;&lt;code&gt;use_alb=true&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use_alb = false
  +----------+   :80 (public IP)   +----------------+
  | Clients  | ------------------&amp;gt; | EC2 (nginx)    |
  +----------+                     +----------------+

use_alb = true
  +----------+   :80   +-----+   :80   +----------------+
  | Clients  | ------&amp;gt; | ALB | ------&amp;gt; | EC2 × az_count |
  +----------+         +-----+         | (private)      |
                                      +----------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;NAT&lt;/strong&gt; is billed when the ALB path is on. Repeated &lt;strong&gt;&lt;code&gt;curl&lt;/code&gt;&lt;/strong&gt; to the ALB can show different &lt;strong&gt;AZ / private IP&lt;/strong&gt; in &lt;strong&gt;&lt;code&gt;index.html&lt;/code&gt;&lt;/strong&gt; as backends rotate. &lt;strong&gt;&lt;code&gt;user_data&lt;/code&gt;&lt;/strong&gt; fills those fields via &lt;strong&gt;IMDSv2&lt;/strong&gt; after &lt;strong&gt;nginx&lt;/strong&gt; is up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;IMDS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="s2"&gt;"http://169.254.169.254/latest/api/token"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token-ttl-seconds: 21600"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;PRIVATE_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token: &lt;/span&gt;&lt;span class="nv"&gt;$IMDS_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"http://169.254.169.254/latest/meta-data/local-ipv4"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;AZ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-aws-ec2-metadata-token: &lt;/span&gt;&lt;span class="nv"&gt;$IMDS_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"http://169.254.169.254/latest/meta-data/placement/availability-zone"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;main.tf&lt;/code&gt; — placement and bootstrap:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance_count&lt;/span&gt;

  &lt;span class="nx"&gt;subnet_id&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use_alb&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;aws_subnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_security_group_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;user_data&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;templatefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/user_data.tftpl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;enable_ssm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enable_ssm&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;user_data_replace_on_change&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;iam_instance_profile&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enable_ssm&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_instance_profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ssm&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;main.tf&lt;/code&gt; — instance ingress (ALB on) and target group health check:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;"ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use_alb&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP from ALB security group (forwarded client traffic)"&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;security_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;"ingress"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;for_each&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use_alb&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP from VPC for ALB health checks and internal probes"&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cidr_block&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_target_group"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use_alb&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="nx"&gt;port&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;
  &lt;span class="nx"&gt;protocol&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
  &lt;span class="nx"&gt;protocol_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP1"&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;

  &lt;span class="nx"&gt;health_check&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;enabled&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;
    &lt;span class="nx"&gt;port&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"traffic-port"&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/"&lt;/span&gt;
    &lt;span class="nx"&gt;matcher&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"200"&lt;/span&gt;
    &lt;span class="nx"&gt;interval&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;
    &lt;span class="nx"&gt;timeout&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
    &lt;span class="nx"&gt;healthy_threshold&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="nx"&gt;unhealthy_threshold&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Session Manager
&lt;/h3&gt;

&lt;p&gt;Agent + &lt;strong&gt;AmazonSSMManagedInstanceCore&lt;/strong&gt;; &lt;strong&gt;&lt;code&gt;user_data.tftpl&lt;/code&gt;&lt;/strong&gt; via &lt;strong&gt;&lt;code&gt;templatefile&lt;/code&gt;&lt;/strong&gt; so &lt;strong&gt;&lt;code&gt;#!/bin/bash&lt;/code&gt;&lt;/strong&gt; is at column 0 (indented &lt;strong&gt;&lt;code&gt;heredoc&lt;/code&gt;&lt;/strong&gt; in Terraform often breaks the shebang). With &lt;strong&gt;&lt;code&gt;enable_ssm&lt;/code&gt;&lt;/strong&gt;, the template pulls the &lt;strong&gt;SSM Agent RPM from S3&lt;/strong&gt; and starts the service. Use the AMI’s &lt;strong&gt;&lt;code&gt;curl&lt;/code&gt;&lt;/strong&gt;—do not &lt;strong&gt;&lt;code&gt;dnf install curl&lt;/code&gt;&lt;/strong&gt; on AL2023 (conflicts with &lt;strong&gt;&lt;code&gt;curl-minimal&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;set -e&lt;/code&gt;&lt;/strong&gt; aborts before &lt;strong&gt;nginx&lt;/strong&gt;). Egress: &lt;strong&gt;NAT&lt;/strong&gt; or &lt;strong&gt;SSM VPC endpoints&lt;/strong&gt;. Docs: &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/agent-install-al2.html" rel="noopener noreferrer"&gt;agent install&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent-status-and-restart.html" rel="noopener noreferrer"&gt;status&lt;/a&gt;. Your principal needs &lt;strong&gt;&lt;code&gt;ssm:StartSession&lt;/code&gt;&lt;/strong&gt;; &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html" rel="noopener noreferrer"&gt;Session Manager plugin&lt;/a&gt; for CLI. After apply, wait for &lt;strong&gt;Online&lt;/strong&gt; in Fleet Manager; with &lt;strong&gt;&lt;code&gt;use_alb=true&lt;/code&gt;&lt;/strong&gt;, use &lt;strong&gt;&lt;code&gt;instance_ids&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;ssm_start_session_command&lt;/code&gt;&lt;/strong&gt; (first instance). &lt;strong&gt;&lt;code&gt;%{ if enable_ssm ~}&lt;/code&gt;&lt;/strong&gt; … &lt;strong&gt;&lt;code&gt;%{ endif ~}&lt;/code&gt;&lt;/strong&gt; wraps the SSM block in the template.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;iam.tf&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_role_policy_attachment"&lt;/span&gt; &lt;span class="s2"&gt;"ssm_core"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enable_ssm&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ssm&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;policy_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;user_data.tftpl&lt;/code&gt; (SSM path):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Column-0 shebang required: indented Terraform heredocs break #!/bin/bash and cloud-init may skip the script.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eux&lt;/span&gt;
&lt;span class="c"&gt;# Do not `dnf install curl` here: AL2023 ships curl-minimal; installing full curl conflicts and aborts the whole script under set -e.&lt;/span&gt;
&lt;span class="nv"&gt;SSM_RPM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in
  &lt;/span&gt;x86_64&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;SSM_RPM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm"&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
  aarch64&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;SSM_RPM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_arm64/amazon-ssm-agent.rpm"&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Unsupported arch for SSM agent RPM"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1 &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/amazon-ssm-agent.rpm &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSM_RPM&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; /tmp/amazon-ssm-agent.rpm
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /tmp/amazon-ssm-agent.rpm
systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;amazon-ssm-agent
systemctl restart amazon-ssm-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  robots.txt
&lt;/h3&gt;

&lt;p&gt;Crawler hint file—not security. The demo writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User-agent: *
Allow: /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;user_data.tftpl&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/usr/share/nginx/html/robots.txt &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;ROBOTS&lt;/span&gt;&lt;span class="sh"&gt;'
User-agent: *
Allow: /
&lt;/span&gt;&lt;span class="no"&gt;ROBOTS
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://developers.google.com/search/docs/crawling-indexing/robots/intro" rel="noopener noreferrer"&gt;Google: robots.txt&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/jdevto/tf-aws-ec2-static-demo.git
&lt;span class="nb"&gt;cd &lt;/span&gt;tf-aws-ec2-static-demo
terraform init &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ALB (needs ≥2 AZs; default &lt;strong&gt;&lt;code&gt;az_count&lt;/code&gt;&lt;/strong&gt; is 3):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply &lt;span class="nt"&gt;-var&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"use_alb=true"&lt;/span&gt;
&lt;span class="c"&gt;# terraform apply -var="use_alb=true" -var="az_count=2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait &lt;strong&gt;1–2 minutes&lt;/strong&gt; after first boot for &lt;strong&gt;&lt;code&gt;user_data&lt;/code&gt;&lt;/strong&gt;. &lt;strong&gt;&lt;code&gt;user_data_replace_on_change = true&lt;/code&gt;&lt;/strong&gt; replaces instances when the template changes. Variables: repo &lt;strong&gt;&lt;a href="https://github.com/jdevto/tf-aws-ec2-static-demo/blob/main/README.md" rel="noopener noreferrer"&gt;README&lt;/a&gt;&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"use_alb"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bool&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"allowed_http_cidr"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0.0.0.0/0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"enable_ssm"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bool&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform output verify_commands
&lt;span class="c"&gt;# use_alb=false:&lt;/span&gt;
curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; instance_public_ip&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/robots.txt"&lt;/span&gt;
&lt;span class="c"&gt;# use_alb=true (no instance public IP):&lt;/span&gt;
&lt;span class="c"&gt;# curl -sS "$(terraform output -raw website_url_alb)/robots.txt"&lt;/span&gt;
terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; ssm_start_session_command
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Troubleshooting
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Issue&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Timeout / connection refused&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;use_alb=true&lt;/code&gt;:&lt;/strong&gt; use ALB URL, not instance IP. &lt;strong&gt;&lt;code&gt;false&lt;/code&gt;:&lt;/strong&gt; SG, &lt;strong&gt;&lt;code&gt;allowed_http_cidr&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;user_data&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;instance_public_ip&lt;/code&gt;&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ALB unhealthy / &lt;strong&gt;502&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;SG &lt;strong&gt;:80&lt;/strong&gt; from ALB + VPC; &lt;strong&gt;&lt;code&gt;/&lt;/code&gt;&lt;/strong&gt; returns &lt;strong&gt;200&lt;/strong&gt;. &lt;strong&gt;&lt;code&gt;cloud-init-output.log&lt;/code&gt;:&lt;/strong&gt; failed &lt;strong&gt;&lt;code&gt;user_data&lt;/code&gt;&lt;/strong&gt; (e.g. &lt;strong&gt;&lt;code&gt;dnf install curl&lt;/code&gt;&lt;/strong&gt; vs &lt;strong&gt;&lt;code&gt;curl-minimal&lt;/code&gt;&lt;/strong&gt;).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apply limits&lt;/td&gt;
&lt;td&gt;Other region/account or limit increase.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSM offline / denied&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;enable_ssm&lt;/code&gt;&lt;/strong&gt;, agent time, &lt;strong&gt;&lt;code&gt;ssm:StartSession&lt;/code&gt;&lt;/strong&gt;, outbound to SSM (NAT or endpoints).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jdevto/tf-aws-ec2-static-demo" rel="noopener noreferrer"&gt;tf-aws-ec2-static-demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs" rel="noopener noreferrer"&gt;Terraform AWS provider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html" rel="noopener noreferrer"&gt;Amazon VPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html" rel="noopener noreferrer"&gt;Application Load Balancer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html" rel="noopener noreferrer"&gt;Session Manager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html" rel="noopener noreferrer"&gt;Session Manager plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>ec2</category>
      <category>vpc</category>
      <category>ssm</category>
    </item>
    <item>
      <title>Using GoAccess to View nginx Logs</title>
      <dc:creator>John  Ajera</dc:creator>
      <pubDate>Sat, 28 Mar 2026 08:54:19 +0000</pubDate>
      <link>https://dev.to/jajera/using-goaccess-to-view-nginx-logs-1d1c</link>
      <guid>https://dev.to/jajera/using-goaccess-to-view-nginx-logs-1d1c</guid>
      <description>&lt;h2&gt;
  
  
  Using GoAccess to View nginx Logs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://goaccess.io/" rel="noopener noreferrer"&gt;GoAccess&lt;/a&gt; is a fast terminal and HTML log analyzer for nginx access logs (requests/sec, status codes, top URLs, referrers, user agents). This guide covers installation, optional S3 download, and typical run modes.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Overview
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;strong&gt;GoAccess&lt;/strong&gt; (package manager, source, or &lt;a href="https://github.com/jdevto/cli-tools" rel="noopener noreferrer"&gt;jdevto/cli-tools&lt;/a&gt; script)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional:&lt;/strong&gt; sync logs from &lt;strong&gt;S3&lt;/strong&gt; when they are not on the machine you analyze from&lt;/li&gt;
&lt;li&gt;Run &lt;strong&gt;TUI&lt;/strong&gt;, &lt;strong&gt;HTML report&lt;/strong&gt;, &lt;strong&gt;live tail&lt;/strong&gt;, or &lt;strong&gt;compressed&lt;/strong&gt; input; use &lt;code&gt;COMBINED&lt;/code&gt; or &lt;code&gt;COMMON&lt;/code&gt; to match nginx &lt;code&gt;log_format&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skip §4 if logs are already on disk.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Readable &lt;strong&gt;nginx access logs&lt;/strong&gt; (usually &lt;strong&gt;combined&lt;/strong&gt; or &lt;strong&gt;common&lt;/strong&gt; format)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sudo&lt;/strong&gt; for distro package installs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 (optional):&lt;/strong&gt; AWS CLI v2 and &lt;code&gt;s3:GetObject&lt;/code&gt; / &lt;code&gt;s3:ListBucket&lt;/code&gt; for the prefix you use&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Install GoAccess
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Fedora / RHEL / CentOS Stream
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; goaccess
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Debian / Ubuntu
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; goaccess
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;goaccess &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Newer or custom build:&lt;/strong&gt; &lt;a href="https://goaccess.io/download" rel="noopener noreferrer"&gt;official build instructions&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  jdevto/cli-tools installer (optional)
&lt;/h4&gt;

&lt;p&gt;Builds from the &lt;a href="https://tar.goaccess.io" rel="noopener noreferrer"&gt;official tarball&lt;/a&gt;; details and env vars in &lt;a href="https://github.com/jdevto/cli-tools/blob/main/docs/install_goaccess.md" rel="noopener noreferrer"&gt;install_goaccess.md&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://raw.githubusercontent.com/jdevto/cli-tools/main/scripts/install_goaccess.sh&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://raw.githubusercontent.com/jdevto/cli-tools/main/scripts/install_goaccess.sh&lt;span class="o"&gt;)&lt;/span&gt; uninstall
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. Optional: Download nginx Logs from S3
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Single file
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/nginx-logs
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;s3://my-bucket/path/to/access.log ~/nginx-logs/access.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key is &lt;code&gt;.gz&lt;/code&gt; only:&lt;/strong&gt; download, decompress, or stream without extracting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;s3://my-bucket/path/to/access.log.gz ~/nginx-logs/access.log.gz
&lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; ~/nginx-logs/access.log.gz
&lt;span class="c"&gt;# or: zcat ~/nginx-logs/access.log.gz | goaccess - --log-format=COMBINED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Prefix (many files)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/nginx-logs
aws s3 &lt;span class="nb"&gt;sync &lt;/span&gt;s3://my-bucket/nginx-logs/ ~/nginx-logs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace bucket and prefix. If you only have &lt;strong&gt;&lt;code&gt;*.gz&lt;/code&gt;&lt;/strong&gt;, after sync use &lt;code&gt;zcat ~/nginx-logs/*.gz &amp;gt; ~/nginx-logs/merged-access.log&lt;/code&gt; (order across files may not be chronological) or decompress then merge plain &lt;code&gt;*.log&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Merge plain logs (optional)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/nginx-logs/&lt;span class="k"&gt;*&lt;/span&gt;.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/nginx-logs/merged-access.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5. Run GoAccess
&lt;/h3&gt;

&lt;p&gt;Match &lt;strong&gt;&lt;code&gt;--log-format&lt;/code&gt;&lt;/strong&gt; to nginx (&lt;code&gt;COMBINED&lt;/code&gt; is the usual default).&lt;/p&gt;

&lt;h4&gt;
  
  
  Terminal UI
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;goaccess /var/log/nginx/access.log &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  HTML report
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;goaccess /var/log/nginx/access.log &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED &lt;span class="nt"&gt;-o&lt;/span&gt; report.html &lt;span class="nt"&gt;--no-parsing-spinner&lt;/span&gt;
xdg-open report.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Live log
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/access.log | goaccess - &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Common log format
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;goaccess /var/log/nginx/access.log &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMMON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Custom &lt;code&gt;log_format&lt;/code&gt;: map fields with GoAccess &lt;a href="https://goaccess.io/man" rel="noopener noreferrer"&gt;custom format&lt;/a&gt; tokens.&lt;/p&gt;

&lt;h4&gt;
  
  
  Compressed on disk
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zcat /var/log/nginx/access.log.&lt;span class="k"&gt;*&lt;/span&gt;.gz | goaccess - &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  6. Summary: Copy-Paste
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; goaccess
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/nginx-logs &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; aws s3 &lt;span class="nb"&gt;sync &lt;/span&gt;s3://YOUR_BUCKET/YOUR_PREFIX/ ~/nginx-logs/   &lt;span class="c"&gt;# optional&lt;/span&gt;
goaccess /var/log/nginx/access.log &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED
goaccess /var/log/nginx/access.log &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED &lt;span class="nt"&gt;-o&lt;/span&gt; report.html &lt;span class="nt"&gt;--no-parsing-spinner&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; xdg-open report.html
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/access.log | goaccess - &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  7. Troubleshooting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Parsed 0 lines / wrong numbers:&lt;/strong&gt; Log line shape ≠ &lt;code&gt;COMBINED&lt;/code&gt;/&lt;code&gt;COMMON&lt;/code&gt;. Compare &lt;code&gt;head -1&lt;/code&gt; to nginx &lt;code&gt;log_format&lt;/code&gt; and adjust &lt;code&gt;--log-format&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Permission denied on log:&lt;/strong&gt; &lt;code&gt;sudo goaccess ...&lt;/code&gt;, fix group on log files, or copy the log to your home directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S3 Access Denied:&lt;/strong&gt; IAM on bucket/prefix; check &lt;code&gt;AWS_PROFILE&lt;/code&gt; / role.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live tail idle:&lt;/strong&gt; Confirm nginx &lt;code&gt;access_log&lt;/code&gt; path and read permissions on the active file.&lt;/p&gt;




&lt;h3&gt;
  
  
  8. References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://goaccess.io/" rel="noopener noreferrer"&gt;GoAccess&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://goaccess.io/man" rel="noopener noreferrer"&gt;GoAccess man (log formats)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/http/ngx_http_log_module.html" rel="noopener noreferrer"&gt;nginx log module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/reference/s3/cp.html" rel="noopener noreferrer"&gt;AWS CLI s3 cp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>goaccess</category>
      <category>nginx</category>
      <category>logs</category>
      <category>aws</category>
    </item>
    <item>
      <title>Using GoAccess to View nginx Logs</title>
      <dc:creator>John  Ajera</dc:creator>
      <pubDate>Sun, 22 Mar 2026 23:22:42 +0000</pubDate>
      <link>https://dev.to/jajera/using-goaccess-to-view-nginx-logs-1491</link>
      <guid>https://dev.to/jajera/using-goaccess-to-view-nginx-logs-1491</guid>
      <description>&lt;h2&gt;
  
  
  Using GoAccess to View nginx Logs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://goaccess.io/" rel="noopener noreferrer"&gt;GoAccess&lt;/a&gt; is a fast terminal and HTML log analyzer for nginx access logs (requests/sec, status codes, top URLs, referrers, user agents). This guide covers installation, optional S3 download, and typical run modes.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Overview
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Install &lt;strong&gt;GoAccess&lt;/strong&gt; (package manager, source, or &lt;a href="https://github.com/jdevto/cli-tools" rel="noopener noreferrer"&gt;jdevto/cli-tools&lt;/a&gt; script)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional:&lt;/strong&gt; sync logs from &lt;strong&gt;S3&lt;/strong&gt; when they are not on the machine you analyze from&lt;/li&gt;
&lt;li&gt;Run &lt;strong&gt;TUI&lt;/strong&gt;, &lt;strong&gt;HTML report&lt;/strong&gt;, &lt;strong&gt;live tail&lt;/strong&gt;, or &lt;strong&gt;compressed&lt;/strong&gt; input; use &lt;code&gt;COMBINED&lt;/code&gt; or &lt;code&gt;COMMON&lt;/code&gt; to match nginx &lt;code&gt;log_format&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Skip §4 if logs are already on disk.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Readable &lt;strong&gt;nginx access logs&lt;/strong&gt; (usually &lt;strong&gt;combined&lt;/strong&gt; or &lt;strong&gt;common&lt;/strong&gt; format)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sudo&lt;/strong&gt; for distro package installs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 (optional):&lt;/strong&gt; AWS CLI v2 and &lt;code&gt;s3:GetObject&lt;/code&gt; / &lt;code&gt;s3:ListBucket&lt;/code&gt; for the prefix you use&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Install GoAccess
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Fedora / RHEL / CentOS Stream
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; goaccess
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Debian / Ubuntu
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; goaccess
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;goaccess &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Newer or custom build:&lt;/strong&gt; &lt;a href="https://goaccess.io/download" rel="noopener noreferrer"&gt;official build instructions&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  jdevto/cli-tools installer (optional)
&lt;/h4&gt;

&lt;p&gt;Builds from the &lt;a href="https://tar.goaccess.io" rel="noopener noreferrer"&gt;official tarball&lt;/a&gt;; details and env vars in &lt;a href="https://github.com/jdevto/cli-tools/blob/main/docs/install_goaccess.md" rel="noopener noreferrer"&gt;install_goaccess.md&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://raw.githubusercontent.com/jdevto/cli-tools/main/scripts/install_goaccess.sh&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://raw.githubusercontent.com/jdevto/cli-tools/main/scripts/install_goaccess.sh&lt;span class="o"&gt;)&lt;/span&gt; uninstall
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. Optional: Download nginx Logs from S3
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Single file
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/nginx-logs
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;s3://my-bucket/path/to/access.log ~/nginx-logs/access.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key is &lt;code&gt;.gz&lt;/code&gt; only:&lt;/strong&gt; download, decompress, or stream without extracting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;s3://my-bucket/path/to/access.log.gz ~/nginx-logs/access.log.gz
&lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; ~/nginx-logs/access.log.gz
&lt;span class="c"&gt;# or: zcat ~/nginx-logs/access.log.gz | goaccess - --log-format=COMBINED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Prefix (many files)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/nginx-logs
aws s3 &lt;span class="nb"&gt;sync &lt;/span&gt;s3://my-bucket/nginx-logs/ ~/nginx-logs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace bucket and prefix. If you only have &lt;strong&gt;&lt;code&gt;*.gz&lt;/code&gt;&lt;/strong&gt;, after sync use &lt;code&gt;zcat ~/nginx-logs/*.gz &amp;gt; ~/nginx-logs/merged-access.log&lt;/code&gt; (order across files may not be chronological) or decompress then merge plain &lt;code&gt;*.log&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Merge plain logs (optional)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/nginx-logs/&lt;span class="k"&gt;*&lt;/span&gt;.log &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/nginx-logs/merged-access.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5. Run GoAccess
&lt;/h3&gt;

&lt;p&gt;Match &lt;strong&gt;&lt;code&gt;--log-format&lt;/code&gt;&lt;/strong&gt; to nginx (&lt;code&gt;COMBINED&lt;/code&gt; is the usual default).&lt;/p&gt;

&lt;h4&gt;
  
  
  Terminal UI
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;goaccess /var/log/nginx/access.log &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  HTML report
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;goaccess /var/log/nginx/access.log &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED &lt;span class="nt"&gt;-o&lt;/span&gt; report.html &lt;span class="nt"&gt;--no-parsing-spinner&lt;/span&gt;
xdg-open report.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Live log
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/access.log | goaccess - &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Common log format
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;goaccess /var/log/nginx/access.log &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMMON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Custom &lt;code&gt;log_format&lt;/code&gt;: map fields with GoAccess &lt;a href="https://goaccess.io/man" rel="noopener noreferrer"&gt;custom format&lt;/a&gt; tokens.&lt;/p&gt;

&lt;h4&gt;
  
  
  Compressed on disk
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zcat /var/log/nginx/access.log.&lt;span class="k"&gt;*&lt;/span&gt;.gz | goaccess - &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  6. Summary: Copy-Paste
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; goaccess
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/nginx-logs &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; aws s3 &lt;span class="nb"&gt;sync &lt;/span&gt;s3://YOUR_BUCKET/YOUR_PREFIX/ ~/nginx-logs/   &lt;span class="c"&gt;# optional&lt;/span&gt;
goaccess /var/log/nginx/access.log &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED
goaccess /var/log/nginx/access.log &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED &lt;span class="nt"&gt;-o&lt;/span&gt; report.html &lt;span class="nt"&gt;--no-parsing-spinner&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; xdg-open report.html
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/access.log | goaccess - &lt;span class="nt"&gt;--log-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;COMBINED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  7. Troubleshooting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Parsed 0 lines / wrong numbers:&lt;/strong&gt; Log line shape ≠ &lt;code&gt;COMBINED&lt;/code&gt;/&lt;code&gt;COMMON&lt;/code&gt;. Compare &lt;code&gt;head -1&lt;/code&gt; to nginx &lt;code&gt;log_format&lt;/code&gt; and adjust &lt;code&gt;--log-format&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Permission denied on log:&lt;/strong&gt; &lt;code&gt;sudo goaccess ...&lt;/code&gt;, fix group on log files, or copy the log to your home directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S3 Access Denied:&lt;/strong&gt; IAM on bucket/prefix; check &lt;code&gt;AWS_PROFILE&lt;/code&gt; / role.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live tail idle:&lt;/strong&gt; Confirm nginx &lt;code&gt;access_log&lt;/code&gt; path and read permissions on the active file.&lt;/p&gt;




&lt;h3&gt;
  
  
  8. References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://goaccess.io/" rel="noopener noreferrer"&gt;GoAccess&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://goaccess.io/man" rel="noopener noreferrer"&gt;GoAccess man (log formats)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nginx.org/en/docs/http/ngx_http_log_module.html" rel="noopener noreferrer"&gt;nginx log module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/reference/s3/cp.html" rel="noopener noreferrer"&gt;AWS CLI s3 cp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>goaccess</category>
      <category>nginx</category>
      <category>logs</category>
      <category>aws</category>
    </item>
  </channel>
</rss>
