Symptom
A load that worked yesterday, or a new stage you just created, fails as soon as Snowflake touches the bucket or container:
LIST @stageorCOPY INTOreturns an access denied error from S3 (Access Denied (Status Code: 403; Error Code: AccessDenied)) or Azure (This request is not authorized to perform this operation).CREATE STAGEfails because the location is not allowed by the storage integration.CREATE STAGEfails with insufficient privileges on the integration.- Snowpipe stops loading with no obvious error in the pipe.
Error text varies between Snowflake releases and cloud providers, so match on the general meaning, not the exact string.
A storage integration has four parts that must all agree:
- The integration object in Snowflake (allowed locations, role ARN or tenant ID).
- The trust on the cloud side: an IAM role trust policy on AWS, or admin consent plus a role assignment on Azure.
- Snowflake privileges: the role creating or using the stage needs
USAGEon the integration. - The network path: bucket policies, storage firewalls and private endpoints.
Work through them in that order.
Step 1: test the integration directly
SYSTEM$VALIDATE_STORAGE_INTEGRATION tests the integration without a stage, so it separates integration problems from stage problems.
USE ROLE ACCOUNTADMIN;
SELECT SYSTEM$VALIDATE_STORAGE_INTEGRATION(
'S3_RAW_INT', -- integration name, case-sensitive
's3://acme-raw/landing/', -- must be inside STORAGE_ALLOWED_LOCATIONS
'snowflake_validate.txt', -- test file
'list'); -- read | write | list | delete | all
The result is JSON with an overall status and a status per action. read fails if the test file does not exist, and write fails if it already does, so pick the action you need. all writes and deletes a test object, so only use it where Snowflake is meant to have write access.
| Result | Where to look |
|---|---|
| Path not allowed | Step 2: allowed locations |
| AssumeRole / authorization failure on AWS | Step 3: trust policy and external ID |
| Authorization failure on Azure | Step 4: consent and role assignment |
| Success, but the stage still fails | Step 5: stage definition and grants |
| Success from some places, timeouts or 403 from others | Step 6: network rules |
Step 2: the path must be in the allowed locations
DESC STORAGE INTEGRATION S3_RAW_INT;
Check ENABLED, STORAGE_ALLOWED_LOCATIONS and STORAGE_BLOCKED_LOCATIONS. The stage URL must start with one of the allowed prefixes and not match a blocked one. Common mistakes:
- Missing trailing slash, or a different path case. S3 keys and Azure paths are case-sensitive.
- Azure stage written as
azure://acct.blob.core.windows.net/container/while the integration allows a different account or container. - A new folder added by a project team that was never added to the integration.
ALTER ... SET STORAGE_ALLOWED_LOCATIONS replaces the whole list. Copy the current value from DESC and append to it:
ALTER STORAGE INTEGRATION S3_RAW_INT SET
STORAGE_ALLOWED_LOCATIONS = ('s3://acme-raw/landing/', 's3://acme-raw/partner-feeds/');
Warning
Leaving out an existing path in the
SETbreaks every stage and pipe that uses it, without warning. Save theDESCoutput before changing it.
Step 3: AWS – trust policy and external ID
From DESC STORAGE INTEGRATION, note STORAGE_AWS_ROLE_ARN, STORAGE_AWS_IAM_USER_ARN and STORAGE_AWS_EXTERNAL_ID. Compare them with the role in AWS:
aws iam get-role --role-name snowflake-raw-reader \
--query 'Role.AssumeRolePolicyDocument' --output json
The trust policy must allow sts:AssumeRole for the Snowflake IAM user ARN, with a condition on the external ID:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "<STORAGE_AWS_IAM_USER_ARN>" },
"Action": "sts:AssumeRole",
"Condition": { "StringEquals": { "sts:ExternalId": "<STORAGE_AWS_EXTERNAL_ID>" } }
}]
}
aws iam update-assume-role-policy --role-name snowflake-raw-reader \
--policy-document file://trust.json
The most common cause of "it worked yesterday" is that someone ran CREATE OR REPLACE STORAGE INTEGRATION. Unless you set STORAGE_AWS_EXTERNAL_ID explicitly, Snowflake generates a new external ID for the new integration, and the old trust policy no longer matches. Either update the trust policy with the new value from DESC, or recreate the integration with a fixed external ID:
CREATE OR REPLACE STORAGE INTEGRATION S3_RAW_INT
TYPE = EXTERNAL_STAGE
STORAGE_PROVIDER = 'S3'
ENABLED = TRUE
STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::111122223333:role/snowflake-raw-reader'
STORAGE_AWS_EXTERNAL_ID = 'acme_raw_ext_id'
STORAGE_ALLOWED_LOCATIONS = ('s3://acme-raw/landing/');
Caution
CREATE OR REPLACEon an integration in use breaks every stage and pipe that references it until the trust is fixed. PreferALTER STORAGE INTEGRATIONfor changes, and check the stages that depend on it first.
If the trust is correct and access still fails, check the role's permission policy (s3:GetObject, s3:GetObjectVersion, s3:ListBucket for reads; s3:PutObject and s3:DeleteObject for unload or purge), the bucket policy, and any KMS key policy. With SSE-KMS the role also needs kms:Decrypt on the key, and an explicit deny in the bucket policy overrides everything.
Step 4: Azure – consent and role assignment
DESC STORAGE INTEGRATION on an Azure integration shows AZURE_TENANT_ID, AZURE_CONSENT_URL and AZURE_MULTI_TENANT_APP_NAME.
AZURE_TENANT_IDmust be the tenant that owns the storage account. A wrong tenant ID is a common mistake when there is more than one tenant.- A tenant admin must have opened
AZURE_CONSENT_URLand accepted. That creates a service principal for Snowflake in your tenant. - That service principal needs a data-plane role on the storage account or container. Owner or Contributor alone is not enough; those are management roles.
The service principal display name is the part of AZURE_MULTI_TENANT_APP_NAME before the underscore:
az ad sp list --display-name <app-name-prefix> --query "[].{name:displayName, id:id}" -o table
az role assignment list --assignee <sp-object-id> --all -o table
az role assignment create --assignee-object-id <sp-object-id> \
--assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Reader" \
--scope "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<acct>/blobServices/default/containers/<container>"
Use Storage Blob Data Contributor if Snowflake needs to unload, write or PURGE. New role assignments can take several minutes to take effect, so wait before retesting.
Step 5: stage definition and Snowflake grants
If validation succeeds but the stage fails, compare the two:
DESC STAGE raw.landing.sales_stage; -- check URL and STORAGE_INTEGRATION
SHOW GRANTS ON INTEGRATION S3_RAW_INT;
- The stage URL must sit under an allowed location, and the stage must reference the integration you tested.
- The role that creates the stage needs
USAGEon the integration andCREATE STAGEon the schema. The role that loads needsUSAGEon the stage plus the table privileges.
GRANT USAGE ON INTEGRATION S3_RAW_INT TO ROLE LOADER_ROLE;
GRANT CREATE STAGE ON SCHEMA raw.landing TO ROLE LOADER_ROLE;
Step 6: network rules on the storage side
Storage firewalls cause errors that look the same as permission errors.
On Azure, if the storage account allows only selected networks, add the Snowflake VNet subnet IDs for your account's region:
SELECT SYSTEM$GET_SNOWFLAKE_PLATFORM_INFO(); -- returns snowflake-vnet-subnet-id values
az storage account network-rule add -g <rg> --account-name <acct> --subnet <snowflake-subnet-id>
On AWS, check for bucket policy conditions such as aws:SourceVpce or aws:SourceIp that do not include Snowflake's traffic. If you use private connectivity to storage, the setup is different; follow the Snowflake private connectivity documentation for your edition and cloud rather than opening the firewall.
Symptom → cause → fix
| Symptom | Cause | Fix |
|---|---|---|
| Location not allowed by integration | Path outside STORAGE_ALLOWED_LOCATIONS | ALTER ... SET with the full list plus the new path |
| AWS 403 after an integration change | CREATE OR REPLACE generated a new external ID | Update trust policy, or set STORAGE_AWS_EXTERNAL_ID |
| AWS 403 only on some files | KMS key policy or a bucket policy deny | Grant kms:Decrypt; review deny statements |
| Azure "not authorized" | No consent, wrong tenant, or no Blob Data role | Consent URL, fix tenant ID, assign data role |
| Azure works from the portal but not Snowflake | Storage firewall | Add Snowflake subnet IDs |
| Insufficient privileges on integration | Role lacks USAGE | GRANT USAGE ON INTEGRATION |
Prevent it next time
- Manage integrations with
ALTER, notCREATE OR REPLACE, and setSTORAGE_AWS_EXTERNAL_IDexplicitly so a rebuild keeps the trust. - Keep the integration DDL, IAM trust policy and Azure role assignments in the same repository so a reviewer sees both sides of a change.
- Run
SYSTEM$VALIDATE_STORAGE_INTEGRATIONwithlistfrom a scheduled task or monitoring job and alert on failure, before the nightly load finds the problem. - Record which stages and pipes depend on each integration, and check that list before any change.