How to use AzCopy with SAS to copy data from one Azure tenant to another
A few days ago I had to copy blobs from a storage account in one Azure tenant to another storage account in a different tenant. As a pragmatic approach for this task I used azcopy together with a SAS token.
SAS stands for Shared Access Signature, in simple terms, it is a temporary URL with limited permissions, so you can grant access to a storage resource without exposing the full account key. Using SAS can be useful when you just want to move data from one tenant to another without setting up a full identity and RBAC model for the job.
The script you will need
First, we generate the SAS tokens dynamically in a script rather than manually. By using the Azure CLI and a managed identity, you avoid hardcoding credentials and keep access more secure.
Here is a quick snippet showing how to generate short-lived, least-privilege tokens for both the source (read, list) and destination (read, add, create, write, list), and then execute the transfer with azcopy:
# Set expiry to 30 minutes from now
expiry=$(date -u -d "30 minutes" '+%Y-%m-%dT%H:%MZ')
# Generate Source SAS (Read, List)
src_sas=$(az storage container generate-sas \
--account-name srcstorageprod \
--name logs \
--permissions rl \
--expiry $expiry \
--auth-mode login \
--as-user \
--output tsv)
# Generate Destination SAS (Read, Add, Create, Write, List)
dst_sas=$(az storage container generate-sas \
--account-name dststorageprod \
--name logs-copy \
--permissions racwl \
--expiry $expiry \
--auth-mode login \
--as-user \
--output tsv)
# Construct full URLs and run AzCopy
src_url="https://srcstorageprod.blob.core.windows.net/logs/*?$src_sas"
dst_url="https://dststorageprod.blob.core.windows.net/logs-copy?$dst_sas"
azcopy copy "$src_url" "$dst_url" --recursive
Notice the /* added to the end of the source container path in the script. This ensures we copy the contents of the logs container directly into the destination, rather than accidentally creating a nested logs folder inside logs-copy.
The --recursive flag makes sure the whole container tree is copied instead of just the root files. azcopy usually auto-detects a blob-to-blob transfer, but you can explicitly pass --from-to BlobBlob if needed.
If you opt to generate these tokens manually via the Azure Portal instead of using a script, a typical Container SAS URL will look like this:
https://srcstorageprod.blob.core.windows.net/logs?sv=2024-11-04&st=2026-07-18T10:00:00Z&se=2026-07-18T18:00:00Z&sr=c&sp=rl&sig=YOUR_SIGNATURE
If you are using custom routes
If the storage accounts sit behind a custom domain, such as Azure Front Door or Application Gateway, then the URLs in the command should use those custom domains instead of the default blob.core.windows.net endpoints.
Monitoring the transfer
Once the command runs, you can monitor the progress directly in the terminal. If you want to check the status of jobs later, you can also use:
azcopy jobs list
That is useful when the copy is part of an automation flow and you want to confirm whether the transfer completed successfully.
What usually goes wrong
When I troubleshoot these scenarios, I usually check the following first:
- the SAS has expired,
- the SAS is missing one of the required permissions, such as
worcon the destination, - the storage account is behind a firewall or private endpoint,
- the destination container does not exist yet, because
azcopywill not create the container for you, - the URL is built incorrectly.
- Indentation: When copying the bash block into your editor, ensure the line continuations use standard spaces.
Final word
From a security perspective, keep the SAS validity short, usually around 15 to 30 minutes, and give the token only the minimum permissions that are actually needed for the transfer.
Feel free to drop me a comment, if you found this article helpful.