All Products
Search
Document Center

Object Storage Service:Attach a custom domain name (Python SDK V2)

Last Updated:Mar 20, 2026

After you upload objects to a bucket, OSS automatically generates URLs based on the bucket's public endpoint. To serve those objects under a custom domain name, add a CNAME record that maps your domain to the bucket.

The OSS Python SDK V2 supports the full CNAME lifecycle:

  1. Create a CNAME token (domain ownership verification)

  2. Get a CNAME token

  3. Add a CNAME record (with optional SSL certificate)

  4. List CNAME records

  5. Delete a CNAME record

Prerequisites

Before you begin, ensure that you have:

  • An OSS bucket

  • A custom domain name that you own and can manage DNS records for

  • The alibabacloud_oss_v2 Python package installed

  • OSS credentials set as environment variables (OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET)

Usage notes

  • The sample code in this topic uses the China (Hangzhou) region (cn-hangzhou) and a public endpoint by default. To access OSS from other Alibaba Cloud services in the same region, use an internal endpoint. For supported regions and endpoints, see Regions and endpoints.

How it works

Binding a custom domain requires domain ownership verification before OSS accepts the CNAME record:

  1. Call create_cname_token to get a CNAME token from OSS.

  2. Add the token as a DNS TXT record at your domain registrar to prove ownership.

  3. After DNS propagation, call put_cname to register the CNAME record on the bucket.

  4. Use list_cname to confirm the binding is active.

  5. Use delete_cname to remove the record when needed.

Note: DNS changes can take minutes to hours to propagate. If put_cname fails immediately after adding the TXT record, wait for propagation and retry.

Create a CNAME token

OSS issues a CNAME token for domain ownership verification. Add the token as a DNS TXT record at your domain registrar before calling put_cname.

Each example in this topic uses the same client initialization pattern: credentials are loaded from environment variables, and the region and optional endpoint come from command-line arguments.

import argparse
import alibabacloud_oss_v2 as oss

parser = argparse.ArgumentParser(description="create cname token 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 names that other services can use to access OSS')
parser.add_argument('--domain', help='The custom domain name.', 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)

    result = client.create_cname_token(oss.CreateCnameTokenRequest(
            bucket=args.bucket,
            bucket_cname_configuration=oss.BucketCnameConfiguration(
                cname=oss.Cname(
                    domain=args.domain,
                ),
            ),
    ))

    print(f'status code: {result.status_code},'
          f' request id: {result.request_id},'
          f' cname: {result.cname_token.cname if hasattr(result.cname_token, "cname") else "Not set"},'
          f' token: {result.cname_token.token if hasattr(result.cname_token, "token") else "Not set"},'
          f' expire time: {result.cname_token.expire_time.strftime("%Y-%m-%dT%H:%M:%S.000Z") if hasattr(result.cname_token, "expire_time") else "Not set"},'
          f' bucket: {result.cname_token.bucket if hasattr(result.cname_token, "bucket") else "Not set"},'
          )

if __name__ == "__main__":
    main()

A successful response returns HTTP 200 with the token value in result.cname_token.token and its expiration time in result.cname_token.expire_time. Add this token as a DNS TXT record before it expires.

For the complete sample, see create_cname_token.py.

Get a CNAME token

Retrieve an existing CNAME token by domain name to check its value or expiration.

import argparse
import alibabacloud_oss_v2 as oss

parser = argparse.ArgumentParser(description="get cname token 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 names that other services can use to access OSS')
parser.add_argument('--cname', help='The name of the CNAME record that is mapped to the bucket.', 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)

    result = client.get_cname_token(oss.GetCnameTokenRequest(
            bucket=args.bucket,
            cname=args.cname,
    ))

    print(f'status code: {result.status_code},'
          f' request id: {result.request_id},'
          f' cname: {result.cname_token.cname if hasattr(result.cname_token, "cname") else "Not set"},'
          f' token: {result.cname_token.token if hasattr(result.cname_token, "token") else "Not set"},'
          f' expire time: {result.cname_token.expire_time.strftime("%Y-%m-%dT%H:%M:%S.000Z") if hasattr(result.cname_token, "expire_time") else "Not set"},'
          f' bucket: {result.cname_token.bucket if hasattr(result.cname_token, "bucket") else "Not set"},'
          )

if __name__ == "__main__":
    main()

For the complete sample, see get_cname_token.py.

Add a CNAME record

After DNS ownership verification is complete, call put_cname to bind the custom domain to the bucket. Optionally, associate an SSL certificate at the same time.

The following example binds a domain and attaches an SSL certificate in a single call. To bind without a certificate, omit the certificate_configuration parameter.

import argparse
import alibabacloud_oss_v2 as oss

parser = argparse.ArgumentParser(description="put cname 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 names that other services can use to access OSS')
parser.add_argument('--domain', help='The custom domain name.', 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)

    cert_id = '493****-cn-hangzhou '          # The ID of the SSL certificate.
    certificate = '''-----BEGIN CERTIFICATE-----
MIIDhDCCAmwCCQCFs8ixARsyrDANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMC
**** -----END CERTIFICATE-----'''             # The content of the SSL certificate.
    private_key = '''-----BEGIN CERTIFICATE-----
MIIDhDCCAmwCCQCFs8ixARsyrDANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMC
**** -----END CERTIFICATE-----'''             # The content of the private key.
    previous_cert_id = '493****-cn-hangzhou ' # The ID of the original SSL certificate.
    force = True                               # Forcibly overwrite the original SSL certificate.

    result = client.put_cname(oss.PutCnameRequest(
            bucket=args.bucket,
            bucket_cname_configuration=oss.BucketCnameConfiguration(
                cname=oss.Cname(
                    domain=args.domain,
                    certificate_configuration=oss.CertificateConfiguration(
                        certificate=certificate,
                        private_key=private_key,
                        previous_cert_id=previous_cert_id,
                        force=force,
                        cert_id=cert_id,
                    ),
                ),
            ),
    ))

    print(f'status code: {result.status_code},'
          f' request id: {result.request_id},'
          )

    # To delete the SSL certificate, uncomment the following block:
    # result = client.put_cname(oss.PutCnameRequest(
    #         bucket=args.bucket,
    #         bucket_cname_configuration=oss.BucketCnameConfiguration(
    #             cname=oss.Cname(
    #                 domain=args.domain,
    #                 certificate_configuration=oss.CertificateConfiguration(
    #                     delete_certificate=True,
    #                 ),
    #             ),
    #         ),
    # ))

if __name__ == "__main__":
    main()

The CertificateConfiguration fields are:

FieldDescription
certificateThe content of the SSL certificate
private_keyThe content of the private key
cert_idThe ID of the new SSL certificate
previous_cert_idThe ID of the original SSL certificate
forceForcibly overwrite the original SSL certificate
delete_certificateSet to True to remove the SSL certificate from the domain

For the complete sample, see put_cname.py.

List CNAME records

Retrieve all CNAME records bound to a bucket, including their status and SSL certificate details.

import argparse
import alibabacloud_oss_v2 as oss

parser = argparse.ArgumentParser(description="list cname 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 names that other services can use to access OSS')

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)

    result = client.list_cname(oss.ListCnameRequest(
            bucket=args.bucket,
    ))

    print(f'status code: {result.status_code},'
          f' request id: {result.request_id},'
          f' bucket: {result.bucket},'
          f' owner: {result.owner},'
          )

    if result.cnames:
        for r in result.cnames:
            certificate_info = (f"fingerprint: {r.certificate.fingerprint}, "
                                f"valid start date: {r.certificate.valid_start_date}, "
                                f"valid end date: {r.certificate.valid_end_date}, "
                                f"type: {r.certificate.type}, "
                                f"cert id: {r.certificate.cert_id}, "
                                f"status: {r.certificate.status}, "
                                f"creation date: {r.certificate.creation_date}") if r.certificate else "No certificate"
            print(f'domain: {r.domain}, '
                  f'last modified: {r.last_modified}, '
                  f'status: {r.status}, '
                  f'certificate: {certificate_info}'
                  )

if __name__ == "__main__":
    main()

A successful response lists each bound domain with its status field. A domain with status: Enabled is active and resolving through the bucket. If the certificate field is None, no SSL certificate is attached.

For the complete sample, see list_cname.py.

Delete a CNAME record

Remove a CNAME record to stop routing the custom domain to the bucket.

import argparse
import alibabacloud_oss_v2 as oss

parser = argparse.ArgumentParser(description="delete cname 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 names that other services can use to access OSS')
parser.add_argument('--domain', help='The custom domain name.', 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)

    result = client.delete_cname(oss.DeleteCnameRequest(
            bucket=args.bucket,
            bucket_cname_configuration=oss.BucketCnameConfiguration(
                cname=oss.Cname(
                    domain=args.domain,
                ),
            ),
    ))

    print(f'status code: {result.status_code},'
          f' request id: {result.request_id},'
          )

if __name__ == "__main__":
    main()

For the complete sample, see delete_cname.py.