<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Amazon CloudFront | John Nessime</title>
	<atom:link href="https://john-nessime.com/blog/tag/amazon-cloudfront/feed/" rel="self" type="application/rss+xml" />
	<link>https://john-nessime.com/blog/tag/amazon-cloudfront/</link>
	<description>Cloud, DevOps, Data &#38; AI — Built, Tested, Explained</description>
	<lastBuildDate>Mon, 14 Sep 2026 13:13:08 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.4</generator>

<image>
	<url>https://john-nessime.com/blog/wp-content/uploads/2026/07/cropped-jn-32x32.png</url>
	<title>Amazon CloudFront | John Nessime</title>
	<link>https://john-nessime.com/blog/tag/amazon-cloudfront/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>MediaConvert Cost Per Minute: How the Bill Is Built and How Jobs Fail</title>
		<link>https://john-nessime.com/blog/technical-guides/mediaconvert-cost-per-minute/</link>
		
		<dc:creator><![CDATA[John Nessime]]></dc:creator>
		<pubDate>Sat, 29 Aug 2026 09:00:00 +0000</pubDate>
				<category><![CDATA[Cloud Computing]]></category>
		<category><![CDATA[FinOps]]></category>
		<category><![CDATA[Technical Guides]]></category>
		<category><![CDATA[Adaptive Bitrate]]></category>
		<category><![CDATA[Amazon CloudFront]]></category>
		<category><![CDATA[Amazon S3]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[CloudWatch]]></category>
		<category><![CDATA[Cost Optimization]]></category>
		<category><![CDATA[Error Handling]]></category>
		<category><![CDATA[EventBridge]]></category>
		<category><![CDATA[Idempotency]]></category>
		<category><![CDATA[MediaConvert]]></category>
		<category><![CDATA[Normalized Minutes]]></category>
		<category><![CDATA[QVBR]]></category>
		<category><![CDATA[Reserved Transcode Slots]]></category>
		<category><![CDATA[Video Transcoding]]></category>
		<guid isPermaLink="false">https://john-nessime.com/blog/?p=542</guid>

					<description><![CDATA[<p>One checkbox can reprice every output in a MediaConvert job. A working breakdown of how the per-minute charge is assembled from normalized minutes and per-output billing, why failed jobs are free but still expensive, and how to build retry and recovery that classifies before it retries.</p>
<p>The post <a href="https://john-nessime.com/blog/technical-guides/mediaconvert-cost-per-minute/">MediaConvert Cost Per Minute: How the Bill Is Built and How Jobs Fail</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">A turnaround complaint lands: the promo team&#8217;s masters take too long to appear in the CMS. Someone opens the job template, finds a setting called accelerated transcoding, ticks it, and moves on. Turnaround improves. Nobody mentions it again.</p>



<p class="wp-block-paragraph">The invoice is where it surfaces, and not only on the big outputs. The 240p ladder rung went up. The audio-only rendition went up. Every output in every job that template touches went up, including the ones that never needed the feature.</p>



<p class="wp-block-paragraph">That is not a billing bug. It is the documented rule working as designed, and it is the most common reason a bill moves without anyone changing how much video goes through it. Understanding <strong>MediaConvert cost per minute</strong> starts with accepting that the unit you pay for is not a job, not a source file, and not a minute of compute.</p>



<p class="wp-block-paragraph">What follows is how that charge is assembled, what happens to it when a job fails, and how to build recovery around AWS Elemental MediaConvert. Rates are deliberately absent; AWS changes them and they vary by region. The mechanism is what catches people.</p>



<h2 class="wp-block-heading">How MediaConvert cost per minute is actually assembled</h2>



<p class="wp-block-paragraph">Three rules stack on top of each other. Miss one and your cost model is wrong by a multiple, not a rounding error.</p>



<h3 class="wp-block-heading">You pay per output, not per job</h3>



<p class="wp-block-paragraph">An eight-rung ABR ladder plus an audio-only rendition is nine billable items, each charged for its own duration. AWS bills the duration of the finished assets, not the transcoding time, in hundredth-of-a-minute increments. A ten-minute source producing nine outputs bills as ninety minutes of output.</p>



<p class="wp-block-paragraph">Ladder design is therefore a cost decision, not only a quality one. The bottom rungs are not cheap because they are small. They cost a full pass of the source duration at whatever multiplier their settings attract, whether anyone streams them or not.</p>



<h3 class="wp-block-heading">Normalized minutes, and why multipliers compound</h3>



<p class="wp-block-paragraph">There is no flat rate per output minute. MediaConvert converts each output into normalized minutes, a unit that scales with the work the encode demanded. Resolution, frame rate, codec and number of encoding passes each carry a multiplier, and those multipliers combine. Single-pass to multi-pass is one. 30fps to 60fps is another. Do both to a UHD rung and you have stacked three.</p>



<p class="wp-block-paragraph">Volume discounts then apply to your accumulated normalized minutes for the month, per region, with basic-tier and professional-tier usage accruing separately toward their own thresholds. Two consequences: splitting a workload across regions splits the accrual and can leave you paying a higher effective rate in both, and under consolidated billing in AWS Organizations usage accrues across accounts, so a per-account model understates the discount you actually get.</p>



<h3 class="wp-block-heading">The job-wide setting that reprices everything</h3>



<p class="wp-block-paragraph">Back to the opening. Each output is assessed for its billing tier separately, but only when the job has no job-wide professional-tier feature enabled. Turn one on, accelerated transcoding being the usual culprit, and every output in that job bills at pro-tier rates. An SD rung that would have billed at basic tier gets repriced because a different output in the same job needed to finish faster.</p>



<p class="wp-block-paragraph">If a subset of content genuinely needs acceleration, split it out. Long, visually complex assets get a template with the feature; routine web renditions get one without. Two templates is less tidy than one, and considerably cheaper than a single checkbox repricing your catalogue.</p>



<h2 class="wp-block-heading">Failed jobs are free, which changes how you should retry</h2>



<p class="wp-block-paragraph">MediaConvert charges only when a job reaches COMPLETED. Jobs ending in ERROR are not billed. That sounds like it removes the urgency from failure handling. It does the opposite.</p>



<p class="wp-block-paragraph">A job that fails on output seven of nine may still have written outputs one through six to S3. Those partial artefacts cost storage, and if a naive retry writes to the same prefix you get a mix of old and new segments that a player will serve quite happily until someone notices the ladder is inconsistent. Free at the transcode line, expensive everywhere downstream.</p>



<p class="wp-block-paragraph">&#8220;Not billed&#8221; also makes blind retries feel harmless, so people write them. A loop against a deterministic failure, a corrupt source or a settings mismatch, will never succeed. It burns concurrency paying jobs need and produces identical alerts until the team ignores the channel. The service already retries some conditions itself and exposes a retry count, so read that field before adding an attempt of your own.</p>



<h2 class="wp-block-heading">The queue model is a throughput control, not a cost control</h2>



<p class="wp-block-paragraph">On-demand queues bill per output minute. How many jobs run at once is governed by a service quota for concurrent jobs per account in the region, a quota per individual queue, and a concurrent-jobs setting on each queue. Creating a second queue does not conjure capacity out of the account; you raise it through Service Quotas. Jobs sitting in SUBMITTED rather than moving to PROGRESSING is the signal you have hit the ceiling.</p>



<p class="wp-block-paragraph">Reserved queues work on a different axis. You buy reserved transcode slots against a twelve-month commitment and pay for that capacity regardless of use. Each slot has its own dedicated compute, so one slot runs a given job just as fast as ten would. More slots means more jobs in parallel, not faster jobs. Three things about the commitment deserve a second read before anyone clicks agree:</p>



<ul class="wp-block-list">
<li>You cannot cancel it, and you cannot decrease the slot count.</li>

<li>Adding capacity does not extend the existing term. It starts a fresh twelve-month commitment for the larger count, from the day you add it.</li>

<li>When the plan expires, the queue persists and keeps accepting jobs. MediaConvert just does not run them.</li>
</ul>



<p class="wp-block-paragraph">That last one is the invisible failure in this whole topic. Your submission code gets a job ID back. The API call succeeded. Nothing errors. The jobs simply never progress, and unless you alarm on queue standby time or on the gap between jobs submitted and completed, the first person to notice is whoever asks why the library stopped updating. Set auto-renew deliberately, either way, and diary the term end date.</p>



<p class="wp-block-paragraph">Queue hopping is the pressure valve. Give a job a wait time and a destination queue, and if it is still waiting when the timer expires it moves. The usual shape is reserved to on-demand during a spike. Billing follows the queue the job actually runs in, so a hopped job becomes on-demand spend.</p>



<pre class="wp-block-code"><code>{
  "Settings": { "OutputGroups": [], "Inputs": [] },
  "HopDestinations": [
    {
      "WaitMinutes": 10,
      "Queue": "arn:aws:mediaconvert:us-west-2:111122223333:queues/ondemandqueue",
      "Priority": 25
    }
  ]
}</code></pre>



<p class="wp-block-paragraph">WaitMinutes accepts 1 to 4320. Priority runs from -50 to 50 and defaults to 0, and the optional priority under HopDestinations lets a hopped job land higher or lower in the destination queue than it sat in the original. Hopping is a cost decision as much as a throughput one: it converts capacity you have already paid for into incremental on-demand spend.</p>



<h2 class="wp-block-heading">The failure families that actually bite</h2>



<p class="wp-block-paragraph">MediaConvert publishes numbered error codes with documented causes. Rather than reproduce the table, group them by what you have to do about them, because the correct response differs sharply between groups.</p>



<h3 class="wp-block-heading">Access failures</h3>



<p class="wp-block-paragraph">The service could not open an input or output location. Causes cluster around IAM permissions on the role the job runs under, cross-account bucket policies when the source lives elsewhere, keys that do not exist, and files whose headers cannot be read. Retry these once with a delay, because uploads still in flight are a real condition. If the second attempt fails identically, it is configuration, and no amount of retrying fixes configuration.</p>



<h3 class="wp-block-heading">Settings failures that only appear on some inputs</h3>



<p class="wp-block-paragraph">The nastiest to diagnose, because the template has been fine for the last thousand assets. An encoder initialisation failure on one rung, triggered by an unusual source aspect ratio or frame rate meeting a fixed constraint in the template, is the classic shape. Never retry these; the job fails identically forever. The fix is usually to loosen the constraint rather than pin it harder, because letting the service derive a value the source cannot satisfy removes the whole class of failure.</p>



<h3 class="wp-block-heading">Warnings, which are not errors and are easy to miss</h3>



<p class="wp-block-paragraph">MediaConvert also returns warning codes for problems that do not stop the job. It completes, you get billed, and something about the output is wrong. Video padding inserted to keep audio and video aligned, black frames carried through from the source, a QVBR score well below the rest of the catalogue: none raise an error, all are visible in metrics. Alert only on ERROR and this entire family is invisible to you.</p>



<h3 class="wp-block-heading">Pinned engine versions that quietly expire</h3>



<p class="wp-block-paragraph">MediaConvert groups its releases into job engine versions so you can hold production on one while validating another. Access is requested through AWS Support rather than open to every account, so check whether it applies to you first. The behaviour worth knowing is expiry: requesting an expired version does not fail. The job runs as though no version had been specified, and the field reporting the version used comes back empty. An invalid version, by contrast, returns a 400 and creates no job. The failure mode of pinning is silence.</p>



<h2 class="wp-block-heading">Building failure recovery that does not cost you twice</h2>



<p class="wp-block-paragraph">The pattern that holds up is event-driven, classified and idempotent. In order:</p>



<ol class="wp-block-list">
<li>Subscribe to job state changes in EventBridge rather than polling. The ERROR event carries the code and message in its detail block.</li>

<li>Route by error code, not by status: one target for retryable access conditions, another for deterministic settings failures that need a human, and a suppression path for anything already seen on that asset.</li>

<li>Write a retry&#8217;s outputs to a new prefix, or clear the old one first. Never let attempt two land beside attempt one&#8217;s partial results.</li>

<li>Send a client request token on job creation. Reusing the same token within a minute of a successful request returns the original job instead of creating a duplicate, which protects you when your orchestration retries a create call whose response it never saw.</li>

<li>Cap attempts and land the remainder in a dead letter queue with the original event attached. A failure nobody can see is a failure nobody fixes.</li>

<li>Carry your own identifiers through user metadata on the job, so the returning event joins back to the asset without a lookup table.</li>
</ol>



<pre class="wp-block-code"><code>{
  "source": ["aws.mediaconvert"],
  "detail-type": ["MediaConvert Job State Change"],
  "detail": {
    "status": ["ERROR"]
  }
}</code></pre>



<p class="wp-block-paragraph">The matching event delivers a detail object containing the queue ARN, job ID, status, an errorCode and an errorMessage. Route on errorCode. That one decision is the difference between a recovery system and a retry loop.</p>



<h2 class="wp-block-heading">Measuring what you spend before the invoice tells you</h2>



<p class="wp-block-paragraph">MediaConvert sends metrics to CloudWatch at the end of every job, several mapping directly onto the billing dimensions. The ones I reach for first:</p>



<ul class="wp-block-list">
<li><code>SDOutputDuration</code>, <code>HDOutputDuration</code>, <code>UHDOutputDuration</code>, <code>8KOutputDuration</code> and <code>AudioOutputDuration</code>, in milliseconds per queue. Summed over a month, that is billable output volume by resolution class, days ahead of Cost Explorer.</li>

<li><code>JobErrorsByCode</code>, counting jobs that failed with a particular code. Build the dashboard on this one; it shows which failure family is growing.</li>

<li><code>StandbyTime</code>, how long a job waits before processing starts. Alarm on it and you catch backlogs, and you catch an expired reserved plan.</li>

<li><code>JobsSubmittedCount</code> against <code>JobsCompletedCount</code>. A widening gap is the earliest honest signal something upstream is broken.</li>

<li><code>QVBRAvgQualityLowBitrate</code> and <code>QVBRMinQualityHighBitrate</code>, for quality regressions that never raise an error.</li>
</ul>



<p class="wp-block-paragraph">For attribution rather than volume, enable cost allocation tags and set the billing tags source on your jobs so per-output costs land against a tag you chose. Pushing that into a wider FinOps view through Vantage, CloudZero or a Grafana Cloud dashboard fed from CloudWatch gets you per-title cost without building the aggregation yourself. Either way, these metrics let you model MediaConvert cost per minute continuously instead of discovering it monthly.</p>



<p class="wp-block-paragraph">Transcoding is only part of the line. Storage for sources and renditions in S3, and egress through Amazon CloudFront or whichever CDN fronts delivery, are separate charges and often the larger ones. A cost model that stops at the encoder will be wrong.</p>



<h2 class="wp-block-heading">Troubleshooting a failed job</h2>



<ol class="wp-block-list">
<li>Get the error code first. Everything else follows from which family it belongs to.</li>

<li>Ask whether other assets in the same bucket succeeded in the same window. Yes points at the file, no points at permissions or configuration.</li>

<li>For a settings failure, bisect the job. Strip it to a single video output, confirm it runs, then add outputs and features back one at a time. Tedious, and faster than reasoning about it.</li>

<li>Check warnings on jobs that completed nearby. Warnings often precede errors from the same root cause.</li>

<li>If nothing ran at all, look at the queue before the job: plan status on a reserved queue, standby time on an on-demand one.</li>
</ol>



<h2 class="wp-block-heading">Common mistakes</h2>



<ul class="wp-block-list">
<li>Modelling cost from source duration instead of total output duration across the ladder.</li>

<li>Enabling a job-wide professional-tier feature in a shared template and repricing every output in it.</li>

<li>Creating extra on-demand queues for throughput when the account quota is the real ceiling.</li>

<li>Buying reserved slots off a peak-week estimate and paying for idle capacity the other fifty-one.</li>

<li>Retrying every failure identically, including the deterministic ones that cannot succeed.</li>

<li>Alerting on ERROR only, and never looking at warnings or quality scores.</li>
</ul>



<h2 class="wp-block-heading">Best practices worth the effort</h2>



<ul class="wp-block-list">
<li>Separate templates by cost profile rather than content type. Anything with a job-wide pro-tier feature gets its own.</li>

<li>Audit the ladder against playback data at least once. Rungs nobody streams are pure cost.</li>

<li>Keep a workload in one region so volume discounts accrue against a single pool.</li>

<li>Run a representative job on an on-demand queue with the reserved simulation setting enabled before committing to slots.</li>

<li>Use job priority instead of extra queues when the goal is for urgent work to jump ahead.</li>

<li>Derive client request tokens from the asset, not from a timestamp, so retries are genuinely idempotent.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Frequently asked questions</h2>



<h3 class="wp-block-heading">Do I pay for MediaConvert jobs that fail?</h3>



<p class="wp-block-paragraph">No. Billing applies only to jobs reaching COMPLETED status, so a job ending in ERROR is not charged. You may still be paying for partial outputs that reached S3 before the failure, and the storage they occupy until something cleans them up.</p>



<h3 class="wp-block-heading">What is a normalized minute?</h3>



<p class="wp-block-paragraph">The billing unit MediaConvert uses so a minute of demanding output counts for more than a minute of simple output. Resolution, frame rate, codec and encoding passes each contribute a multiplier, and those combine into the normalized total your volume discount tier is measured against.</p>



<h3 class="wp-block-heading">Is a reserved queue cheaper than on demand?</h3>



<p class="wp-block-paragraph">Only if utilisation is genuinely steady. You pay for reserved transcode slots regardless of use, across a twelve-month commitment you cannot cancel or shrink. Bursty catalogues usually do better on demand, where volume discounts apply automatically as monthly usage climbs. Reserved pricing also excludes some features, so confirm everything your templates use is available there before committing.</p>



<h3 class="wp-block-heading">Why are my jobs stuck in SUBMITTED?</h3>



<p class="wp-block-paragraph">On an on-demand queue, it is already running its maximum number of concurrent jobs; raise the quota through Service Quotas rather than adding queues. On a reserved queue, check whether the pricing plan has expired, because an expired queue keeps accepting jobs and never processes them.</p>



<h3 class="wp-block-heading">Does adding queues give me more transcoding capacity?</h3>



<p class="wp-block-paragraph">No. Concurrency is bounded by account-level and queue-level quotas in the region. Extra queues isolate workloads from each other, for instance keeping one client&#8217;s jobs from starving another&#8217;s. Capacity increases come from Service Quotas.</p>



<h3 class="wp-block-heading">How do I stop one setting from repricing my whole job?</h3>



<p class="wp-block-paragraph">Split the job. Job-wide professional-tier features apply their tier to every output, so isolate the assets that need them into their own template and keep routine renditions in a template with no job-wide pro-tier features enabled.</p>



<h3 class="wp-block-heading">Should I run FFmpeg on my own servers instead?</h3>



<p class="wp-block-paragraph">Sometimes. Predictable, modest volume on dedicated hardware from a provider like Contabo or InterServer can undercut per-minute pricing, and you get exact control over encoder flags. What you take on is capacity planning, codec licensing questions, and building the retry and observability machinery the managed service hands you for free. That trade favours MediaConvert for spiky workloads and broadcast-grade features, and self-hosting for steady, simple ladders where an engineer already owns the pipeline.</p>



<h2 class="wp-block-heading">The one thing to take away</h2>



<p class="wp-block-paragraph">MediaConvert cost per minute is not a rate you look up. It is a product: output count, times duration, times a stack of feature multipliers, with a single job-wide setting able to reprice the lot. Get that model into whatever you plan from and the bill stops being a monthly surprise.</p>



<p class="wp-block-paragraph">On the failure side, the codes are documented and the events are structured, so there is no excuse for a retry loop that cannot tell a permissions problem from a settings problem. Classify before you retry. And put an alarm on queue standby time today, because the failure that costs you most is the one where nothing errors at all.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Need a second pair of eyes on your transcoding pipeline?</h2>



<p class="wp-block-paragraph">I work with teams running video-on-demand pipelines on AWS, usually at the point where the bill or the failure rate has become someone&#8217;s problem. What I actually help with here:</p>



<ul class="wp-block-list">
<li>Auditing job templates and ABR ladders for outputs and multipliers costing more than they return.</li>

<li>Building a cost model from your CloudWatch output-duration metrics so you can forecast rather than react.</li>

<li>Designing EventBridge-driven failure handling that classifies by error code and retries only what can succeed.</li>

<li>Working the reserved versus on-demand decision against your real utilisation, including whether queue hopping earns its keep.</li>

<li>Debugging settings failures that only hit certain sources, and loosening the constraints causing them.</li>

<li>Wiring transcoding, S3 lifecycle and CDN delivery into one cost and reliability picture instead of three.</li>
</ul>



<p class="wp-block-paragraph">Send me a job template, an error code, or a month of output-duration metrics and I will tell you what I see.</p>



<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="https://www.upwork.com/freelancers/~01f15a912ad84a6620" target="_blank" rel="noreferrer noopener">Work with me on Upwork</a></div>
</div>
<p>The post <a href="https://john-nessime.com/blog/technical-guides/mediaconvert-cost-per-minute/">MediaConvert Cost Per Minute: How the Bill Is Built and How Jobs Fail</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Choosing an AWS Region for a MENA Client: Latency Is the Last Question</title>
		<link>https://john-nessime.com/blog/cloud-computing/aws-region-for-mena/</link>
		
		<dc:creator><![CDATA[John Nessime]]></dc:creator>
		<pubDate>Wed, 26 Aug 2026 09:00:00 +0000</pubDate>
				<category><![CDATA[Cloud Computing]]></category>
		<category><![CDATA[Compliance]]></category>
		<category><![CDATA[Amazon CloudFront]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[AWS Control Tower]]></category>
		<category><![CDATA[AWS Regions]]></category>
		<category><![CDATA[Cross-Border Data Transfer]]></category>
		<category><![CDATA[Data Residency]]></category>
		<category><![CDATA[Data Transfer Costs]]></category>
		<category><![CDATA[Disaster Recovery]]></category>
		<category><![CDATA[MENA]]></category>
		<category><![CDATA[Multi-AZ]]></category>
		<category><![CDATA[Parameter Store]]></category>
		<category><![CDATA[PDPL]]></category>
		<category><![CDATA[Region Selection]]></category>
		<category><![CDATA[Service Control Policies]]></category>
		<guid isPermaLink="false">https://john-nessime.com/blog/?p=548</guid>

					<description><![CDATA[<p>Latency is the constraint everyone reaches for first when choosing an AWS region for MENA clients, and it is almost always the one that matters least. Residency law eliminates candidates, service availability eliminates more, and latency only picks between whatever survives. A practical walkthrough of that filter, with the commands to check service availability and region opt-in status programmatically, the guardrail that keeps the decision true, and the things that break in the first few weeks after you commit.</p>
<p>The post <a href="https://john-nessime.com/blog/cloud-computing/aws-region-for-mena/">Choosing an AWS Region for a MENA Client: Latency Is the Last Question</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Someone says it on the kickoff call, usually in the first ten minutes. &#8220;Bahrain, right? It&#8217;s the closest one.&#8221; Everybody nods, the Terraform gets written, and six weeks later the client&#8217;s lawyer asks a question nobody prepared for: where exactly is the personal data of our Egyptian users sitting right now?</p>



<p class="wp-block-paragraph">That is the moment the region choice stops being a performance question and turns into an expensive one. Choosing an AWS region for MENA workloads looks like a latency problem and almost never is. Latency is the constraint you can measure in an afternoon and fix with a CDN. Residency is the constraint that can force you to rebuild.</p>



<p class="wp-block-paragraph">This post walks through the order I work in when picking an AWS region for MENA clients: which constraints bind hard, which ones bend, how to check service availability programmatically instead of trusting a table someone screenshotted, and what tends to break after the decision is made.</p>



<h2 class="wp-block-heading">Work the constraints in the order they bind</h2>



<p class="wp-block-paragraph">Region selection is a filter, not a scoring exercise. You are not hunting for the best average across four categories. You are eliminating regions that cannot work, in the order that matters, then picking from whatever survives.</p>



<p class="wp-block-paragraph">The order I use:</p>



<ol class="wp-block-list">
<li><strong>Residency and regulatory obligations.</strong> Not engineerable. Either the law permits the data to sit there or it does not.</li>



<li><strong>Service availability.</strong> Not engineerable cheaply. If the region lacks something your design needs, you change the design or change the region.</li>



<li><strong>Resilience and blast radius.</strong> Partly engineerable, bounded by which regions are near enough to pair with.</li>



<li><strong>Latency.</strong> Very engineerable. Edge caching, connection reuse and smaller payloads beat a few hundred kilometres of fibre most of the time.</li>
</ol>



<p class="wp-block-paragraph">Most guides run this list backwards, because latency is the fun part and the one with a nice map. Running it backwards is how you end up with a beautifully fast architecture that legal makes you move.</p>



<h2 class="wp-block-heading">Constraint one: residency, and the region that does not exist</h2>



<p class="wp-block-paragraph">Here is the thing that catches people who have only worked with EU or US clients. In Europe you can usually satisfy a residency requirement by picking a region in the right country. Across MENA, that option frequently is not on the table, because AWS does not have an infrastructure region in most MENA countries.</p>



<p class="wp-block-paragraph">Check the current region table before you promise anything. The list changes, and the geography column is what a regulator cares about, not the region&#8217;s marketing name. The programmatic version:</p>



<pre class="wp-block-code"><code># List every region your account can see, with opt-in status
aws account list-regions 
  --query 'Regions[*].[RegionName,RegionOptStatus]' 
  --output table

# Confirm the physical country AWS records for a region
aws ssm get-parameters-by-path 
  --path /aws/service/global-infrastructure/regions/me-central-1 
  --region us-east-1 
  --output table</code></pre>



<p class="wp-block-paragraph">That second command returns the metadata AWS publishes for the region, including a geolocation country code. Better source than a blog post, this one included, because it comes from the same data the console uses.</p>



<p class="wp-block-paragraph">One caution on the path itself: the <code>global-infrastructure</code> parameter tree is only queryable from a subset of regions, so pin <code>--region us-east-1</code> on those calls rather than assuming your default profile region will work.</p>



<h3 class="wp-block-heading">Announced is not the same as available</h3>



<p class="wp-block-paragraph">AWS announces regions well ahead of general availability, and those announcements get quoted back at you by sales teams and clients as though the capacity already exists. Treat an announcement as a roadmap item. If the region code does not appear in the table your account can query, you cannot deploy to it, and you certainly cannot write a residency clause around it. That gap matters in the Gulf specifically, where announced-but-not-yet-open regions have been part of the conversation for a while.</p>



<h3 class="wp-block-heading">Residency and localisation are not the same requirement</h3>



<p class="wp-block-paragraph">The word &#8220;residency&#8221; gets used for at least three different obligations, and clients rarely distinguish between them. Before you look at a single region, get the client&#8217;s counsel to tell you which one applies:</p>



<ul class="wp-block-list">
<li><strong>Hard localisation.</strong> The data must sit in-country, full stop. With no region in that country, no AWS region satisfies this and you are into a local provider, on-premises kit, or a hybrid split.</li>



<li><strong>Conditional transfer.</strong> Data may leave if a condition is met: regulator authorisation, an adequacy finding, explicit consent, a specific contractual basis. This is the common case, and it is more paperwork problem than architecture problem.</li>



<li><strong>Sector rules on top.</strong> Finance, health and government workloads usually add a supervisor with its own cloud rules, which may name approved jurisdictions or require notice before you move anything.</li>
</ul>



<p class="wp-block-paragraph">Egypt is the clearest illustration of why this matters to an engineer. Its Personal Data Protection Law and the executive regulations under it set up a regime where moving personal data out of the country generally needs a licence or permit from the supervisory authority, rather than a self-assessment you document internally and move on from. There is no AWS region in Egypt. So for an Egyptian client with personal data in scope, picking a region does not answer the residency question. It restates it: which foreign region will we be authorised to transfer into, and who is filing that application?</p>



<p class="wp-block-paragraph">I am not a lawyer and neither are you. What you can do is refuse to let the region be chosen before that answer exists, because picking first and asking later is how the rebuild happens.</p>



<h3 class="wp-block-heading">Enforce the decision in the account, not in the runbook</h3>



<p class="wp-block-paragraph">Once the approved regions are settled, stop treating them as a convention. A convention survives until someone opens the console in the wrong region at eleven at night.</p>



<p class="wp-block-paragraph">The mechanism is the <code>aws:RequestedRegion</code> condition key, applied either as an IAM policy or, better, as a Service Control Policy across the organisation so it cannot be edited away by an account admin. AWS Control Tower ships a managed version of the same idea as its Region deny control if you are running a landing zone.</p>



<pre class="wp-block-code"><code>{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyOutsideApprovedRegions",
      "Effect": "Deny",
      "NotAction": [
        "cloudfront:*",
        "iam:*",
        "organizations:*",
        "route53:*",
        "support:*"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:RequestedRegion": [
            "me-central-1"
          ]
        }
      }
    }
  ]
}</code></pre>



<p class="wp-block-paragraph">The <code>NotAction</code> block is the part people get wrong, and it is worth understanding rather than copying. Several AWS services are global with a single control-plane endpoint that lives in <code>us-east-1</code>. A blanket deny on everything outside your approved region catches those calls too, and locks you out of IAM. Exempting them by action prefix is what keeps the policy usable. Extend the list to match what the client actually runs, and test it in a sandbox account before it goes anywhere near production.</p>



<p class="wp-block-paragraph">One wrinkle worth checking: managed inference services can route requests to other regions within a geography to absorb load. Sensible availability feature, simultaneous residency problem. If generative AI is in scope, confirm the routing behaviour explicitly rather than assuming a request to a regional endpoint stays there.</p>



<h2 class="wp-block-heading">Constraint two: service availability, where the nasty surprises live</h2>



<p class="wp-block-paragraph">Not every AWS service exists in every region, and the gap is widest in the newest and smallest ones. This is the failure mode that bites hardest, because it is invisible during design and obvious during implementation. The diagram gets approved, the Terraform plan runs, and one resource type throws an error that reads like a permissions problem but is really a &#8220;this does not exist here&#8221; problem.</p>



<p class="wp-block-paragraph">Do not check this against the services page by eye. Check it in a script, at design time, against the same source AWS uses:</p>



<pre class="wp-block-code"><code># Is a given service available in a given region?
# An empty result means no.
aws ssm get-parameters-by-path 
  --path /aws/service/global-infrastructure/services/athena/regions/me-central-1 
  --region us-east-1 
  --output text

# Every service AWS records for a region
aws ssm get-parameters-by-path 
  --path /aws/service/global-infrastructure/regions/me-central-1/services 
  --region us-east-1 
  --query 'Parameters[].Name' 
  --output text

# The canonical list of service identifiers to test against
aws ssm get-parameters-by-path 
  --path /aws/service/global-infrastructure/services 
  --region us-east-1 
  --query 'Parameters[].Name | sort(@)' 
  --output text</code></pre>



<p class="wp-block-paragraph">The identifiers in that tree are short API names, not display names, which trips people up. Pull the full list and grep it rather than guessing how a service name is spelled.</p>



<p class="wp-block-paragraph">Two refinements make this useful rather than a party trick:</p>



<ul class="wp-block-list">
<li><strong>Write the check as a matrix, not a lookup.</strong> Put the services your design depends on in a file, loop over the candidate regions, print a grid. You want the gaps visible before any infrastructure code exists.</li>



<li><strong>Presence is not feature parity.</strong> The parameter tree says a service exists in a region. It says nothing about which instance families, engine versions, storage classes or model options are offered there, and newer regions routinely carry a narrower menu. Where the specific variant matters, check that service&#8217;s own regional documentation too.</li>
</ul>



<h3 class="wp-block-heading">Opt-in regions and the quiet failures they cause</h3>



<p class="wp-block-paragraph">Every AWS region introduced in recent years, including all of the Middle East regions, is disabled by default and must be enabled on the account before you can use it. Small piece of admin, disproportionate number of confusing failures.</p>



<p class="wp-block-paragraph">The failure shape is always the same: it works in your account and fails in the client&#8217;s, or works in the management account and fails in a member account created last week. Enablement is per-account and not instantaneous, so any automated account vending process has to handle it.</p>



<pre class="wp-block-code"><code>aws account get-region-opt-status --region-name me-central-1</code></pre>



<p class="wp-block-paragraph">Run that against every account in scope as a pre-flight check, not as a debugging step after a pipeline fails.</p>



<h2 class="wp-block-heading">Constraint three: resilience and concentration risk</h2>



<p class="wp-block-paragraph">Every AWS region carries at least three Availability Zones, and the Middle East regions are three-AZ regions. That is enough for a properly designed multi-AZ deployment, and multi-AZ should be your default for anything the client would notice going down.</p>



<p class="wp-block-paragraph">The subtlety is what you pair the region with. Cross-region DR inside MENA pairs two regions that sit closer together than a European or North American pair would, and that share more exposure to the same events: a cut on a common submarine route, a regulatory action, a physical incident. That is not an argument against a MENA pair. It is an argument for saying plainly what the DR plan protects against, because a nearby second region covers a regional AWS failure and not much wider than that.</p>



<p class="wp-block-paragraph">Two practical points on AZ handling that catch people:</p>



<ul class="wp-block-list">
<li><strong>AZ names are per-account.</strong> The letter suffix on a zone name does not map to the same physical zone across two different accounts. If you are coordinating placement between accounts, use the zone ID rather than the name. <code>aws ec2 describe-availability-zones --region me-central-1</code> returns both.</li>



<li><strong>Capacity is not uniform.</strong> Smaller regions have less headroom in specific instance families, and you can hit an insufficient-capacity error on a launch that would have succeeded in a large region. Design for instance-family flexibility, and if the workload is steady state, talk to the client about a capacity commitment rather than discovering the ceiling during a launch window.</li>
</ul>



<h2 class="wp-block-heading">Constraint four: latency, finally</h2>



<p class="wp-block-paragraph">By the time residency and service availability have done their work you often have one candidate left, and the latency question answers itself. When you do still have a choice, measure properly.</p>



<p class="wp-block-paragraph">Measuring from your own machine tells you about your own connection, which is not what you care about. Regional routing is often counter-intuitive, and traffic between two Gulf cities does not necessarily take the short path. You want a measurement from the network the users are actually on. Options, in rough order of how much I trust them:</p>



<ul class="wp-block-list">
<li><strong>Real user monitoring on an existing property.</strong> If the client already has a site or app with users in that market, instrument it and read the field data. Nothing synthetic beats this.</li>



<li><strong>A small instance in each candidate region, probed from the target country.</strong> Cheap, quick, measures the actual path rather than a claim about it.</li>



<li><strong>A commercial VPN endpoint in the target country</strong> when you have no presence there. NordVPN or Surfshark will sanity-check the relative ordering of two regions, but treat the absolute numbers with suspicion: you are measuring a path through the provider&#8217;s network, not a real user&#8217;s path.</li>



<li><strong>Published latency matrices.</strong> Fine for a first pass. Not evidence.</li>
</ul>



<h3 class="wp-block-heading">Split the problem before you optimise the region</h3>



<p class="wp-block-paragraph">Most of what users experience as slowness is not round-trip time to the origin. It is uncached static assets, chatty APIs, TLS handshakes on cold connections, and payloads bigger than they need to be. A CDN fixes a large share of that whatever region the origin sits in, and the edge network is far denser across MENA than the region footprint is. Amazon CloudFront is the obvious in-stack choice; Cloudflare is worth a look when the client wants DNS, WAF and edge in one place, or when the origin is not on AWS at all.</p>



<p class="wp-block-paragraph">The caveat matters though. Caching at the edge puts copies of responses in edge locations worldwide. For static assets that is fine. For authenticated responses carrying personal data it is a residency question of its own, and the answer is usually to scope caching tightly and leave dynamic personal responses uncached.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">What breaks after you have chosen</h2>



<p class="wp-block-paragraph">A short troubleshooting list for the first few weeks, drawn from the things that reliably go wrong.</p>



<ul class="wp-block-list">
<li><strong>Access denied on a service you definitely have permissions for.</strong> Check the region deny SCP first, then check whether the call is hitting a global endpoint you forgot to exempt. The error will not tell you an SCP is responsible.</li>



<li><strong>A certificate that will not attach to your distribution.</strong> Certificates used with CloudFront must be requested in <code>us-east-1</code> regardless of where the origin lives. This looks like a residency violation to a nervous auditor and is not one, since a certificate holds no personal data. Have the explanation ready.</li>



<li><strong>Terraform plans that differ between engineers.</strong> Someone has a different default region in their profile. Pin it in the provider block and stop relying on environment configuration.</li>



<li><strong>An unexpected data transfer line on the bill.</strong> Cross-region traffic is charged, and replication, backups and cross-region reads all generate it quietly. Get cost allocation tags working before the first full month closes so you can attribute it rather than argue about it.</li>



<li><strong>A service that exists but behaves differently.</strong> Quotas start lower in newer regions and some features lag. Check the quota console before assuming your code is wrong.</li>
</ul>



<h2 class="wp-block-heading">Common mistakes</h2>



<ul class="wp-block-list">
<li>Choosing the region on the kickoff call, before anyone has asked counsel what the transfer basis is.</li>



<li>Treating proximity as the residency answer. A region in a neighbouring country is still a cross-border transfer.</li>



<li>Copying a region deny policy from a blog without adapting the global-service exemptions to the client&#8217;s real service inventory.</li>



<li>Checking service availability once at the start and never again as the design grows new components.</li>



<li>Forgetting the observability stack. Logs and metrics carry personal data more often than people expect, and a monitoring platform hosted in another jurisdiction is a transfer. Grafana Cloud and the AWS-native options both let you choose where data lands, so choose deliberately.</li>



<li>Assuming this has to be AWS at all. For a low-sensitivity workload with no compliance surface, a VPS from Contabo or InterServer may beat a thin footprint in an expensive region. Say so if it is true.</li>
</ul>



<h2 class="wp-block-heading">How I would pick an AWS region for MENA</h2>



<ol class="wp-block-list">
<li>Get the in-scope data classes in writing. Personal data, health data, financial records and anonymised telemetry carry different rules.</li>



<li>Ask counsel which residency shape applies and whether a sector supervisor sits on top. Eliminate the regions that fail it. If nothing survives, have the hybrid conversation now rather than in month four.</li>



<li>Run the availability matrix against the survivors and eliminate again. If more than one region is left, measure latency from the target country and pick.</li>



<li>Write down the choice and the reasoning, enable the region on every account in scope, and apply the guardrail that same week.</li>
</ol>



<p class="wp-block-paragraph">That last step is the one people skip. A region decision without a written rationale gets relitigated every time a new engineer joins, and a region decision without a guardrail quietly stops being true.</p>



<h2 class="wp-block-heading">Frequently asked questions</h2>



<h3 class="wp-block-heading">Which AWS region is best for a client in the UAE?</h3>



<p class="wp-block-paragraph">If in-country storage is required, the UAE region is the only AWS option that puts data inside the country. If the requirement is softer, Bahrain is a fair alternative and has been open longer, which usually means broader service coverage. Run the availability matrix against both before committing.</p>



<h3 class="wp-block-heading">Is there an AWS region in Egypt or Saudi Arabia?</h3>



<p class="wp-block-paragraph">Check the region table rather than trusting any article, this one included. AWS has publicly announced intent to build in the area and those announcements circulate widely, but only regions appearing in the table your account can query are deployable. Where there is no region, the real question is which nearby region you are permitted to transfer into.</p>



<h3 class="wp-block-heading">Does choosing a nearby AWS region satisfy MENA data residency law?</h3>



<p class="wp-block-paragraph">Not by itself. Several MENA data protection regimes treat any movement of personal data across national borders as a transfer needing a legal basis, and some require prior authorisation from a regulator. Geographic closeness carries no legal weight. Get the transfer basis confirmed before the region is fixed.</p>



<h3 class="wp-block-heading">How do I check whether a specific AWS service is available in a region?</h3>



<p class="wp-block-paragraph">Query the global infrastructure tree in Systems Manager Parameter Store. An empty result for a service and region pair means it is not offered there. It is the most reliable programmatic source, but it only tells you the service exists, not which features or instance types are available.</p>



<h3 class="wp-block-heading">Why do I get access denied in a Middle East region even as an administrator?</h3>



<p class="wp-block-paragraph">Two likely causes: the region is opt-in and has not been enabled on that account, or an SCP is denying requests outside an approved region list. Administrator permissions do not override an SCP, which is rather the point of one.</p>



<h3 class="wp-block-heading">How hard is it to change region later?</h3>



<p class="wp-block-paragraph">Harder than a move between accounts and much harder than people estimate. Every region-scoped identifier changes, stateful services need a data move with a cutover, and any regional endpoint baked into client configuration has to be coordinated. Budget it as a project, not a task. Which is exactly why the upfront decision deserves the care.</p>



<h2 class="wp-block-heading">The one thing worth remembering</h2>



<p class="wp-block-paragraph">Choosing an AWS region for MENA clients is a legal decision with a technical implementation, not the reverse. Residency eliminates candidates, service availability eliminates more, latency picks between whatever survives. Run the filter in that order and you will sometimes land on a region that is not the closest one, which is fine. Run it backwards and you get a fast architecture in the wrong jurisdiction, which no amount of edge caching fixes.</p>



<p class="wp-block-paragraph">Get the transfer basis in writing, enforce the decision with a guardrail rather than a convention, and record why you chose what you chose.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Need help choosing or defending a region decision?</h2>



<p class="wp-block-paragraph">This is the kind of work where an outside opinion early saves a rebuild later. Things I can help with:</p>



<ul class="wp-block-list">
<li>Running a service availability matrix across candidate regions for your actual architecture, so the gaps surface before any infrastructure code is written</li>



<li>Writing and testing a region deny SCP with the global-service exemptions tuned to your service inventory, without locking anyone out</li>



<li>Building a repeatable latency measurement from your target markets rather than from a vendor&#8217;s map</li>



<li>Designing the CDN and caching split so static content goes to the edge and personal data does not</li>



<li>Reviewing an existing deployment for accidental cross-region data flows in backups, replication, logging and monitoring</li>



<li>Producing the written region rationale your client&#8217;s auditor or counsel is going to ask for</li>
</ul>



<p class="wp-block-paragraph">Send me the architecture diagram, a Terraform provider block, or the output of your region opt-in check, and I will tell you what I would look at first.</p>



<div class="wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex">
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="https://www.upwork.com/freelancers/~01f15a912ad84a6620" target="_blank" rel="noreferrer noopener">Work with me on Upwork</a></div>
</div>
<p>The post <a href="https://john-nessime.com/blog/cloud-computing/aws-region-for-mena/">Choosing an AWS Region for a MENA Client: Latency Is the Last Question</a> appeared first on <a href="https://john-nessime.com/blog">John Nessime</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
