<?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: Sunny JayaRaju</title>
    <description>The latest articles on DEV Community by Sunny JayaRaju (@sunnyjayaraju).</description>
    <link>https://dev.to/sunnyjayaraju</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3772611%2F5bd08e1d-942d-4acd-8363-1efc038e1dc9.png</url>
      <title>DEV Community: Sunny JayaRaju</title>
      <link>https://dev.to/sunnyjayaraju</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sunnyjayaraju"/>
    <language>en</language>
    <item>
      <title>Apigee X Eval Org Stuck at 502? Check These 2 Things First</title>
      <dc:creator>Sunny JayaRaju</dc:creator>
      <pubDate>Sat, 05 Sep 2026 07:04:20 +0000</pubDate>
      <link>https://dev.to/sunnyjayaraju/apigee-x-eval-org-stuck-at-502-check-these-2-things-first-50mm</link>
      <guid>https://dev.to/sunnyjayaraju/apigee-x-eval-org-stuck-at-502-check-these-2-things-first-50mm</guid>
      <description>&lt;p&gt;&lt;em&gt;Quick-fix reference. For the full debugging story behind this, see: &lt;a href="https://dev.to/sunnyjayaraju/the-502-that-wouldnt-die-debugging-a-broken-apigee-eval-org-from-the-ground-up-4m2k"&gt;The 502 That Wouldn't Die&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom
&lt;/h2&gt;

&lt;p&gt;Your Apigee X evaluation org shows fully provisioned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Console wizard: all 4 setup steps green ✅&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;organizations.get&lt;/code&gt; API: &lt;code&gt;"state": "ACTIVE"&lt;/code&gt; ✅&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;instances.list&lt;/code&gt; API: &lt;code&gt;"state": "ACTIVE"&lt;/code&gt;, real host/port ✅&lt;/li&gt;
&lt;li&gt;Environment attached, proxy deployed, envgroup hostname bound — all clean ✅&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And yet every request — even to a brand-new proxy — returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;502&lt;/span&gt;
&lt;span class="na"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Server Error&lt;/span&gt;
&lt;span class="s"&gt;The server encountered a temporary error and could not complete your request.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Waiting longer doesn't fix it. Deploying a different proxy doesn't fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  First: confirm this is actually your bug
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute backend-services list
gcloud compute backend-services get-health apigee-proxy-backend &lt;span class="nt"&gt;--global&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this shows &lt;code&gt;healthState: UNHEALTHY&lt;/code&gt; on the &lt;code&gt;apigee-proxy-*&lt;/code&gt; instances, keep reading — this is the load-balancer layer that sits between the external HTTPS LB and your actual Apigee runtime, and it's separate from Apigee's own control plane. That's why everything above reports "ACTIVE" while requests still 502: Apigee's config is correct, but the forwarding instances behind the LB aren't actually serving traffic.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note before you start:&lt;/strong&gt; the fixes below both work on the &lt;strong&gt;instance template&lt;/strong&gt;, not just the live running instances. These forwarding VMs are preemptible — Google can reclaim and silently replace them at any time, and a replacement rebuilds straight from the template. If you patch a live instance directly instead of the template, the fix will quietly disappear the next time that VM gets swapped, and you'll be back here wondering why it "randomly broke again."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cause #1: Missing service account on the instance template
&lt;/h2&gt;

&lt;p&gt;Check the boot log of one of the unhealthy instances:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute instances get-serial-port-output &amp;lt;INSTANCE_NAME&amp;gt; &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;ZONE&amp;gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Instance has service account: false, ...
Failed to download from GCS: ... credentials: cannot fetch token ...
Trying unauthenticated download
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute instance-templates describe apigee-proxy-&amp;lt;REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"yaml(properties.serviceAccounts)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this prints &lt;code&gt;null&lt;/code&gt;, the template has no service account attached, so the VM can never authenticate to Cloud Storage to pull its real startup script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix — clone the template with a service account attached, then roll the MIG onto it:&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;# Get every field from your existing template first so you replicate it exactly:&lt;/span&gt;
gcloud compute instance-templates describe apigee-proxy-&amp;lt;REGION&amp;gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;yaml

gcloud compute instance-templates create apigee-proxy-&amp;lt;REGION&amp;gt;-fixed &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--machine-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;e2-micro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;debian-cloud &lt;span class="nt"&gt;--image-family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;debian-12 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--boot-disk-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20GB &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default &lt;span class="nt"&gt;--subnet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https-server,apigee-proxy,gke-apigee-proxy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;startup-script-url&lt;span class="o"&gt;=&lt;/span&gt;gs://apigee-5g-saas/apigee-envoy-proxy-release/latest/conf/startup-script.sh,ENDPOINT&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;PROJECT_NUMBER&amp;gt;-compute@developer.gserviceaccount.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scopes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cloud-platform &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--preemptible&lt;/span&gt; &lt;span class="nt"&gt;--no-restart-on-failure&lt;/span&gt; &lt;span class="nt"&gt;--maintenance-policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TERMINATE

gcloud compute instance-groups managed set-instance-template apigee-proxy-&amp;lt;REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;apigee-proxy-&amp;lt;REGION&amp;gt;-fixed &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;REGION&amp;gt;

gcloud compute instance-groups managed rolling-action replace apigee-proxy-&amp;lt;REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;REGION&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Match every field from your &lt;code&gt;describe --format=yaml&lt;/code&gt; output — machine type, disk, network, tags, and especially the &lt;code&gt;scheduling&lt;/code&gt; block. &lt;code&gt;--preemptible&lt;/code&gt;, &lt;code&gt;--no-restart-on-failure&lt;/code&gt;, and &lt;code&gt;--maintenance-policy=TERMINATE&lt;/code&gt; must be specified together or &lt;code&gt;gcloud&lt;/code&gt; rejects the combination.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Cause #2: Blank &lt;code&gt;ENDPOINT&lt;/code&gt; metadata
&lt;/h2&gt;

&lt;p&gt;Even after fixing the service account, health checks can still fail. These forwarding VMs don't run a proxy application themselves — they install an &lt;code&gt;iptables&lt;/code&gt; DNAT rule redirecting incoming port-443 traffic to your real Apigee runtime instance's internal IP. That IP comes from an instance metadata key called &lt;code&gt;ENDPOINT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Check it from inside an instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute ssh &amp;lt;INSTANCE_NAME&amp;gt; &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;ZONE&amp;gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Metadata-Flavor: Google"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"http://metadata.google.internal/computeMetadata/v1/instance/attributes/ENDPOINT"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this returns nothing, that's the second bug. Get your runtime instance's real internal IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gcloud auth print-access-token&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="s2"&gt;"https://apigee.googleapis.com/v1/organizations/&amp;lt;ORG&amp;gt;/instances"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for the &lt;code&gt;"host"&lt;/code&gt; field in the response (e.g. &lt;code&gt;10.51.204.98&lt;/code&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Don't just patch the running instances — patch the template.&lt;/strong&gt; These forwarding VMs are typically &lt;code&gt;--preemptible&lt;/code&gt;, meaning Google can reclaim them at any time (often within 24h). When that happens, the managed instance group silently rebuilds a replacement &lt;strong&gt;straight from the template&lt;/strong&gt; — and if you only patched the live instance's metadata, the new one comes back with &lt;code&gt;ENDPOINT&lt;/code&gt; blank again, and you're right back to a 502 with no idea why it "randomly broke." Fix the template, not just the instance, or this will relapse on you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Fix — bake &lt;code&gt;ENDPOINT&lt;/code&gt; into a corrected template, then roll the instance group onto it (this is the fix that actually survives instance replacement):&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;gcloud compute instance-templates create apigee-proxy-&amp;lt;REGION&amp;gt;-fixed-v2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--machine-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;e2-micro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;debian-cloud &lt;span class="nt"&gt;--image-family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;debian-12 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--boot-disk-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20GB &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default &lt;span class="nt"&gt;--subnet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https-server,apigee-proxy,gke-apigee-proxy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;startup-script-url&lt;span class="o"&gt;=&lt;/span&gt;gs://apigee-5g-saas/apigee-envoy-proxy-release/latest/conf/startup-script.sh,ENDPOINT&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;RUNTIME_INTERNAL_IP&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;PROJECT_NUMBER&amp;gt;-compute@developer.gserviceaccount.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scopes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cloud-platform &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--preemptible&lt;/span&gt; &lt;span class="nt"&gt;--no-restart-on-failure&lt;/span&gt; &lt;span class="nt"&gt;--maintenance-policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TERMINATE

gcloud compute instance-groups managed set-instance-template apigee-proxy-&amp;lt;REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;apigee-proxy-&amp;lt;REGION&amp;gt;-fixed-v2 &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;REGION&amp;gt;

gcloud compute instance-groups managed rolling-action replace apigee-proxy-&amp;lt;REGION&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;REGION&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same pattern as the Cause #1 fix — clone the template with the correction included, point the MIG at it, roll. Once &lt;code&gt;ENDPOINT&lt;/code&gt; lives in the template itself, any future automatic replacement (preemption, autohealing, whatever) inherits the correct value with zero manual intervention.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(If you only need a same-minute workaround while the rolling-replace above is in flight, you can patch the live instance directly — &lt;code&gt;gcloud compute instances add-metadata &amp;lt;INSTANCE_NAME&amp;gt; --zone=&amp;lt;ZONE&amp;gt; --metadata=ENDPOINT=&amp;lt;RUNTIME_INTERNAL_IP&amp;gt;&lt;/code&gt; followed by &lt;code&gt;sudo google_metadata_script_runner startup&lt;/code&gt; over SSH — but treat that as temporary only, not the real fix.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Verify the NAT rule landed on the new instances:&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;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="c"&gt;# Look for: DNAT tcp dpt:443 to:&amp;lt;RUNTIME_INTERNAL_IP&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Confirm it's fixed
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute backend-services get-health apigee-proxy-backend &lt;span class="nt"&gt;--global&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both instances should now show &lt;code&gt;healthState: HEALTHY&lt;/code&gt;. Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://&amp;lt;YOUR_HOSTNAME&amp;gt;.nip.io/hello-world"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get a real response instead of the 502 page.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is one specific failure mode out of many possible causes of a 502 on Apigee X — always confirm the backend health check first before assuming this applies to you. Full context and the debugging process that led here: &lt;a href="https://dev.to/sunnyjayaraju/the-502-that-wouldnt-die-debugging-a-broken-apigee-eval-org-from-the-ground-up-4m2k"&gt;The 502 That Wouldn't Die&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gcp</category>
      <category>apigee</category>
      <category>cloud</category>
      <category>devops</category>
    </item>
    <item>
      <title>The 502 That Wouldn't Die: Debugging a Broken Apigee Eval Org From the Ground Up</title>
      <dc:creator>Sunny JayaRaju</dc:creator>
      <pubDate>Sat, 05 Sep 2026 06:32:53 +0000</pubDate>
      <link>https://dev.to/sunnyjayaraju/the-502-that-wouldnt-die-debugging-a-broken-apigee-eval-org-from-the-ground-up-4m2k</link>
      <guid>https://dev.to/sunnyjayaraju/the-502-that-wouldnt-die-debugging-a-broken-apigee-eval-org-from-the-ground-up-4m2k</guid>
      <description>&lt;p&gt;&lt;em&gt;A real-world war story of chasing an Apigee X evaluation organization from "Server Error" to "Hello, Guest!" — the hard way, with &lt;code&gt;gcloud&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt;, and a lot of patience.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I spent hours debugging a persistent &lt;code&gt;502 Server Error&lt;/code&gt; on a fresh Apigee X evaluation organization. The Cloud Console showed every provisioning step green. The Apigee Management API confirmed the organization and runtime instance were both &lt;code&gt;ACTIVE&lt;/code&gt;. And yet every single request — even to a brand-new proxy — came back with a 502.&lt;/p&gt;

&lt;p&gt;The eventual root cause: &lt;strong&gt;the Apigee provisioning wizard built the proxy-forwarding instance template without a service account attached, and left a required &lt;code&gt;ENDPOINT&lt;/code&gt; metadata key blank.&lt;/strong&gt; Without a service account, the VMs couldn't authenticate to Cloud Storage to fetch their real startup script. Without &lt;code&gt;ENDPOINT&lt;/code&gt;, the fallback script had nothing to forward traffic to. The fix was to clone the broken instance template with the missing service account and the &lt;code&gt;ENDPOINT&lt;/code&gt; value both baked in, then roll the managed instance group onto it — fixing the template, not just the live instances, matters, since these forwarding VMs are preemptible and get silently replaced by Google; a fix applied only to a running instance quietly disappears the next time it's swapped (I found this out the hard way — see the Update section near the end).&lt;/p&gt;

&lt;p&gt;If you're staring at a 502 on a "successfully" provisioned Apigee eval org, this post is for you.&lt;/p&gt;




&lt;h2&gt;
  
  
  Background: how I got here
&lt;/h2&gt;

&lt;p&gt;I was setting up Apigee purely to practice — nothing fancy, no production intent, just a personal free-trial Google Cloud project to learn the platform. Along the way I made a very ordinary mistake: while creating a second project to experiment with, it ended up parented under &lt;strong&gt;"No organization"&lt;/strong&gt; instead of my actual Cloud Identity org, because I had several Console tabs open switching between projects mid-setup.&lt;/p&gt;

&lt;p&gt;Confused by the mismatch, I deleted that second project. Shortly after, my &lt;em&gt;original&lt;/em&gt; project's Apigee evaluation setup started throwing a &lt;strong&gt;resource-locked&lt;/strong&gt; error on the "Apigee evaluation organization" step, with a message like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;the resource is locked by another operation that is 1 percent completed so far
where organization &amp;lt;project-id&amp;gt; is currently being provisioned by operation: &amp;lt;uuid&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That project's setup never fully completed — the "Access routing" step permanently showed "Not configured." At that point, rather than trying to excavate a half-provisioned org with a stuck long-running operation, I made the call to &lt;strong&gt;shut down both projects and start clean in a brand-new project.&lt;/strong&gt; This is a legitimate, low-cost strategy for Apigee eval orgs specifically — they're free, disposable, and tied to your Cloud Billing account (not the project) for trial credits and the 60-day clock, so nuking a broken project costs you nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson #1: if you're mid-setup and juggling multiple projects, do it in separate browser profiles or windows, not tabs.&lt;/strong&gt; The Cloud Console's single-page-app state can leak between tabs in confusing ways (I saw one project's setup page briefly display another project's Apigee org name — almost certainly a UI caching artifact, not a real resource collision).&lt;/p&gt;




&lt;h2&gt;
  
  
  The rebuild: a promising start, then déjà vu
&lt;/h2&gt;

&lt;p&gt;With a fresh project, I walked through &lt;strong&gt;Apigee → Try Apigee for free → Set up Apigee Evaluation&lt;/strong&gt; step by step, in a single tab, waiting for each of the four steps to go green before touching anything else:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable APIs ✅&lt;/li&gt;
&lt;li&gt;Networking ✅&lt;/li&gt;
&lt;li&gt;Apigee evaluation organization&lt;/li&gt;
&lt;li&gt;Access routing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 3 immediately failed with a generic &lt;strong&gt;"Something went wrong"&lt;/strong&gt; and offered a "Try Again" button. Naturally, my first thought was: &lt;em&gt;here we go again.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But clicking "Try Again" resolved it within about nine minutes — which felt suspicious. Org creation is supposed to be a slow, heavyweight operation. Why would a failed attempt succeed almost instantly on retry?&lt;/p&gt;

&lt;h3&gt;
  
  
  Why the quick retry wasn't fishy
&lt;/h3&gt;

&lt;p&gt;It turns out this is documented, known behavior. Google's own &lt;a href="https://cloud.google.com/apigee/docs/api-platform/get-started/troubleshooting-install" rel="noopener noreferrer"&gt;Apigee installation troubleshooting guide&lt;/a&gt; calls out this exact failure mode:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If Apigee returns a 403 when you first try to create the new organization, it could mean that one or more of your APIs have not been enabled... If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Org creation is an asynchronous long-running operation (LRO) layered on top of API enablement from the prior step. There's a small window where the APIs &lt;em&gt;report&lt;/em&gt; as enabled but haven't fully propagated through Google's internal systems yet. The wizard's org-creation call hits that window, gets rejected, and shows "Something went wrong" — but the expensive networking/VPC-peering work from Step 2 already succeeded, so once the propagation catches up, the retry only has to do the comparatively cheap part (create the org record) and finishes fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't trust a UI checkmark on faith, though — verify independently.&lt;/strong&gt; From Cloud Shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud config &lt;span class="nb"&gt;set &lt;/span&gt;project YOUR_PROJECT_ID
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gcloud auth print-access-token&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="s2"&gt;"https://apigee.googleapis.com/v1/organizations/YOUR_PROJECT_ID"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for &lt;code&gt;"state": "ACTIVE"&lt;/code&gt; in the response. This hits the Apigee Management API directly, bypassing the Console entirely — the actual ground truth, not a rendered checkmark.&lt;/p&gt;

&lt;p&gt;Step 4 (Access routing) subsequently completed cleanly, and the wizard showed the triumphant &lt;strong&gt;"Congratulations! Your Apigee organization is ready"&lt;/strong&gt; message with a "Launch" button pointing at a &lt;code&gt;*.nip.io&lt;/code&gt; test URL.&lt;/p&gt;




&lt;h2&gt;
  
  
  The real bug: green everywhere, 502 everywhere
&lt;/h2&gt;

&lt;p&gt;I clicked Launch. &lt;code&gt;502 Server Error. The server encountered a temporary error and could not complete your request. Please try again in 30 seconds.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I waited the requisite 15–20 minutes for what I assumed was normal load-balancer propagation lag, then tried again. Still 502. I deployed a fresh test proxy just to rule out something proxy-specific. Still 502.&lt;/p&gt;

&lt;p&gt;This is the point where "just wait longer" stops being a reasonable answer and it's time to actually verify each layer of the stack independently, from the top down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: Is the request even reaching Apigee's infrastructure?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"https://YOUR_HOSTNAME.nip.io/hello-world"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The verbose output showed a &lt;strong&gt;clean TLS handshake with a valid, freshly-issued Google-managed certificate&lt;/strong&gt;, HTTP/2 negotiated, and a response that came back structured exactly like Google's generic Global Front End (GFE) "backend unhealthy" page — not a DNS failure, not a connection refusal, not an SSL error. This told me the request was reaching Google's load balancer correctly; the problem was somewhere &lt;em&gt;behind&lt;/em&gt; it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: Is the Apigee org/instance actually healthy?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gcloud auth print-access-token&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="s2"&gt;"https://apigee.googleapis.com/v1/organizations/YOUR_ORG/instances"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This came back &lt;code&gt;"state": "ACTIVE"&lt;/code&gt; with a real internal host/port and service attachment. So the actual Apigee runtime — the expensive, slow-to-provision Message Processor infrastructure — was completely fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3: Are environment, deployment, and hostname binding all correct?
&lt;/h3&gt;

&lt;p&gt;Three more calls, all clean:&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;# Is the environment attached to the instance?&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gcloud auth print-access-token&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="s2"&gt;".../instances/eval-instance/attachments"&lt;/span&gt;

&lt;span class="c"&gt;# Is a proxy actually deployed?&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gcloud auth print-access-token&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="s2"&gt;".../environments/eval/deployments"&lt;/span&gt;

&lt;span class="c"&gt;# Is the hostname bound to an environment group?&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;gcloud auth print-access-token&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="s2"&gt;".../envgroups"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Environment attached, proxy deployed, hostname bound and &lt;code&gt;ACTIVE&lt;/code&gt;. Every configuration object in the Apigee control plane was correct. This ruled out the entire Apigee-managed side of the stack. The problem had to be in the one piece of infrastructure that Apigee's "Access routing" step provisions but doesn't directly manage day-to-day: &lt;strong&gt;the Compute Engine load balancer and its backing instance group.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 4: The load balancer's backend health
&lt;/h3&gt;

&lt;p&gt;Apigee X's Access Routing step builds a small Compute Engine managed instance group (MIG) of lightweight forwarding VMs that sit between the external HTTPS Load Balancer and the actual Apigee runtime instance (which lives on a private/internal IP). Checking this directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute backend-services get-health apigee-proxy-backend &lt;span class="nt"&gt;--global&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;healthStatus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;healthState&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UNHEALTHY&lt;/span&gt;
  &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.../apigee-proxy-7ztd&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;healthState&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UNHEALTHY&lt;/span&gt;
  &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.../apigee-proxy-np3w&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;There it was.&lt;/strong&gt; Zero of two backend instances were passing their health check. That's a 502 by design — a GFE-class load balancer will never forward traffic to a backend it considers unhealthy, no matter how long you wait.&lt;/p&gt;




&lt;h2&gt;
  
  
  Root-causing the unhealthy backend
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ruling out the obvious suspect: firewall rules
&lt;/h3&gt;

&lt;p&gt;The most common cause of "backend permanently unhealthy" on a fresh GCP load balancer is a missing firewall rule allowing Google's health-check probe ranges (&lt;code&gt;130.211.0.0/22&lt;/code&gt; and &lt;code&gt;35.191.0.0/16&lt;/code&gt;) through to the target instances. I checked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute firewall-rules list &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"table(name,sourceRanges.list(),allowed[].map().firewall_rule().list(),targetTags.list())"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule existed, with the correct source ranges, correct port (443), and a matching target tag (&lt;code&gt;gke-apigee-proxy&lt;/code&gt;) — and a quick &lt;code&gt;gcloud compute instances describe&lt;/code&gt; confirmed the actual VMs carried that exact tag. Firewall theory eliminated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading the actual boot log
&lt;/h3&gt;

&lt;p&gt;With the network layer cleared, the next move was to read what the instance itself thought happened during boot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute instances get-serial-port-output apigee-proxy-7ztd &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;asia-south1-b | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Buried in there was the actual smoking gun:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Instance has service account: false, setting ACS client isEnabled to false
...
Failed to download from GCS: downloading object [apigee-envoy-proxy-release/latest/conf/startup-script.sh]...
credentials: cannot fetch token: metadata: GCE metadata "instance/service-accounts/default/token?..." not defined
Trying unauthenticated download
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The VM had no service account attached at all.&lt;/strong&gt; Without one, it can't mint an OAuth token, so it can't authenticate to Cloud Storage to pull down its &lt;em&gt;real&lt;/em&gt; startup script (the one that actually sets up the Envoy-based forwarding proxy). It silently fell back to an unauthenticated download, which only pulled a stub containing basic &lt;code&gt;sysctl&lt;/code&gt;/&lt;code&gt;iptables&lt;/code&gt; scaffolding — nothing that ever configures real port-forwarding to the Apigee runtime.&lt;/p&gt;

&lt;p&gt;Confirming this against the instance template sealed it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute instance-templates describe apigee-proxy-asia-south1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"yaml(properties.serviceAccounts)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meanwhile, a healthy default Compute Engine service account existed and was enabled on the project (&lt;code&gt;gcloud iam service-accounts list&lt;/code&gt;) — it just wasn't referenced anywhere on this template. This was a genuine defect in how the Apigee provisioning wizard built this specific instance template — not a quota issue, not an org policy, not something I'd misconfigured.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix, part 1: rebuild the template with a service account
&lt;/h3&gt;

&lt;p&gt;You can't attach a service account to an existing instance template after the fact — templates are immutable. So the fix is to clone it with the missing piece added, then roll the managed instance group onto the new template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute instance-templates create apigee-proxy-asia-south1-fixed &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--machine-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;e2-micro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;debian-cloud &lt;span class="nt"&gt;--image-family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;debian-12 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--boot-disk-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20GB &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default &lt;span class="nt"&gt;--subnet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;asia-south1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https-server,apigee-proxy,gke-apigee-proxy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;startup-script-url&lt;span class="o"&gt;=&lt;/span&gt;gs://apigee-5g-saas/apigee-envoy-proxy-release/latest/conf/startup-script.sh,ENDPOINT&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_PROJECT_NUMBER-compute@developer.gserviceaccount.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scopes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cloud-platform &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--preemptible&lt;/span&gt; &lt;span class="nt"&gt;--no-restart-on-failure&lt;/span&gt; &lt;span class="nt"&gt;--maintenance-policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TERMINATE

gcloud compute instance-groups managed set-instance-template apigee-proxy-asia-south1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;apigee-proxy-asia-south1-fixed &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;asia-south1

gcloud compute instance-groups managed rolling-action replace apigee-proxy-asia-south1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;asia-south1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; copy every field from your existing template's &lt;code&gt;describe --format=yaml&lt;/code&gt; output exactly — machine type, disk, network, tags, and especially the &lt;code&gt;scheduling&lt;/code&gt; block (&lt;code&gt;preemptible&lt;/code&gt; + &lt;code&gt;no-restart-on-failure&lt;/code&gt; + &lt;code&gt;maintenance-policy=TERMINATE&lt;/code&gt; must all be specified together or &lt;code&gt;gcloud&lt;/code&gt; rejects the combination).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After the rolling replace, the new instances came up with the service account correctly attached — the boot log's authentication error was gone. But health checks &lt;em&gt;still&lt;/em&gt; failed. One bug down, one to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix, part 2: the missing &lt;code&gt;ENDPOINT&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;SSHing into a replacement instance and checking what was actually listening confirmed there was still no process bound to port 443:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute ssh apigee-proxy-dgz1 &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;asia-south1-b
&lt;span class="nb"&gt;sudo &lt;/span&gt;ss &lt;span class="nt"&gt;-tlnp&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;443   &lt;span class="c"&gt;# → nothing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These forwarding VMs, it turns out, don't run a full proxy application themselves — they run a startup script that installs an &lt;code&gt;iptables&lt;/code&gt; DNAT (destination NAT) rule redirecting incoming port-443 traffic straight to the real Apigee runtime instance's internal IP. That destination IP is supplied via an instance metadata key called &lt;code&gt;ENDPOINT&lt;/code&gt;. On the original (and my recreated) template, that key existed — but was &lt;strong&gt;blank&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ENDPOINT&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With nowhere to forward to, the script had nothing meaningful to do beyond the generic network scaffolding — hence the suspiciously short, error-free-but-incomplete startup log.&lt;/p&gt;

&lt;p&gt;The fix: patch the metadata with the real internal IP of the Apigee runtime instance (retrieved earlier from the &lt;code&gt;instances&lt;/code&gt; API call in Layer 2), then force each VM to re-run its startup script without a full reboot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute instances add-metadata apigee-proxy-dgz1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;asia-south1-b &lt;span class="nt"&gt;--metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.51.204.98

gcloud compute instances add-metadata apigee-proxy-v4xb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;asia-south1-c &lt;span class="nt"&gt;--metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.51.204.98

gcloud compute ssh apigee-proxy-dgz1 &lt;span class="nt"&gt;--zone&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;asia-south1-b
&lt;span class="nb"&gt;sudo &lt;/span&gt;google_metadata_script_runner startup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checking the NAT table confirmed the fix landed:&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;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-v&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;Chain PREROUTING
DNAT  tcp dpt:443 to:10.51.204.98
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeated on the second instance, then the moment of truth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcloud compute backend-services get-health apigee-proxy-backend &lt;span class="nt"&gt;--global&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;healthState: HEALTHY   (apigee-proxy-dgz1)
healthState: HEALTHY   (apigee-proxy-v4xb)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://YOUR_HOSTNAME.nip.io/hello-world"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello, Guest!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Update: it relapsed two days later, and here's why
&lt;/h2&gt;

&lt;p&gt;Two days after publishing this, I opened the project to actually start practicing — and got the exact same 502 again. Same symptom, same 4/4-green setup wizard, same "everything reports ACTIVE" story. My first reaction was that something had regressed on its own, which felt alarming. It hadn't. The real explanation was a gap in the fix above that I'd missed the first time.&lt;/p&gt;

&lt;p&gt;Those &lt;code&gt;apigee-proxy-*&lt;/code&gt; forwarding VMs are provisioned as &lt;strong&gt;&lt;code&gt;--preemptible&lt;/code&gt;&lt;/strong&gt; — cheap, but reclaimable by Google at any time, sometimes within 24 hours. When Google reclaims one, the managed instance group automatically rebuilds a replacement, &lt;strong&gt;straight from the instance template&lt;/strong&gt; — not from whatever state the old VM happened to be in.&lt;/p&gt;

&lt;p&gt;Here's the gap: the &lt;code&gt;ENDPOINT&lt;/code&gt; fix above patches the metadata &lt;strong&gt;on the running instances&lt;/strong&gt; (&lt;code&gt;gcloud compute instances add-metadata ...&lt;/code&gt;), not on the template itself. That's a real, working fix — for as long as those exact VMs stay alive. But the template underneath (&lt;code&gt;apigee-proxy-asia-south1-fixed&lt;/code&gt;) still had &lt;code&gt;ENDPOINT&lt;/code&gt; blank the whole time. The moment either instance got preempted and silently replaced, the replacement came back from that same broken template — same blank &lt;code&gt;ENDPOINT&lt;/code&gt;, same 502, and from the outside it looked like the fix had mysteriously "worn off."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The durable fix is to bake the value into a new template revision, the same pattern as the service-account fix earlier:&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;gcloud compute instance-templates create apigee-proxy-asia-south1-fixed-v2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--machine-type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;e2-micro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image-project&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;debian-cloud &lt;span class="nt"&gt;--image-family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;debian-12 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--boot-disk-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20GB &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default &lt;span class="nt"&gt;--subnet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;asia-south1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https-server,apigee-proxy,gke-apigee-proxy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metadata&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;startup-script-url&lt;span class="o"&gt;=&lt;/span&gt;gs://apigee-5g-saas/apigee-envoy-proxy-release/latest/conf/startup-script.sh,ENDPOINT&lt;span class="o"&gt;=&lt;/span&gt;10.51.204.98 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;315668123660-compute@developer.gserviceaccount.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scopes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cloud-platform &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--preemptible&lt;/span&gt; &lt;span class="nt"&gt;--no-restart-on-failure&lt;/span&gt; &lt;span class="nt"&gt;--maintenance-policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TERMINATE

gcloud compute instance-groups managed set-instance-template apigee-proxy-asia-south1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;apigee-proxy-asia-south1-fixed-v2 &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;asia-south1

gcloud compute instance-groups managed rolling-action replace apigee-proxy-asia-south1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;asia-south1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once &lt;code&gt;ENDPOINT&lt;/code&gt; lives in the template rather than on individual instances, any future preemption or autoheal replacement inherits the correct value automatically — no more manual re-patching, ever, for this project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson generalizes beyond this one bug:&lt;/strong&gt; on any Compute Engine managed instance group, a fix applied via &lt;code&gt;add-metadata&lt;/code&gt; (or any live-instance edit) to a running VM is inherently temporary if that VM can be replaced by the group's own lifecycle — preemption, autohealing, or a manual scale event. The template is the actual source of truth for anything that needs to survive. Patch the instance for an immediate same-minute fix if you're in a hurry; patch the template for the fix that actually sticks.&lt;/p&gt;




&lt;h2&gt;
  
  
  The debugging methodology, distilled
&lt;/h2&gt;

&lt;p&gt;If you take nothing else from this post, take the layered verification approach — it's the reusable part:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don't trust a single "it says it's ready" signal.&lt;/strong&gt; Console checkmarks, API &lt;code&gt;state: ACTIVE&lt;/code&gt; fields, and actual traffic serving are three different questions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify each layer of the request path independently&lt;/strong&gt;, from the outside in:

&lt;ul&gt;
&lt;li&gt;TLS/DNS reachability (&lt;code&gt;curl -v&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Apigee org + runtime instance state (Management API)&lt;/li&gt;
&lt;li&gt;Environment attachment, deployment, and hostname binding (Management API)&lt;/li&gt;
&lt;li&gt;Load balancer backend health (&lt;code&gt;gcloud compute backend-services get-health&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The actual VM's boot log and listening ports (&lt;code&gt;get-serial-port-output&lt;/code&gt;, &lt;code&gt;ss -tlnp&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the boot log before assuming propagation delay.&lt;/strong&gt; A silent early exit with no errors can look identical to "still starting" from the outside — the only way to tell the difference is to read what actually ran.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand what's supposed to be running before declaring something broken.&lt;/strong&gt; My first assumption — that these VMs should have a listening process — was wrong; they're a &lt;code&gt;DNAT&lt;/code&gt; relay, not an application server. Getting that model right changed what "healthy" even meant to check for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When something is auto-provisioned by a wizard, verify its artifacts independently&lt;/strong&gt; rather than assuming the automation is infallible. In this case, two separate fields on an auto-generated instance template were wrong, and neither surfaced as an explicit error anywhere in the Console.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Should you expect this to happen to you?
&lt;/h2&gt;

&lt;p&gt;Almost certainly not on every setup — this felt like a genuine, if rare, defect in how the Apigee X evaluation wizard built one specific instance template in one specific run. Most eval org setups likely complete without ever seeing this. But if you do hit a 502 that survives 20+ minutes of patient waiting on an otherwise "fully provisioned" eval org, the checklist above will get you to ground truth fast — instead of guessing at propagation delays indefinitely.&lt;/p&gt;

&lt;p&gt;If you're setting up Apigee X eval for the first time: budget for the fact that a "Something went wrong" on the org-creation step is often transient and not worth panicking over, but a 502 that survives a proper wait, with all Apigee-side config verified clean, is a signal to go look at the Compute Engine layer underneath — specifically the &lt;code&gt;apigee-proxy-*&lt;/code&gt; instance group, its template's service account, and the &lt;code&gt;ENDPOINT&lt;/code&gt; metadata key.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Questions or hit something similar? Drop a comment — happy to compare notes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>gcp</category>
      <category>apigee</category>
      <category>cloud</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building Enterprise-Ready APIs on Google Cloud Apigee X: 5 Hands-On Projects</title>
      <dc:creator>Sunny JayaRaju</dc:creator>
      <pubDate>Wed, 12 Aug 2026 18:19:08 +0000</pubDate>
      <link>https://dev.to/sunnyjayaraju/building-enterprise-ready-apis-on-google-cloud-apigee-x-5-hands-on-projects-2bn5</link>
      <guid>https://dev.to/sunnyjayaraju/building-enterprise-ready-apis-on-google-cloud-apigee-x-5-hands-on-projects-2bn5</guid>
      <description>&lt;h2&gt;
  
  
  Why I built an Apigee Lab
&lt;/h2&gt;

&lt;p&gt;Enterprise APIs are the backbone of modern platforms. But reading about API gateways is one thing — &lt;strong&gt;building them, breaking them, and hardening them&lt;/strong&gt; is where you actually learn the craft.&lt;/p&gt;

&lt;p&gt;That's exactly what I set out to do with &lt;strong&gt;&lt;a href="https://github.com/SunnyJayaRaju/Apigee-Lab" rel="noopener noreferrer"&gt;Apigee-Lab&lt;/a&gt;&lt;/strong&gt; — an open monorepo of 5 enterprise-inspired projects built on &lt;strong&gt;Google Cloud Apigee X&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Each project is fully documented — with architecture diagrams, Mermaid sequence diagrams, deployment guides, and real policy XML. No "hello world" filler. Just production patterns you can actually study and reuse.&lt;/p&gt;

&lt;p&gt;Here's what's inside.&lt;/p&gt;




&lt;h2&gt;
  
  
  1️⃣ Weather Shield Gateway — API Security Layer
&lt;/h2&gt;

&lt;p&gt;A secure, optimized gateway for weather API traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it demonstrates:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JWT authentication&lt;/strong&gt; to protect routes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spike Arrest&lt;/strong&gt; to stop traffic surges and abuse&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response caching&lt;/strong&gt; to cut backend load and latency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monetization quotas&lt;/strong&gt; (Silver/Gold tiers)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /weather-lab?city=London
Authorization: Bearer &amp;lt;jwt&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Request flow: spike arrest → JWT verify → quota check → cache check → backend → transform (XML→JSON).&lt;/p&gt;

&lt;p&gt;Read the full architecture in &lt;a href="https://github.com/SunnyJayaRaju/Apigee-Lab/tree/main/Weather-Shield-Gateway" rel="noopener noreferrer"&gt;project docs&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2️⃣ Secure Bank Access — OAuth 2.0 Identity Flows
&lt;/h2&gt;

&lt;p&gt;A mock banking API that demonstrates clean &lt;strong&gt;identity and access management&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it demonstrates:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Client Credentials Flow&lt;/strong&gt; for machine-to-machine security&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conditional proxy flows&lt;/strong&gt; separating public (&lt;code&gt;/token&lt;/code&gt;) and protected (&lt;code&gt;/balance&lt;/code&gt;) routes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Products, Developers, and Apps&lt;/strong&gt; to enforce scopes and limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two-phase pattern: &lt;em&gt;the handshake&lt;/em&gt; (get a token) → &lt;em&gt;the access&lt;/em&gt; (verify and authorize).&lt;/p&gt;

&lt;p&gt;Read the full architecture in &lt;a href="https://github.com/SunnyJayaRaju/Apigee-Lab/tree/main/Secure-Bank-Access" rel="noopener noreferrer"&gt;project docs&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3️⃣ Retail Mesh Orchestrator — Microservices Aggregation
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;composition layer&lt;/strong&gt; that consolidates multiple backend responses into one payload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it demonstrates:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parallel Service Callouts&lt;/strong&gt; to cut round-trips&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript transformations&lt;/strong&gt; for JSON/XML normalization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fault-tolerant&lt;/strong&gt; routing during backend failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The classic &lt;em&gt;Backend-for-Frontend&lt;/em&gt; pattern, done with Apigee as the orchestrator.&lt;/p&gt;

&lt;p&gt;Read the full architecture in &lt;a href="https://github.com/SunnyJayaRaju/Apigee-Lab/tree/main/Retail-Mesh-Orchestrator" rel="noopener noreferrer"&gt;project docs&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4️⃣ Apigee DevOps Pipeline — Full CI/CD Automation
&lt;/h2&gt;

&lt;p&gt;Instead of deploying proxies manually, this project automates the delivery flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it demonstrates:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Actions&lt;/strong&gt; workflows that lint, package, and version proxies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;apigeelint&lt;/strong&gt; to catch structural and policy errors before merge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artifact staging&lt;/strong&gt; for controlled deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every push runs a quality gate: build structure → lint → package → upload. Bad code never ships.&lt;/p&gt;

&lt;p&gt;Read the full pipeline in &lt;a href="https://github.com/SunnyJayaRaju/Apigee-Lab/tree/main/Apigee-DevOps-Pipeline" rel="noopener noreferrer"&gt;project docs&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  5️⃣ Security Governance — Shared Flows
&lt;/h2&gt;

&lt;p&gt;A centralized security module for &lt;strong&gt;global policy enforcement&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it demonstrates:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reusable Shared Flows&lt;/strong&gt; to standardize logic across proxies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spike Arrests and Security Headers&lt;/strong&gt; enforced globally via &lt;code&gt;FlowCallout&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Governance rules &lt;strong&gt;decoupled&lt;/strong&gt; from individual proxy logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One shared flow, referenced by many proxies. Consistent security across the entire org.&lt;/p&gt;

&lt;p&gt;Read the full architecture in &lt;a href="https://github.com/SunnyJayaRaju/Apigee-Lab/tree/main/Shared-Flows-Governance" rel="noopener noreferrer"&gt;project docs&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I learned building these
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Security-first design:&lt;/strong&gt; Every proxy needs an identity layer, traffic control, and a governance story — not just routing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusability wins:&lt;/strong&gt; Shared Flows turn drift-prone duplicate policies into one maintained source of truth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD is non-negotiable:&lt;/strong&gt; Linting and packaging Apigee bundles in CI catches problems before they ever reach &lt;code&gt;eval&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composition is a superpower:&lt;/strong&gt; The gateway can be the orchestrator — merging multiple backends into one clean response.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;All projects are documented so you can deploy them to your own Apigee X environment (&lt;code&gt;eval&lt;/code&gt; org) and verify them with curl or Postman.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Explore the full lab →&lt;/strong&gt; &lt;a href="https://github.com/SunnyJayaRaju/Apigee-Lab" rel="noopener noreferrer"&gt;github.com/SunnyJayaRaju/Apigee-Lab&lt;/a&gt; 🌟&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you build APIs, gateways, or microservices — I'd love your feedback, issues, or a PR. Star it if it helps, and let's keep learning in public.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article is part of my public learning journey — you can follow more API engineering notes in my &lt;a href="https://github.com/SunnyJayaRaju/Curious-Explorer" rel="noopener noreferrer"&gt;Curious-Explorer knowledge base&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>apigee</category>
      <category>googlecloud</category>
      <category>api</category>
      <category>oauth2</category>
    </item>
  </channel>
</rss>
