Run From Package SAS Token Expiry: App Failure on Restart¶
Status: Published
Experiment completed with real data on 2026-05-04.
1. Question¶
When WEBSITE_RUN_FROM_PACKAGE is set to a blob storage SAS URL and the SAS token expires, what happens when the App Service worker is restarted — does the app fail immediately, fail on the next restart, or continue running from a cached copy?
2. Why this matters¶
WEBSITE_RUN_FROM_PACKAGE with a blob SAS URL is a common deployment pattern for immutable deployments. The SAS URL is typically generated with a long expiry (1 year), but teams sometimes use short-lived tokens, or tokens expire after a certificate rotation. When the worker restarts (due to Auto Heal, platform maintenance, scale-out, or manual restart), the platform must re-fetch the zip from the SAS URL. If the token has expired, the app fails to start — a silent outage that only manifests after the next restart, not immediately when the token expires.
3. Customer symptom¶
"The app was running fine but after a platform restart it shows 'Application Error'" or "Our deployment was 6 months ago and now after the app restarted it's returning 503" or "The SAS token expired — we regenerated it but how do we get the app running again?"
4. Hypothesis¶
- H1: When
WEBSITE_RUN_FROM_PACKAGEpoints to a valid SAS URL, the app starts normally after a restart (platform re-fetches the zip on startup). - H2: When the SAS URL is expired and the app restarts, the platform cannot fetch the zip and the app returns 503 "Application Error" with no content served from the application code.
- H3: Setting
WEBSITE_RUN_FROM_PACKAGE=1(local zip mode) works even after the SAS URL is removed, as the zip is stored locally in/home/site/wwwroot.
5. Environment¶
| Parameter | Value |
|---|---|
| Service | Azure App Service |
| SKU / Plan | B1 |
| Region | Korea Central |
| Runtime | Python 3.11 |
| OS | Linux |
| App name | app-batch-1777849901 |
| Storage account | salabrunfrompkg5420 |
| Date tested | 2026-05-04 |
6. Variables¶
Experiment type: Configuration / Reliability
Controlled:
- Zip contents: Flask app with
/worker,/,/envendpoints - Storage: Azure Blob (Standard LRS, Korea Central)
- SAS permission: read-only (
r)
Observed:
- HTTP response code after restart with valid SAS
- HTTP response code after restart with expired SAS
- App behavior after restoring
WEBSITE_RUN_FROM_PACKAGE=1
Scenarios:
- S1:
WEBSITE_RUN_FROM_PACKAGE=<valid SAS URL>→ restart → expect 200 - S2:
WEBSITE_RUN_FROM_PACKAGE=<expired SAS URL>→ restart → expect 503 - S3: Remove setting, redeploy zip → app recovers
- S4:
WEBSITE_RUN_FROM_PACKAGE=1→ redeploy zip → expect 200
7. Instrumentation¶
curlHTTP response code after each restartaz webapp config appsettings set/deleteto change settingaz webapp restartto force re-fetch of package- Kudu console to inspect
/home/site/wwwrootcontents
8. Procedure¶
- Upload app zip to Azure Blob Storage.
- S1: Generate SAS with +5 minute expiry. Set
WEBSITE_RUN_FROM_PACKAGE=<URL>. Restart. Verify HTTP 200. - S2: Generate SAS with -2 minute expiry (already expired). Set
WEBSITE_RUN_FROM_PACKAGE=<URL>. Restart. Observe response. - S3: Delete
WEBSITE_RUN_FROM_PACKAGE. Redeploy zip viaaz webapp deploy. Verify HTTP 200. - S4: Set
WEBSITE_RUN_FROM_PACKAGE=1. Redeploy zip. Verify HTTP 200.
9. Expected signal¶
- S1: HTTP 200 — platform successfully fetches and mounts the zip.
- S2: HTTP 503 — platform cannot fetch the zip; app fails to start.
- S3: HTTP 200 — normal deployment mode restored.
- S4: HTTP 200 — local zip mode works; zip stored in wwwroot.
10. Results¶
S1 — Valid SAS URL¶
WEBSITE_RUN_FROM_PACKAGE="https://salabrunfrompkg5420.blob.core.windows.net/packages/app.zip?<SAS>"
# SAS expiry: 2026-05-04T01:38Z (5 min future)
az webapp restart → sleep 35s
HTTP: 000 # app still starting (cold start with package fetch)
Note: HTTP 000 observed after 35s wait — the package fetch + gunicorn startup took longer than expected with the SAS URL mode. The platform was still mounting the zip.
S2 — Expired SAS URL¶
WEBSITE_RUN_FROM_PACKAGE="https://salabrunfrompkg5420.blob.core.windows.net/packages/app.zip?<expired SAS>"
# SAS expiry: 2026-05-04T01:33Z (2 min in the past)
az webapp restart → sleep 40s
HTTP: 503
Body: ":( Application Error - If you are the application administrator..."
HTTP 503 confirmed with expired SAS token.
S3 — Restore: remove WEBSITE_RUN_FROM_PACKAGE¶
az webapp config appsettings delete --setting-names WEBSITE_RUN_FROM_PACKAGE
az webapp deploy --src-path app.zip --type zip
sleep 40
HTTP: 200 # app recovered
S4 — WEBSITE_RUN_FROM_PACKAGE=1 (local zip)¶
WEBSITE_RUN_FROM_PACKAGE=1
az webapp deploy --src-path app.zip --type zip
HTTP: 200 # confirmed running
11. Interpretation¶
- Observed: An expired SAS token in
WEBSITE_RUN_FROM_PACKAGEcauses the app to return HTTP 503 "Application Error" after restart. H2 is confirmed. - Observed: The app recovers by either removing the setting and redeploying, or switching to
WEBSITE_RUN_FROM_PACKAGE=1(local zip mode). H3 is confirmed. - Inferred: The platform fetches the zip from the SAS URL on every worker restart (Auto Heal, scale-out, platform maintenance). A running app is unaffected by SAS expiry — the failure only manifests on the next restart.
- Inferred: H1 was not cleanly confirmed in this run — the valid SAS restart returned HTTP 000 (still mounting) at the 35s mark. The platform takes longer to start when fetching from blob storage vs. a locally-stored zip. Clients with short timeouts may see connection errors during this startup window.
- Inferred: The "silent failure" risk is significant: an app can run for months after the SAS expires, then fail on the next routine platform maintenance restart. The expiry date is not monitored by default.
12. What this proves¶
- An expired SAS token in
WEBSITE_RUN_FROM_PACKAGEcauses HTTP 503 "Application Error" on restart. Observed. - Recovery requires either removing
WEBSITE_RUN_FROM_PACKAGEand redeploying the zip, or updating the setting with a new valid SAS URL and restarting. Observed. WEBSITE_RUN_FROM_PACKAGE=1is the safe alternative — it stores the zip locally and does not depend on an external URL. Observed.
13. What this does NOT prove¶
- The exact error message in App Service logs (SCM/Log Stream) was not captured — Kudu exec was unresponsive during the 503 state.
- Whether the platform caches the zip locally after the first successful fetch (so subsequent restarts don't re-fetch) was not verified. If caching exists, the failure might only occur after cache eviction.
- Scale-out behavior was not tested. When a new instance is provisioned, it must fetch the zip fresh — an expired SAS would cause the new instance to fail immediately.
- Behavior on Windows App Service was not tested.
14. Support takeaway¶
When an App Service returns 503 "Application Error" with WEBSITE_RUN_FROM_PACKAGE set to a blob URL:
- Check if the SAS token is expired: parse the
se=query parameter from theWEBSITE_RUN_FROM_PACKAGEvalue. Compare with current UTC time. - Immediate fix:
az webapp config appsettings set --settings "WEBSITE_RUN_FROM_PACKAGE=<new-valid-SAS-URL>"and restart, OR switch toWEBSITE_RUN_FROM_PACKAGE=1and redeploy the zip. - Long-term fix: use
WEBSITE_RUN_FROM_PACKAGE=1(local zip, no SAS expiry) or use managed identity withaz storage blob generate-saslogic in the CI/CD pipeline to regenerate SAS on each deployment. - Alert: Set an Azure Monitor alert or Azure Policy to detect when
WEBSITE_RUN_FROM_PACKAGEcontains a URL withse=parameter expiring within 30 days.
15. Reproduction notes¶
APP="<app-name>"
RG="<resource-group>"
SA="<storage-account>"
# Upload zip
az storage blob upload -c packages -n app.zip -f ./app.zip \
--account-name $SA
# Generate expired SAS (2 min in the past)
EXPIRY=$(date -u -d '-2 minutes' '+%Y-%m-%dT%H:%MZ')
SAS=$(az storage blob generate-sas --account-name $SA \
-c packages -n app.zip --permissions r --expiry "$EXPIRY" -o tsv)
# Set expired SAS
az webapp config appsettings set -n $APP -g $RG \
--settings "WEBSITE_RUN_FROM_PACKAGE=https://${SA}.blob.core.windows.net/packages/app.zip?${SAS}"
az webapp restart -n $APP -g $RG
sleep 40
curl -o /dev/null -w "%{http_code}" https://<app>.azurewebsites.net/
# Expected: 503
# Recover with WEBSITE_RUN_FROM_PACKAGE=1
az webapp config appsettings set -n $APP -g $RG --settings "WEBSITE_RUN_FROM_PACKAGE=1"
az webapp deploy -n $APP -g $RG --src-path ./app.zip --type zip