All Products
Search
Document Center

Object Storage Service:Use a presigned URL to handle object uploads

Last Updated:Mar 20, 2026

By default, files in an OSS bucket are private and only the bucket owner can upload them. To let a third party upload a file without exposing your credentials, generate a presigned URL for that file. Anyone with the URL can upload within its validity period—useful for scenarios like letting partners submit contracts or letting users upload profile pictures.

Important

Due to a policy change to improve compliance and security, starting March 20, 2025, new OSS users must use a custom domain name (CNAME) to call data API operations on buckets in Chinese mainland regions. Default public endpoints are restricted for these operations. Refer to the official announcement for a complete list of the affected operations. If you access OSS via HTTPS, bind a valid SSL certificate to your custom domain—this is mandatory for OSS Console access.

Prerequisites

Before you begin, make sure that you have:

  • An OSS bucket in the target region

  • The oss:PutObject permission for the bucket owner's account. No permission is required to generate a presigned URL itself, but the upload succeeds only if the URL generator has this permission. See Grant custom permissions to a RAM user

Usage notes

  • Examples in this topic use the public endpoint of the China (Hangzhou) region. When accessing OSS from other Alibaba Cloud services in the same region, use an internal endpoint. For details, see Regions and endpoints.

  • Presigned URLs do not support FormData uploads. For FormData, use OSS form upload instead.

How presigned URLs work

A presigned URL is a signed link that grants temporary access to a specific OSS object. The signature is computed locally from the AccessKey pair, resource path, expiration time, and other parameters, then embedded in the URL as query parameters.

The typical URL format is:

https://BucketName.Endpoint/Object?signature_parameters

For example:

https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-process=image%2Fresize%2Cp_10&x-oss-date=20241115T095058Z&x-oss-expires=3600&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI****************%2F20241115%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=6e7a********************************

The URL contains the following parameters:

ParameterDescription
x-oss-dateThe date and time (ISO 8601) when the signature was created
x-oss-expiresHow long the URL remains valid, in seconds, from x-oss-date
x-oss-signature-versionThe signing algorithm used
x-oss-credentialCredential scope identifying the AccessKey ID, date, region, service, and request type
x-oss-signatureThe computed signature

When OSS receives a request with a presigned URL, it verifies the signature. If any parameter has been modified or the URL has expired, OSS denies the request.

Validity periods:

Credential typeMaximum validity
SDK (AccessKey pair)7 days
Security Token Service (STS) temporary credentials43,200 seconds (12 hours)
Important

When you generate a presigned URL using STS temporary credentials, the URL expires when either the configured validity period ends or the STS token expires—whichever occurs first. If the STS token has only 30 minutes remaining, the URL becomes invalid after 30 minutes regardless of the configured expiry. Set the URL validity period to be shorter than the STS token's remaining lifetime.

Security: Treat presigned URLs as bearer tokens—anyone with the URL can perform the authorized operation until it expires. Share presigned URLs only with intended recipients, and use short validity periods for sensitive operations.

Common use cases:

  • Temporary uploads: A backend generates a short-lived presigned URL and returns it to a frontend. The user uploads directly to OSS without needing OSS credentials.

  • Flexible sharing: Share a presigned URL via email or messaging so a recipient can upload without accessing the OSS console.

Upload a single file

The upload flow involves two parties: the server (bucket owner) that generates the presigned URL, and the client (third party) that uses it to upload.

Upload using presigned URL flow

Step 1: Generate a presigned URL (server side)

The bucket owner generates a presigned URL for a PUT request. The third party uses this URL to upload the file.

The maximum validity period for a presigned URL generated with an SDK is 7 days. When using an STS token, the maximum is 43,200 seconds (12 hours). If the STS token expires before the configured validity period ends, the URL becomes invalid at token expiry—not at the configured time.

The following examples use Java, Go, and Python. For other SDKs, see the links below each example.

Java

For more SDK details, see Upload a file using a presigned URL in Java.

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import java.net.URL;
import java.util.*;
import java.util.Date;

public class GetSignUrl {
    public static void main(String[] args) throws Throwable {
        // This example uses the public endpoint of the China (Hangzhou) region. Specify the actual endpoint.
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        // Obtain access credentials from environment variables. Before you run the sample code, make sure that the OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET environment variables are configured.
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // Specify the bucket name, for example, examplebucket.
        String bucketName = "examplebucket";
        // Specify the full path of the object, for example, exampleobject.txt. Do not include the bucket name.
        String objectName = "exampleobject.txt";
        // Specify the region where the bucket is located, for example, cn-hangzhou.
        String region = "cn-hangzhou";

        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        OSS ossClient = OSSClientBuilder.create()
                .endpoint(endpoint)
                .credentialsProvider(credentialsProvider)
                .clientConfiguration(clientBuilderConfiguration)
                .region(region)
                .build();

        URL signedUrl = null;
        try {
            // Set the expiration time to 1 hour.
            Date expiration = new Date(new Date().getTime() + 3600 * 1000L);

            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.PUT);
            request.setExpiration(expiration);
            signedUrl = ossClient.generatePresignedUrl(request);
            System.out.println("signed url for putObject: " + signedUrl);

        } catch (OSSException oe) {
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Error Message:" + ce.getMessage());
        }
    }
}

Go

For more SDK details, see Upload a file using a presigned URL in Go.

package main

import (
	"context"
	"flag"
	"log"
	"time"

	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
)

var (
	region     string
	bucketName string
	objectName string
)

func init() {
	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
	flag.StringVar(&objectName, "object", "", "The name of the object.")
}

func main() {
	flag.Parse()

	if len(bucketName) == 0 || len(region) == 0 || len(objectName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, bucket name, region, and object name are required")
	}

	cfg := oss.LoadDefaultConfig().
		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
		WithRegion(region)

	client := oss.NewClient(cfg)

	// Generate a presigned URL for the PutObject request.
	result, err := client.Presign(context.TODO(), &oss.PutObjectRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(objectName),
		// ContentType: oss.Ptr("text/txt"), // If you set ContentType here, include the same value in the PUT request.
		// Metadata: map[string]string{"key1": "value1"}, // Same applies to Metadata.
	},
		oss.PresignExpires(10*time.Minute),
	)
	if err != nil {
		log.Fatalf("failed to presign PutObject: %v", err)
	}

	log.Printf("method: %v\n", result.Method)
	log.Printf("expiration: %v\n", result.Expiration)
	log.Printf("url: %v\n", result.URL)
	if len(result.SignedHeaders) > 0 {
		// If headers are signed into the URL, the PUT request must include the same headers.
		log.Printf("signed headers:\n")
		for k, v := range result.SignedHeaders {
			log.Printf("%v: %v\n", k, v)
		}
	}
}

Python

For more SDK details, see Upload a file using a presigned URL in Python.

import argparse
import alibabacloud_oss_v2 as oss
from datetime import timedelta

parser = argparse.ArgumentParser(description="presign put object sample")
parser.add_argument('--region', help='The region in which the bucket is located.', required=True)
parser.add_argument('--bucket', help='The name of the bucket.', required=True)
parser.add_argument('--endpoint', help='The domain name used to access OSS.')
parser.add_argument('--key', help='The name of the object.', required=True)

def main():
    args = parser.parse_args()

    credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    cfg = oss.config.load_default()
    cfg.credentials_provider = credentials_provider
    cfg.region = args.region
    if args.endpoint is not None:
        cfg.endpoint = args.endpoint

    client = oss.Client(cfg)

    # Generate a presigned URL valid for 3,600 seconds.
    pre_result = client.presign(oss.PutObjectRequest(
        bucket=args.bucket,
        key=args.key,
    ), expires=timedelta(seconds=3600))

    print(f'method: {pre_result.method},'
          f' expiration: {pre_result.expiration.strftime("%Y-%m-%dT%H:%M:%S.000Z")},'
          f' url: {pre_result.url}')

    for key, value in pre_result.signed_headers.items():
        print(f'signed header - {key}: {value}')

if __name__ == "__main__":
    main()

Other SDKs

SDKReference
Node.jsUpload files using presigned URLs in Node.js
PHPUpload files using presigned URLs in PHP
AndroidUpload a file using a presigned URL in Android
iOSUpload a file using a presigned URL in iOS
.NETUpload files using presigned URLs in .NET
C++Upload a file using a presigned URL in C++
CUpload a file using a presigned URL in C

Step 2: Upload the file (client side)

The third party uses the presigned URL to upload the file with a PUT request.

Important

A presigned URL can be used multiple times until it expires. Multiple uploads to the same URL overwrite the object. After the URL expires, regenerate it.

curl

curl -X PUT -T /path/to/local/file "https://exampleobject.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T083238Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI****************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a******************************************************"

Java

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import java.io.*;
import java.net.URL;

public class SignUrlUpload {
    public static void main(String[] args) throws Throwable {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;

        // Replace <signedUrl> with the presigned URL.
        URL signedUrl = new URL("<signedUrl>");
        String pathName = "C:\\Users\\demo.txt";

        try {
            HttpPut put = new HttpPut(signedUrl.toString());
            HttpEntity entity = new FileEntity(new File(pathName));
            put.setEntity(entity);

            httpClient = HttpClients.createDefault();
            response = httpClient.execute(put);

            System.out.println("Status code: " + response.getStatusLine().getStatusCode());
            if (response.getStatusLine().getStatusCode() == 200) {
                System.out.println("Upload successful.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (response != null) response.close();
            if (httpClient != null) httpClient.close();
        }
    }
}

Go

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func uploadFile(signedUrl, filePath string) error {
	file, err := os.Open(filePath)
	if err != nil {
		return fmt.Errorf("unable to open file: %w", err)
	}
	defer file.Close()

	client := &http.Client{}
	req, err := http.NewRequest("PUT", signedUrl, file)
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Printf("Status code: %d\n", resp.StatusCode)
	if resp.StatusCode == 200 {
		fmt.Println("Upload successful.")
	}
	fmt.Println(string(body))
	return nil
}

func main() {
	// Replace <signedUrl> with the presigned URL.
	signedUrl := "<signedUrl>"
	filePath := "C:\\Users\\demo.txt"

	if err := uploadFile(signedUrl, filePath); err != nil {
		fmt.Println("An error occurred:", err)
	}
}

Python

import requests

def upload_file(signed_url, file_path):
    try:
        with open(file_path, 'rb') as file:
            response = requests.put(signed_url, data=file)
        print(f"Status code: {response.status_code}")
        if response.status_code == 200:
            print("Upload successful.")
        print(response.text)
    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    # Replace <signedUrl> with the presigned URL.
    signed_url = "<signedUrl>"
    file_path = "C:\\Users\\demo.txt"
    upload_file(signed_url, file_path)

Node.js

const fs = require('fs');
const axios = require('axios');

async function uploadFile(signedUrl, filePath) {
    try {
        const fileStream = fs.createReadStream(filePath);
        const response = await axios.put(signedUrl, fileStream, {
            headers: {
                'Content-Type': 'application/octet-stream'
            }
        });
        console.log(`Status code: ${response.status}`);
        if (response.status === 200) {
            console.log('Upload successful.');
        }
    } catch (error) {
        console.error(`An error occurred: ${error.message}`);
    }
}

(async () => {
    // Replace <signedUrl> with the presigned URL.
    const signedUrl = '<signedUrl>';
    const filePath = 'C:\\Users\\demo.txt';
    await uploadFile(signedUrl, filePath);
})();

Browser.js

Important

Browsers automatically add a Content-Type request header. If Content-Type was not included when generating the presigned URL, OSS returns a 403 signature mismatch error. To avoid this, always specify Content-Type when generating the URL for browser-based uploads. Specifying Content-Type also prevents clients from uploading files of unexpected types—if the upload request uses a different Content-Type, OSS rejects it with a 403 error.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>File Upload Example</title>
</head>
<body>
    <input type="file" id="fileInput" />
    <button id="uploadButton">Upload file</button>

    <script>
        // Replace this with the presigned URL generated in Step 1.
        const signedUrl = "<signedUrl>";

        document.getElementById('uploadButton').addEventListener('click', async () => {
            const fileInput = document.getElementById('fileInput');
            const file = fileInput.files[0];

            if (!file) {
                alert('Please select a file to upload.');
                return;
            }

            try {
                await upload(file, signedUrl);
                alert('File uploaded successfully!');
            } catch (error) {
                console.error('Error during upload:', error);
                alert('Upload failed: ' + error.message);
            }
        });

        const upload = async (file, presignedUrl) => {
            const response = await fetch(presignedUrl, {
                method: 'PUT',
                body: file,
            });

            if (!response.ok) {
                throw new Error(`Upload failed, status: ${response.status}`);
            }
            console.log('File uploaded successfully.');
        };
    </script>
</body>
</html>

Perform a multipart upload

For files that cannot use an OSS SDK directly but are too large for a single PUT (over 5 GB), use presigned URLs to perform a multipart upload. The server initiates the upload task, generates a presigned URL for each part, and the client uploads each part independently. After all parts are uploaded, the server completes the upload by merging the parts.

Important

Multipart upload with presigned URLs requires careful coordination. Each presigned URL is tied to a specific partNumber, and uploading the wrong data to a URL results in a corrupted file. If your client can integrate an OSS SDK, use STS credentials for direct client-side uploads instead—it's simpler and more reliable.

Multipart upload flow

Step 1: The client requests an upload

The client sends the file name, file size, and desired part size to the server. The part size cannot exceed 5 GB. A part size of 5 MB is recommended.

curl -X POST https://yourserver.com/init-upload \
  -H "Content-Type: application/json" \
  -d '{
    "fileName": "exampleobject.jpg",
    "fileSize": 104857600,
    "partSize": 5242880
  }'

Replace https://yourserver.com/init-upload with your server's initialization endpoint.

Step 2: The server initializes the upload and generates presigned URLs

The server:

  1. Calls InitiateMultipartUpload to get an upload ID.

  2. Calculates the number of parts based on the file size and part size.

  3. Generates a presigned URL for each part.

  4. Returns the upload ID and URL list to the client.

Record the upload ID on the server and map it to the file. You'll need it to verify and merge parts later. Store it in a cache or database.

Sample code for initiating a multipart upload and generating presigned URLs

Java

import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.InitiateMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadResult;

import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class InitAndGenerateURL {
    public static void main(String[] args) throws Throwable {
        long fileSize = 15 * 1024 * 1024L;  // 15 MB
        long partSize = 5 * 1024 * 1024L;   // 5 MB per part
        int totalParts = (int) ((fileSize + partSize - 1) / partSize);

        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        String region = "cn-hangzhou";
        String bucketName = "exampleBucket";
        String objectName = "exampleObject.jpeg";
        long expireTime = 3600 * 1000L; // 1 hour

        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        ClientBuilderConfiguration config = new ClientBuilderConfiguration();
        config.setSignatureVersion(SignVersion.V4);

        OSS ossClient = OSSClientBuilder.create()
                .endpoint(endpoint)
                .region(region)
                .credentialsProvider(credentialsProvider)
                .clientConfiguration(config)
                .build();

        InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucketName, objectName);
        InitiateMultipartUploadResult initResult = ossClient.initiateMultipartUpload(initRequest);
        String uploadId = initResult.getUploadId();
        System.out.println("Upload ID: " + uploadId);
        System.out.println("Total parts: " + totalParts);

        for (int i = 1; i <= totalParts; i++) {
            Map<String, String> headers = new HashMap<>();
            String signedUrl = generatePresignedUrl(ossClient, bucketName, objectName, HttpMethod.PUT,
                    expireTime, i, uploadId, headers);
            System.out.println("Part " + i + " URL: " + signedUrl);
        }

        ossClient.shutdown();
    }

    public static String generatePresignedUrl(OSS ossClient, String bucketName, String objectName,
                                              HttpMethod method, long expireTime, int partNum,
                                              String uploadId, Map<String, String> headers) {
        Date expiration = new Date(System.currentTimeMillis() + expireTime);
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, method);
        request.setExpiration(expiration);
        request.setHeaders(headers);
        request.addQueryParameter("partNumber", String.valueOf(partNum));
        request.addQueryParameter("uploadId", uploadId);
        URL url = ossClient.generatePresignedUrl(request);
        return url.toString();
    }
}

Go

package main

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"time"

	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
)

type SignedPart struct {
	PartNumber int    `json:"part_number"`
	URL        string `json:"url"`
	Method     string `json:"method"`
}

type UploadInitResponse struct {
	UploadId string       `json:"upload_id"`
	Parts    []SignedPart `json:"parts"`
}

var (
	region     string
	bucketName string
	objectName string
)

func init() {
	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
	flag.StringVar(&objectName, "object", "", "The name of the object.")
}

func main() {
	flag.Parse()

	fileSize := int64(15 * 1024 * 1024) // 15 MB
	partSize := int64(5 * 1024 * 1024)  // 5 MB per part
	totalParts := int((fileSize + partSize - 1) / partSize)

	cfg := oss.LoadDefaultConfig().
		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
		WithRegion(region)

	client := oss.NewClient(cfg)

	resp, err := client.InitiateMultipartUpload(context.TODO(), &oss.InitiateMultipartUploadRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(objectName),
	})
	if err != nil {
		log.Fatalf("Failed to initiate multipart upload: %v", err)
	}
	uploadId := resp.UploadId
	fmt.Println("Upload ID:", *uploadId)
	fmt.Println("Total parts:", totalParts)

	expire := time.Hour
	var parts []SignedPart
	for i := 1; i <= totalParts; i++ {
		req := &oss.UploadPartRequest{
			Bucket:     oss.Ptr(bucketName),
			Key:        oss.Ptr(objectName),
			PartNumber: int32(i),
			UploadId:   uploadId,
		}
		presignResult, err := client.Presign(context.TODO(), req, oss.PresignExpiration(time.Now().Add(expire)))
		if err != nil {
			log.Fatalf("Failed to generate signed URL for part %d: %v", i, err)
		}
		parts = append(parts, SignedPart{PartNumber: i, URL: presignResult.URL, Method: "PUT"})
	}

	out := UploadInitResponse{UploadId: *uploadId, Parts: parts}
	outJSON, _ := json.MarshalIndent(out, "", "  ")
	fmt.Println(string(outJSON))
}

Python

import argparse
import json
import alibabacloud_oss_v2 as oss

FILE_SIZE = 15 * 1024 * 1024  # 15 MB
PART_SIZE = 5 * 1024 * 1024   # 5 MB per part
EXPIRE_TIME = 3600             # seconds

def main():
    parser = argparse.ArgumentParser(description="presign multipart upload sample")
    parser.add_argument('--region', required=True)
    parser.add_argument('--bucket', required=True)
    parser.add_argument('--endpoint')
    parser.add_argument('--key', required=True)
    args = parser.parse_args()

    credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    cfg = oss.config.load_default()
    cfg.credentials_provider = credentials_provider
    cfg.region = args.region
    if args.endpoint is not None:
        cfg.endpoint = args.endpoint

    client = oss.Client(cfg)

    init_result = client.presign(oss.InitiateMultipartUploadRequest(
        bucket=args.bucket,
        key=args.key,
    ))

    import requests
    with requests.post(init_result.url, headers=init_result.signed_headers) as resp:
        obj = oss.InitiateMultipartUploadResult()
        oss.serde.deserialize_xml(xml_data=resp.content, obj=obj)
        upload_id = obj.upload_id

    total_parts = (FILE_SIZE + PART_SIZE - 1) // PART_SIZE
    parts = []

    for part_number in range(1, total_parts + 1):
        req = oss.UploadPartRequest(
            bucket=args.bucket,
            key=args.key,
            part_number=part_number,
            upload_id=upload_id,
            expiration_in_seconds=EXPIRE_TIME
        )
        presign_result = client.presign(req)
        parts.append({"part_number": part_number, "url": presign_result.url, "method": "PUT"})

    output = {"upload_id": upload_id, "parts": parts}
    print(json.dumps(output, indent=2))

if __name__ == "__main__":
    main()

Step 3: The client uploads parts

The client uses each presigned URL to upload the corresponding part via a PUT request. Concurrent uploads are supported.

Each URL is bound to a specific partNumber. Upload only the correct part data to each URL—parts cannot be mixed or skipped.

The following example uploads part 1:

curl -X PUT -T /path/to/local/file "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.jpg?partNumber=1&uploadId=BE2D0BC931BE4DE1B23F339AABFA49EE&x-oss-credential=LTAI********************%2F20250520%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-date=20250520T082728Z&x-oss-expires=3600&x-oss-signature=81f3d2e5eaa67c432291577ed20af3b3f60df05ab3cddedcdce168ef707f7ad0&x-oss-signature-version=OSS4-HMAC-SHA256"

Step 4: (Optional) The server verifies parts and completes the upload

After the client signals upload completion, the server can optionally call ListParts to verify that all parts are present and have the expected sizes, then calls CompleteMultipartUpload to merge the parts.

Use the upload ID recorded in step 2.

Java

import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.*;

import java.util.ArrayList;
import java.util.List;

public class VerifyAndComplete {
    public static void main(String[] args) throws Exception {
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        String bucketName = "examplebucket";
        String objectName = "exampleobject.jpeg";
        String uploadId = "4B78****************************";
        String region = "cn-hangzhou";

        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        ClientBuilderConfiguration config = new ClientBuilderConfiguration();
        config.setSignatureVersion(SignVersion.V4);

        OSS ossClient = OSSClientBuilder.create()
                .endpoint(endpoint)
                .region(region)
                .credentialsProvider(credentialsProvider)
                .clientConfiguration(config)
                .build();

        try {
            ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, objectName, uploadId);
            PartListing partListing = ossClient.listParts(listPartsRequest);

            List<PartETag> partETags = new ArrayList<>();
            for (PartSummary part : partListing.getParts()) {
                partETags.add(new PartETag(part.getPartNumber(), part.getETag()));
            }

            if (partETags.isEmpty()) {
                System.out.println("No uploaded parts found. Aborting merge.");
                return;
            }

            CompleteMultipartUploadRequest completeRequest =
                    new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
            CompleteMultipartUploadResult result = ossClient.completeMultipartUpload(completeRequest);
            System.out.println("Parts merged successfully!");
            System.out.println("ETag: " + result.getETag());

        } catch (Exception e) {
            System.err.println("Failed to merge parts: " + e.getMessage());
        } finally {
            if (ossClient != null) ossClient.shutdown();
        }
    }
}

Go

package main

import (
	"context"
	"flag"
	"log"

	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
)

var (
	region     string
	bucketName string
	objectName string
	uploadId   string
)

func init() {
	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
	flag.StringVar(&objectName, "object", "", "The name of the object.")
	flag.StringVar(&uploadId, "uploadId", "", "The upload ID.")
}

func main() {
	flag.Parse()

	if region == "" || bucketName == "" || objectName == "" || uploadId == "" {
		flag.PrintDefaults()
		log.Fatal("Missing required parameters.")
	}

	cfg := oss.LoadDefaultConfig().
		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
		WithRegion(region)

	client := oss.NewClient(cfg)

	partListResp, err := client.ListParts(context.TODO(), &oss.ListPartsRequest{
		Bucket:   oss.Ptr(bucketName),
		Key:      oss.Ptr(objectName),
		UploadId: oss.Ptr(uploadId),
	})
	if err != nil {
		log.Fatalf("failed to list parts: %v", err)
	}

	var parts []oss.UploadPart
	for _, p := range partListResp.Parts {
		parts = append(parts, oss.UploadPart{PartNumber: p.PartNumber, ETag: p.ETag})
	}

	completeResult, err := client.CompleteMultipartUpload(context.TODO(), &oss.CompleteMultipartUploadRequest{
		Bucket:   oss.Ptr(bucketName),
		Key:      oss.Ptr(objectName),
		UploadId: oss.Ptr(uploadId),
		CompleteMultipartUpload: &oss.CompleteMultipartUpload{Parts: parts},
	})
	if err != nil {
		log.Fatalf("failed to complete multipart upload: %v", err)
	}

	log.Println("Upload completed successfully.")
	log.Printf("Bucket: %s\n", oss.ToString(completeResult.Bucket))
	log.Printf("Key: %s\n", oss.ToString(completeResult.Key))
	log.Printf("ETag: %s\n", oss.ToString(completeResult.ETag))
}

Python

# -*- coding: utf-8 -*-
import argparse
import alibabacloud_oss_v2 as oss

def main():
    parser = argparse.ArgumentParser(description="complete multipart upload sample")
    parser.add_argument('--region', required=True)
    parser.add_argument('--bucket', required=True)
    parser.add_argument('--endpoint')
    parser.add_argument('--key', required=True)
    parser.add_argument('--upload_id', required=True)
    args = parser.parse_args()

    credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    cfg = oss.config.load_default()
    cfg.credentials_provider = credentials_provider
    cfg.region = args.region
    if args.endpoint:
        cfg.endpoint = args.endpoint

    client = oss.Client(cfg)

    try:
        list_parts_result = client.list_parts(oss.ListPartsRequest(
            bucket=args.bucket,
            key=args.key,
            upload_id=args.upload_id
        ))

        upload_parts = [
            oss.UploadPart(part_number=part.part_number, etag=part.etag)
            for part in list_parts_result.parts
        ]

        if not upload_parts:
            print("No uploaded parts found. Aborting operation.")
            return

        result = client.complete_multipart_upload(oss.CompleteMultipartUploadRequest(
            bucket=args.bucket,
            key=args.key,
            upload_id=args.upload_id,
            complete_multipart_upload=oss.CompleteMultipartUpload(parts=upload_parts)
        ))

        print("Merge successful!")
        print(f"ETag: {result.etag}")
        print(f"Bucket: {result.bucket}")
        print(f"Key: {result.key}")

    except Exception as e:
        print("Failed to merge parts:", e)

if __name__ == "__main__":
    main()

Set headers to control upload behavior

When generating a presigned URL, specify header parameters to enforce storage policies. For example, set x-oss-storage-class to control the storage class, or set Content-Type to restrict file types.

Important

Any headers included when generating the presigned URL must also be included in the PUT request. Omitting them causes a 403 error due to signature mismatch. For a full list of supported system headers, see PutObject. For custom headers, see Manage file metadata.

Security tip: Specifying Content-Type in the presigned URL prevents clients from uploading files of unexpected types. If the upload request uses a different Content-Type, OSS rejects it with a 403 error.

Step 1: Generate a presigned URL with headers (server side)

Java

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.internal.OSSHeaders;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.StorageClass;

import java.net.URL;
import java.util.*;

public class GetSignUrl {
    public static void main(String[] args) throws Throwable {
        String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        String bucketName = "examplebucket";
        String objectName = "exampleobject.txt";
        String region = "cn-hangzhou";

        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        OSS ossClient = OSSClientBuilder.create()
                .endpoint(endpoint)
                .credentialsProvider(credentialsProvider)
                .clientConfiguration(clientBuilderConfiguration)
                .region(region)
                .build();

        Map<String, String> headers = new HashMap<>();
        headers.put(OSSHeaders.STORAGE_CLASS, StorageClass.Standard.toString());
        headers.put(OSSHeaders.CONTENT_TYPE, "text/plain; charset=utf8");

        Map<String, String> userMetadata = new HashMap<>();
        userMetadata.put("key1", "value1");
        userMetadata.put("key2", "value2");

        URL signedUrl = null;
        try {
            Date expiration = new Date(new Date().getTime() + 3600 * 1000L);

            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.PUT);
            request.setExpiration(expiration);
            request.setHeaders(headers);
            request.setUserMetadata(userMetadata);

            signedUrl = ossClient.generatePresignedUrl(request);
            System.out.println("signed url for putObject: " + signedUrl);

        } catch (OSSException oe) {
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Error Message:" + oe.getErrorMessage());
        } catch (ClientException ce) {
            System.out.println("Error Message:" + ce.getMessage());
        }
    }
}

Go

package main

import (
	"context"
	"flag"
	"log"
	"time"

	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
)

var (
	region     string
	bucketName string
	objectName string
)

func init() {
	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
	flag.StringVar(&objectName, "object", "", "The name of the object.")
}

func main() {
	flag.Parse()

	cfg := oss.LoadDefaultConfig().
		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
		WithRegion(region)

	client := oss.NewClient(cfg)

	result, err := client.Presign(context.TODO(), &oss.PutObjectRequest{
		Bucket:       oss.Ptr(bucketName),
		Key:          oss.Ptr(objectName),
		ContentType:  oss.Ptr("text/plain;charset=utf8"),   // Must match the Content-Type in the PUT request.
		StorageClass: oss.StorageClassStandard,              // Must match the storage class in the PUT request.
		Metadata:     map[string]string{"key1": "value1", "key2": "value2"}, // Must match metadata in the PUT request.
	},
		oss.PresignExpires(10*time.Minute),
	)
	if err != nil {
		log.Fatalf("failed to presign PutObject: %v", err)
	}

	log.Printf("method: %v\n", result.Method)
	log.Printf("expiration: %v\n", result.Expiration)
	log.Printf("url: %v\n", result.URL)
	if len(result.SignedHeaders) > 0 {
		log.Printf("signed headers:\n")
		for k, v := range result.SignedHeaders {
			log.Printf("%v: %v\n", k, v)
		}
	}
}

Python

import argparse
import alibabacloud_oss_v2 as oss
from datetime import timedelta

parser = argparse.ArgumentParser(description="presign put object with headers sample")
parser.add_argument('--region', required=True)
parser.add_argument('--bucket', required=True)
parser.add_argument('--endpoint')
parser.add_argument('--key', required=True)

def main():
    args = parser.parse_args()

    credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    cfg = oss.config.load_default()
    cfg.credentials_provider = credentials_provider
    cfg.region = args.region
    if args.endpoint is not None:
        cfg.endpoint = args.endpoint

    client = oss.Client(cfg)

    pre_result = client.presign(oss.PutObjectRequest(
        bucket=args.bucket,
        key=args.key,
        content_type='text/plain;charset=utf8',
        storage_class='Standard',
        metadata={'key1': 'value1', 'key2': 'value2'}
    ), expires=timedelta(seconds=3600))

    print(f'method: {pre_result.method},'
          f' expiration: {pre_result.expiration.strftime("%Y-%m-%dT%H:%M:%S.000Z")},'
          f' url: {pre_result.url}')

    for key, value in pre_result.signed_headers.items():
        print(f'signed header - {key}: {value}')

if __name__ == "__main__":
    main()

Step 2: Upload with the matching headers (client side)

Pass the same headers that were included when generating the presigned URL.

curl

curl -X PUT \
     -H "Content-Type: text/plain;charset=utf8" \
     -H "x-oss-storage-class: Standard" \
     -H "x-oss-meta-key1: value1" \
     -H "x-oss-meta-key2: value2" \
     -T "C:\\Users\\demo.txt" \
     "https://exampleobject.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T083238Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI****************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a******************************************************"

Set an upload callback

Add callback parameters to the presigned URL so OSS notifies your application server after a successful upload. For details on how callbacks work, see Callback.

Step 1: Generate a presigned URL with callback parameters (server side)

Python

import argparse
import base64
import alibabacloud_oss_v2 as oss
from datetime import timedelta

parser = argparse.ArgumentParser(description="presign put object with callback sample")
parser.add_argument('--region', required=True)
parser.add_argument('--bucket', required=True)
parser.add_argument('--endpoint')
parser.add_argument('--key', required=True)

def main():
    args = parser.parse_args()

    credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    cfg = oss.config.load_default()
    cfg.credentials_provider = credentials_provider
    cfg.region = args.region
    if args.endpoint is not None:
        cfg.endpoint = args.endpoint

    client = oss.Client(cfg)

    call_back_url = "http://www.example.com/callback"
    callback = base64.b64encode(str(
        '{"callbackUrl":"' + call_back_url + '",'
        '"callbackBody":"bucket=${bucket}&object=${object}&my_var_1=${x:var1}&my_var_2=${x:var2}"}'
    ).encode()).decode()
    callback_var = base64.b64encode('{"x:var1":"value1","x:var2":"value2"}'.encode()).decode()

    pre_result = client.presign(oss.PutObjectRequest(
        bucket=args.bucket,
        key=args.key,
        callback=callback,
        callback_var=callback_var,
    ), expires=timedelta(seconds=3600))

    print(f'method: {pre_result.method},'
          f' expiration: {pre_result.expiration.strftime("%Y-%m-%dT%H:%M:%S.000Z")},'
          f' url: {pre_result.url}')

    for key, value in pre_result.signed_headers.items():
        print(f'signed header - {key}: {value}')

if __name__ == "__main__":
    main()

Go

package main

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"flag"
	"log"
	"time"

	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
)

var (
	region     string
	bucketName string
	objectName string
)

func init() {
	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
	flag.StringVar(&objectName, "object", "", "The name of the object.")
}

func main() {
	flag.Parse()

	cfg := oss.LoadDefaultConfig().
		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
		WithRegion(region)

	client := oss.NewClient(cfg)

	callbackMap := map[string]string{
		"callbackUrl":      "http://example.com:23450",
		"callbackBody":     "bucket=${bucket}&object=${object}&size=${size}&my_var_1=${x:my_var1}&my_var_2=${x:my_var2}",
		"callbackBodyType": "application/x-www-form-urlencoded",
	}

	callbackStr, err := json.Marshal(callbackMap)
	if err != nil {
		log.Fatalf("failed to marshal callback map: %v", err)
	}
	callbackBase64 := base64.StdEncoding.EncodeToString(callbackStr)

	callbackVarMap := map[string]string{
		"x:my_var1": "this is var 1",
		"x:my_var2": "this is var 2",
	}
	callbackVarStr, err := json.Marshal(callbackVarMap)
	if err != nil {
		log.Fatalf("failed to marshal callback var: %v", err)
	}
	callbackVarBase64 := base64.StdEncoding.EncodeToString(callbackVarStr)

	result, err := client.Presign(context.TODO(), &oss.PutObjectRequest{
		Bucket:      oss.Ptr(bucketName),
		Key:         oss.Ptr(objectName),
		Callback:    oss.Ptr(callbackBase64),
		CallbackVar: oss.Ptr(callbackVarBase64),
	},
		oss.PresignExpires(10*time.Minute),
	)
	if err != nil {
		log.Fatalf("failed to presign PutObject: %v", err)
	}

	log.Printf("method: %v\n", result.Method)
	log.Printf("expiration: %v\n", result.Expiration)
	log.Printf("url: %v\n", result.URL)
}

Step 2: Upload with the callback headers (client side)

curl

curl -X PUT \
     -H "x-oss-callback: eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9" \
     -H "x-oss-callback-var: eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==" \
     -T "C:\\Users\\demo.txt" \
     "https://exampleobject.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T083238Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI****************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a******************************************************"

FAQ

Why did my presigned URL expire earlier than expected?

Presigned URLs remain valid only while the credentials used to generate them are valid. If you generate a URL with STS temporary credentials, the URL expires when the STS token expires—regardless of the configured validity period. For example, if the STS token has 30 minutes remaining and you configure a 7-day validity period, the URL expires after 30 minutes. To avoid unexpected expiry, set the URL validity period to be shorter than the STS token's remaining lifetime.

Why am I getting a 403 Forbidden error?

A 403 error means signature verification failed. Check the following:

  • The request headers don't match the headers signed into the URL. If you set Content-Type or metadata when generating the URL, include the same values in the PUT request.

  • The URL has already expired—generate a new one.

  • The URL was modified after generation—use the URL exactly as returned by the SDK.

  • For Browser.js uploads: the browser automatically adds a Content-Type header. Always specify Content-Type when generating the presigned URL to prevent mismatches.

  • When using curl, enclose the URL in quotes to prevent the shell from misinterpreting special characters.

Why am I getting a 405 Method Not Allowed error?

This error means the upload request used the wrong HTTP method. Presigned URL uploads require a PUT request, not POST.

Why am I getting a CORS error?

Cross-Origin Resource Sharing (CORS) errors occur when the bucket's cross-origin policy is missing or misconfigured. Configure CORS rules for the bucket. See Configure cross-domain settings.

How do I let a third party perform operations beyond uploading?

Presigned URLs support only the specific operation (PutObject) for which they were generated. For broader permissions—such as listing or copying objects—use STS temporary credentials instead. See Access OSS using STS credentials.

How do I restrict which websites can access OSS resources?

Configure Referer-based hotlink protection to allow access only from specified origins. See Configure Referer-based hotlink protection.

What's next