> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gcore.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Add TLS certificates to a load balancer

export const MethodSection = ({children}) => children ?? null;

export const MethodSwitch = ({children}) => {
  const tabs = React.Children.toArray(children).map(c => {
    if (!c || !c.props) return null;
    if (c.props.id) return c;
    const inner = c.props.children;
    if (inner && inner.props && inner.props.id) return inner;
    return null;
  }).filter(Boolean);
  const firstId = tabs.length > 0 ? tabs[0].props.id : "";
  const [active, setActive] = React.useState(firstId);
  React.useEffect(() => {
    try {
      const saved = localStorage.getItem("gcore_docs_method");
      if (saved && tabs.find(t => t.props.id === saved)) {
        setActive(saved);
      }
    } catch (_) {}
  }, []);
  React.useEffect(() => {
    try {
      document.querySelectorAll("h2[id], h3[id]").forEach(heading => {
        const visible = heading.offsetParent !== null;
        document.querySelectorAll(`a[href="#${heading.id}"]`).forEach(link => {
          if (link.closest("h1,h2,h3,h4,h5,h6")) return;
          const li = link.closest("li");
          if (li) li.style.display = visible ? "" : "none";
        });
      });
    } catch (_) {}
    window.dispatchEvent(new Event("scroll"));
  }, [active]);
  const handleClick = id => {
    setActive(id);
    try {
      localStorage.setItem("gcore_docs_method", id);
    } catch (_) {}
  };
  return <div>
      <div className="not-prose flex gap-0 border-b border-zinc-200 dark:border-zinc-800 mb-8 mt-2" role="tablist">
        {tabs.map(tab => {
    const isActive = active === tab.props.id;
    return <button key={tab.props.id} role="tab" aria-selected={isActive} onClick={() => handleClick(tab.props.id)} className={["px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors cursor-pointer", isActive ? "border-primary text-primary" : "border-transparent text-zinc-500 hover:text-zinc-800 dark:hover:text-zinc-200"].join(" ")}>
              {tab.props.label}
            </button>;
  })}
      </div>

      {tabs.map(tab => <div key={tab.props.id} style={{
    display: active === tab.props.id ? "" : "none"
  }}>
          {tab.props.children}
        </div>)}
    </div>;
};

<MethodSwitch>
  <MethodSection id="portal" label="Customer Portal">
    <p>A single Load Balancer supports multiple TLS (Transport Layer Security) certificates, enabling several secure websites on one IP address. The Load Balancer selects the correct certificate based on the requested domain name.</p>

    ## SNI certificate selection

    <p>Load balancer listeners support the Server Name Indication (SNI) extension to handle multiple TLS certificates. Based on the SNI information provided by the client, the listener selects the appropriate TLS certificate to encrypt the connection. Once the TLS connection is terminated at the listener, the Load Balancer inspects the decrypted traffic and routes it to the proper server.</p>

    <p>Each listener supports its own TLS certificate, mapped to a specific hostname or domain.</p>

    ## Multiple TLS certificates

    <p>Listeners using the Terminated HTTPS and Prometheus protocols support multiple TLS certificates.</p>

    ### Step 1. Add certificates to Secrets Manager

    <p>Add the required certificates to Gcore Secrets Manager. If no certificates exist yet, the [upload guide](/cloud/secrets-manager/upload-a-pkcs12-file) covers PKCS12 configuration for HTTPS Load Balancers.</p>

    ### Step 2. Add certificates to a listener

    <p>After uploading the certificates, add them to a listener — during Load Balancer creation or from the settings of an existing one.</p>

    <Info>
      Secrets in use by a listener cannot be deleted, ensuring the Load Balancer can fail over successfully when needed. To remove a secret, first delete the listener that uses it, then delete the secret and recreate the listener if needed.
    </Info>

    <Tabs>
      <Tab title="During Load Balancer creation">
        <p>Create a new Load Balancer following the [setup guide](/cloud/networking/create-and-configure-a-load-balancer). In step 5, configure the listener as follows:</p>

        1. Enter a name for the listener.

        2. Select a protocol with the supported TLS encryption: **Terminated HTTPS** or **Prometheus**.

        3. Use a default port or specify a custom port from 1 to 65535.

        4. (Optional) To identify the origin of the user's IP address connecting to a web server via a Load Balancer, enable the **Add headers X-Forwarded-For, X-Forwarded-Port, X-Forwarded-Proto to requests** toggle.

        5. Select the **Default TLS Certificate** — the certificate used when no SNI-matching certificate is configured for the requested domain.

        6. Select the SNI Certificates stored in Secrets Manager.

        7. Set the **Connection limit** — the maximum number of simultaneous connections the listener can handle.

        8. (Optional) In the **Allowed CIDRs** field, specify the IP addresses allowed to access the content. The load balancer blocks requests from any IP outside the specified range.

        9. (Optional) Configure basic authentication for HTTP traffic to restrict unauthorized access. Click **Add user** and specify the following credentials:

        * **Enter username**: the username required to access the resource.

        * **Password**: specify a plain-text password or provide an encrypted password.

        10. Click **Create Listener**.

        <Frame>
          <img src="https://mintcdn.com/gcore/yrEa6OGy4Q_zWlXb/images/docs/cloud/networking/load-balancers/add-certificates-to-load-balancer/add-listener-dialog.png?fit=max&auto=format&n=yrEa6OGy4Q_zWlXb&q=85&s=78d3f730b3be53798ff5c630d67e306d" alt="Add Listener dialog with Terminated HTTPS protocol and certificate fields" width="927" height="811" data-path="images/docs/cloud/networking/load-balancers/add-certificates-to-load-balancer/add-listener-dialog.png" />
        </Frame>
      </Tab>

      <Tab title="From the Load Balancer settings">
        <p>To add TLS termination to an existing Load Balancer without creating a new one, update it as follows:</p>

        1. In the [Gcore Customer Portal](https://portal.gcore.com), navigate to the **Cloud** page and click **Networking**.

        2. Open the **Load Balancers** page.

        <Frame>
          <img src="https://mintcdn.com/gcore/yrEa6OGy4Q_zWlXb/images/docs/cloud/networking/load-balancers/add-certificates-to-load-balancer/load-balancers-page.png?fit=max&auto=format&n=yrEa6OGy4Q_zWlXb&q=85&s=272d2a200a7cb17fd4146e6e1666c7ad" alt="Load Balancers page in Customer Portal" width="1068" height="515" data-path="images/docs/cloud/networking/load-balancers/add-certificates-to-load-balancer/load-balancers-page.png" />
        </Frame>

        3. Find the Load Balancer to configure and click its name to open it.

        4. Navigate to the **Listeners** tab and click **Add Listener**.

        <Frame>
          <img src="https://mintcdn.com/gcore/yrEa6OGy4Q_zWlXb/images/docs/cloud/networking/load-balancers/add-certificates-to-load-balancer/listeners-tab.png?fit=max&auto=format&n=yrEa6OGy4Q_zWlXb&q=85&s=01ce3d34f12ad4d700101b4f8135eb83" alt="Listener tab in the Load Balancer settings" width="1079" height="520" data-path="images/docs/cloud/networking/load-balancers/add-certificates-to-load-balancer/listeners-tab.png" />
        </Frame>

        5. To configure the listener, follow the same steps as in the **During Load Balancer creation** tab.
      </Tab>
    </Tabs>

    ## Encrypted password

    <p>When configuring basic authentication, specify an encrypted password instead of plain text. To generate a hash using the [MKPasswd](https://www.unix.com/man-page/linux/1/mkpasswd/) utility:</p>

    1. Check the available encryption types:

    ```sh theme={null}
    docker run --rm yardalgedal/mkpasswd -m help
    ```

    2. Select an encryption method and generate the password hash. To create a bcrypt hash, run:

    ```sh theme={null}
    docker run --rm yardalgedal/mkpasswd -m bcrypt mypassword
    ```

    3. Insert the hash into the **Encrypted password** field:

    <Frame>
      <img src="https://mintcdn.com/gcore/yrEa6OGy4Q_zWlXb/images/docs/cloud/networking/load-balancers/add-certificates-to-load-balancer/encrypted-password-field.png?fit=max&auto=format&n=yrEa6OGy4Q_zWlXb&q=85&s=7ac0a8ed83109d94df978108c752585d" alt="Encrypted password in listener settings" width="917" height="709" data-path="images/docs/cloud/networking/load-balancers/add-certificates-to-load-balancer/encrypted-password-field.png" />
    </Frame>
  </MethodSection>

  <MethodSection id="api" label="REST API">
    TLS termination on a load balancer requires two resources: a certificate stored in Secrets Manager and a `TERMINATED_HTTPS` listener that references it. A listener accepts one default certificate and multiple SNI certificates — the load balancer selects the correct certificate for each connection based on the domain name in the TLS handshake.

    <Info>
      An [API token](/account-settings/api-tokens) is required, along with a [project ID](/api-reference/cloud/projects/list-projects) and [region ID](/api-reference/cloud/regions/list-regions).
    </Info>

    Open a bash terminal and set these as environment variables before running the examples:

    ```bash theme={null}
    export GCORE_API_KEY="{YOUR_API_KEY}"
    export GCORE_CLOUD_PROJECT_ID="{YOUR_PROJECT_ID}"
    export GCORE_CLOUD_REGION_ID="{YOUR_REGION_ID}"
    export LB_ID="{YOUR_LOAD_BALANCER_ID}"
    ```

    <Info>
      `LB_ID` is the UUID of an existing [load balancer](/cloud/networking/create-and-configure-a-load-balancer).
    </Info>

    ## Quickstart

    The scripts below upload a TLS certificate to Secrets Manager and create a `TERMINATED_HTTPS` listener on an existing load balancer.

    <Tabs>
      <Tab title="Python SDK">
        ```python theme={null}
        import os
        from gcore import Gcore
        from gcore.types.cloud import secret_upload_tls_certificate_params

        client = Gcore()
        lb_id = os.environ["LB_ID"]

        # Step 1. Upload the default TLS certificate to Secrets Manager
        cert_pem = open("/path/to/certificate.pem").read()
        key_pem = open("/path/to/private-key.pem").read()
        chain_pem = open("/path/to/chain.pem").read()

        secret = client.cloud.secrets.upload_tls_certificate_and_poll(
            name="my-lb-cert",
            payload=secret_upload_tls_certificate_params.Payload(
                certificate=cert_pem,
                private_key=key_pem,
                certificate_chain=chain_pem,
            ),
        )
        print(f"Secret: {secret.id}  status: {secret.status}")

        # Step 2. Create a TERMINATED_HTTPS listener with the certificate
        listener = client.cloud.load_balancers.listeners.create_and_poll(
            load_balancer_id=lb_id,
            name="my-https-listener",
            protocol="TERMINATED_HTTPS",
            protocol_port=443,
            secret_id=secret.id,
        )
        print(f"Listener: {listener.id}  protocol: {listener.protocol}  port: {listener.protocol_port}")
        ```
      </Tab>

      <Tab title="Go SDK">
        ```go theme={null}
        package main

        import (
        	"context"
        	"fmt"
        	"log"
        	"os"
        	gcore "github.com/G-Core/gcore-go"
        	"github.com/G-Core/gcore-go/cloud"
        )

        func readFile(path string) string {
        	data, err := os.ReadFile(path)
        	if err != nil {
        		log.Fatal(err)
        	}
        	return string(data)
        }

        func main() {
        	lbID := os.Getenv("LB_ID")

        	client := gcore.NewClient()
        	ctx := context.Background()

        	// Step 1. Upload the default TLS certificate to Secrets Manager
        	secret, err := client.Cloud.Secrets.UploadTlsCertificateAndPoll(ctx, cloud.SecretUploadTlsCertificateParams{
        		Name: "my-lb-cert",
        		Payload: cloud.SecretUploadTlsCertificateParamsPayload{
        			Certificate:      readFile("/path/to/certificate.pem"),
        			PrivateKey:       readFile("/path/to/private-key.pem"),
        			CertificateChain: readFile("/path/to/chain.pem"),
        		},
        	})
        	if err != nil {
        		log.Fatal(err)
        	}
        	fmt.Printf("Secret: %s  status: %s\n", secret.ID, secret.Status)

        	// Step 2. Create a TERMINATED_HTTPS listener with the certificate
        	listener, err := client.Cloud.LoadBalancers.Listeners.NewAndPoll(ctx, cloud.LoadBalancerListenerNewParams{
        		LoadBalancerID: lbID,
        		Name:           "my-https-listener",
        		Protocol:       cloud.LbListenerProtocolTerminatedHTTPS,
        		ProtocolPort:   443,
        		SecretID:       cloud.LoadBalancerListenerNewParamsSecretID(secret.ID),
        	})
        	if err != nil {
        		log.Fatal(err)
        	}
        	fmt.Printf("Listener: %s  protocol: %s  port: %d\n",
        		listener.ID, listener.Protocol, listener.ProtocolPort)
        }
        ```
      </Tab>
    </Tabs>

    ## Step-by-step

    <p>Steps 1–4 below upload a certificate to Secrets Manager, list existing certificates, attach the certificate to a new listener, and add SNI certificates for multi-domain support.</p>

    <Accordion title="Show all steps">
      ### Step 1. Upload a TLS certificate to Secrets Manager

      Certificates are stored in Gcore Secrets Manager before being referenced by a load balancer listener. Each certificate requires three PEM-encoded fields: the certificate itself, the private key, and the certificate chain.

      | Parameter                   | Required | Description                                                                                           |
      | --------------------------- | -------- | ----------------------------------------------------------------------------------------------------- |
      | `name`                      | Yes      | Display name for the certificate in Secrets Manager                                                   |
      | `payload.certificate`       | Yes      | SSL certificate in PEM format                                                                         |
      | `payload.private_key`       | Yes      | Private key in PEM format                                                                             |
      | `payload.certificate_chain` | Yes      | Intermediate and root CA certificates in PEM format; use the certificate itself for self-signed certs |
      | `expiration`                | No       | ISO 8601 datetime after which the secret expires; omit for no expiration                              |

      <Tabs>
        <Tab title="Python SDK">
          ```python theme={null}
          import os
          from gcore import Gcore
          from gcore.types.cloud import secret_upload_tls_certificate_params

          client = Gcore()

          cert_pem = open("/path/to/certificate.pem").read()
          key_pem = open("/path/to/private-key.pem").read()
          chain_pem = open("/path/to/chain.pem").read()

          secret = client.cloud.secrets.upload_tls_certificate_and_poll(
              name="my-lb-cert",
              expiration="2030-12-31T23:59:59",
              payload=secret_upload_tls_certificate_params.Payload(
                  certificate=cert_pem,
                  private_key=key_pem,
                  certificate_chain=chain_pem,
              ),
          )
          print(f"Secret ID: {secret.id}  status: {secret.status}  type: {secret.secret_type}")
          ```
        </Tab>

        <Tab title="Go SDK">
          ```go theme={null}
          secret, err := client.Cloud.Secrets.UploadTlsCertificateAndPoll(ctx, cloud.SecretUploadTlsCertificateParams{
          	Name: "my-lb-cert",
          	Payload: cloud.SecretUploadTlsCertificateParamsPayload{
          		Certificate:      readFile("/path/to/certificate.pem"),
          		PrivateKey:       readFile("/path/to/private-key.pem"),
          		CertificateChain: readFile("/path/to/chain.pem"),
          	},
          })
          if err != nil {
          	log.Fatal(err)
          }
          fmt.Printf("Secret ID: %s  status: %s\n", secret.ID, secret.Status)
          ```
        </Tab>

        <Tab title="curl">
          ```bash theme={null}
          # Read PEM files and escape newlines for JSON
          CERT_PEM=$(awk '{printf "%s\\n", $0}' /path/to/certificate.pem)
          KEY_PEM=$(awk '{printf "%s\\n", $0}' /path/to/private-key.pem)
          CHAIN_PEM=$(awk '{printf "%s\\n", $0}' /path/to/chain.pem)

          curl -X POST "https://api.gcore.com/cloud/v2/secrets/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID" \
            -H "Authorization: APIKey $GCORE_API_KEY" \
            -H "Content-Type: application/json" \
            -d "{
              \"name\": \"my-lb-cert\",
              \"expiration\": \"2030-12-31T23:59:59\",
              \"payload\": {
                \"certificate\": \"$CERT_PEM\",
                \"private_key\": \"$KEY_PEM\",
                \"certificate_chain\": \"$CHAIN_PEM\"
              }
            }"
          ```

          The API returns a task ID:

          ```json theme={null}
          {
            "tasks": ["9d5289af-7a66-4419-ae20-8c0521b7f145"]   // save as TASK_ID
          }
          ```

          Poll <code>GET /cloud/v1/tasks/{task_id}</code> every 5 seconds until `state` is `FINISHED`:

          ```bash theme={null}
          curl "https://api.gcore.com/cloud/v1/tasks/$TASK_ID" \
            -H "Authorization: APIKey $GCORE_API_KEY"
          ```

          When complete:

          ```json theme={null}
          {
            "state": "FINISHED",
            "created_resources": {
              "secrets": ["04c518a0-738e-4fc6-b045-32a779ef83b3"]   // save as SECRET_ID
            }
          }
          ```
        </Tab>
      </Tabs>

      The [secrets API](/api-reference/cloud#tag/Secrets/operation/SecretsViewSetV2.post) confirms creation with `status: ACTIVE` and `secret_type: certificate`.

      ### Step 2. List certificates in Secrets Manager

      Listing secrets returns all certificates stored in the current project and region.

      <Tabs>
        <Tab title="Python SDK">
          ```python theme={null}
          page = client.cloud.secrets.list()
          for s in page.results:
              print(f"  {s.id}  {s.name}  {s.status}")
          ```
        </Tab>

        <Tab title="Go SDK">
          ```go theme={null}
          secrets, err := client.Cloud.Secrets.List(ctx, cloud.SecretListParams{})
          if err != nil {
          	log.Fatal(err)
          }
          for _, s := range secrets.Results {
          	fmt.Printf("  %s  %s  %s\n", s.ID, s.Name, s.Status)
          }
          ```
        </Tab>

        <Tab title="curl">
          ```bash theme={null}
          curl "https://api.gcore.com/cloud/v1/secrets/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID" \
            -H "Authorization: APIKey $GCORE_API_KEY"
          ```

          The API returns:

          ```json theme={null}
          {
            "count": 1,
            "results": [
              {
                "id": "04c518a0-738e-4fc6-b045-32a779ef83b3",
                "name": "my-lb-cert",
                "status": "ACTIVE",
                "secret_type": "certificate",
                "created": "2026-05-30T19:06:26",
                "expiration": "2030-12-31T23:59:59"
              }
            ]
          }
          ```
        </Tab>
      </Tabs>

      The [secrets API](/api-reference/cloud#tag/Secrets/operation/SecretListViewSet.get) returns all secrets regardless of type; filter by `secret_type: certificate` for TLS certificates.

      ### Step 3. Create a listener with the TLS certificate

      A `TERMINATED_HTTPS` listener decrypts TLS traffic at the load balancer and forwards plain HTTP to backend pool members. The `secret_id` optionally sets the default certificate — the one used when no SNI certificate matches the requested domain.

      | Parameter          | Required | Description                                                                                                     |
      | ------------------ | -------- | --------------------------------------------------------------------------------------------------------------- |
      | `load_balancer_id` | Yes      | ID of the existing load balancer                                                                                |
      | `name`             | Yes      | Listener name                                                                                                   |
      | `protocol`         | Yes      | Must be `TERMINATED_HTTPS` for TLS termination                                                                  |
      | `protocol_port`    | Yes      | Port number (1-65535); use `443` for standard HTTPS                                                             |
      | `secret_id`        | No       | ID of the default TLS certificate from Secrets Manager; pass `""` to use only SNI certificates with no fallback |
      | `sni_secret_id`    | No       | List of additional certificate IDs for SNI-based selection                                                      |

      <Tabs>
        <Tab title="Python SDK">
          ```python theme={null}
          listener = client.cloud.load_balancers.listeners.create_and_poll(
              load_balancer_id=lb_id,
              name="my-https-listener",
              protocol="TERMINATED_HTTPS",
              protocol_port=443,
              secret_id=secret.id,
          )
          print(f"Listener: {listener.id}  status: {listener.provisioning_status}")
          print(f"Default certificate: {listener.secret_id}")
          ```
        </Tab>

        <Tab title="Go SDK">
          ```go theme={null}
          listener, err := client.Cloud.LoadBalancers.Listeners.NewAndPoll(ctx, cloud.LoadBalancerListenerNewParams{
          	LoadBalancerID: lbID,
          	Name:           "my-https-listener",
          	Protocol:       cloud.LbListenerProtocolTerminatedHTTPS,
          	ProtocolPort:   443,
          	SecretID:       cloud.LoadBalancerListenerNewParamsSecretID(secret.ID),
          })
          if err != nil {
          	log.Fatal(err)
          }
          fmt.Printf("Listener: %s  status: %s\n", listener.ID, listener.ProvisioningStatus)
          fmt.Printf("Default certificate: %s\n", listener.SecretID)
          ```
        </Tab>

        <Tab title="curl">
          ```bash theme={null}
          curl -X POST "https://api.gcore.com/cloud/v1/lblisteners/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID" \
            -H "Authorization: APIKey $GCORE_API_KEY" \
            -H "Content-Type: application/json" \
            -d "{
              \"load_balancer_id\": \"$LB_ID\",
              \"name\": \"my-https-listener\",
              \"protocol\": \"TERMINATED_HTTPS\",
              \"protocol_port\": 443,
              \"secret_id\": \"$SECRET_ID\"
            }"
          ```

          The API returns a task ID:

          ```json theme={null}
          {
            "tasks": ["4cd68984-9e6b-4633-bc69-c862cca8abf7"]   // save as TASK_ID
          }
          ```

          Poll every 5 seconds until `state` is `FINISHED`. The completed task contains the new listener ID:

          ```json theme={null}
          {
            "state": "FINISHED",
            "created_resources": {
              "listeners": ["38db0600-8173-4386-8cf6-3ab143bbf1c8"]   // save as LISTENER_ID
            }
          }
          ```

          Verify the result:

          ```bash theme={null}
          curl "https://api.gcore.com/cloud/v1/lblisteners/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID/$LISTENER_ID" \
            -H "Authorization: APIKey $GCORE_API_KEY"
          ```

          ```json theme={null}
          {
            "id": "38db0600-8173-4386-8cf6-3ab143bbf1c8",
            "name": "my-https-listener",
            "protocol": "TERMINATED_HTTPS",
            "protocol_port": 443,
            "secret_id": "04c518a0-738e-4fc6-b045-32a779ef83b3",
            "sni_secret_id": [],
            "provisioning_status": "ACTIVE",
            "operating_status": "ONLINE"
          }
          ```
        </Tab>
      </Tabs>

      The [create listener](/api-reference/cloud#tag/Load-Balancer-Listeners/operation/LoadBalancerListenerViewSet.post) endpoint also supports the `PROMETHEUS` protocol for TLS-secured metrics endpoints.

      ### Step 4. Add SNI certificates to an existing listener

      SNI certificates let a single listener serve different TLS certificates for different domain names. The load balancer matches the domain in the client's TLS handshake against the Subject Alternative Names (SANs) of each SNI certificate and serves the matching one; the default certificate from `secret_id` is used as fallback.

      | Parameter       | Required | Description                                                                    |
      | --------------- | -------- | ------------------------------------------------------------------------------ |
      | `sni_secret_id` | Yes      | List of certificate IDs to use for SNI matching; replaces the current SNI list |

      <Tabs>
        <Tab title="Python SDK">
          ```python theme={null}
          # Upload the SNI certificate first (same as Step 1)
          sni_secret = client.cloud.secrets.upload_tls_certificate_and_poll(
              name="my-sni-cert",
              payload=secret_upload_tls_certificate_params.Payload(
                  certificate=open("/path/to/sni-certificate.pem").read(),
                  private_key=open("/path/to/sni-private-key.pem").read(),
                  certificate_chain=open("/path/to/sni-chain.pem").read(),
              ),
          )

          tasks = client.cloud.load_balancers.listeners.update(
              listener_id=listener.id,
              sni_secret_id=[sni_secret.id],
          )
          task = client.cloud.tasks.poll(tasks.tasks[0])
          print(f"Update status: {task.state}")

          updated = client.cloud.load_balancers.listeners.get(listener_id=listener.id)
          print(f"SNI certificates: {updated.sni_secret_id}")
          ```
        </Tab>

        <Tab title="Go SDK">
          ```go theme={null}
          // Upload the SNI certificate first (same as Step 1)
          sniSecret, err := client.Cloud.Secrets.UploadTlsCertificateAndPoll(ctx, cloud.SecretUploadTlsCertificateParams{
          	Name: "my-sni-cert",
          	Payload: cloud.SecretUploadTlsCertificateParamsPayload{
          		Certificate:      readFile("/path/to/sni-certificate.pem"),
          		PrivateKey:       readFile("/path/to/sni-private-key.pem"),
          		CertificateChain: readFile("/path/to/sni-chain.pem"),
          	},
          })
          if err != nil {
          	log.Fatal(err)
          }

          updated, err := client.Cloud.LoadBalancers.Listeners.UpdateAndPoll(ctx, listenerID, cloud.LoadBalancerListenerUpdateParams{
          	SniSecretID: []string{sniSecret.ID},
          })
          if err != nil {
          	log.Fatal(err)
          }
          fmt.Printf("SNI certificates: %v\n", updated.SniSecretID)
          ```
        </Tab>

        <Tab title="curl">
          ```bash theme={null}
          curl -X PATCH "https://api.gcore.com/cloud/v2/lblisteners/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID/$LISTENER_ID" \
            -H "Authorization: APIKey $GCORE_API_KEY" \
            -H "Content-Type: application/json" \
            -d "{\"sni_secret_id\": [\"$SNI_SECRET_ID\"]}"
          ```

          The API returns a task ID:

          ```json theme={null}
          {
            "tasks": ["f910e568-4f04-4a9c-9924-d1ddb438b897"]
          }
          ```

          Poll every 5 seconds until `state` is `FINISHED`. Verify the result:

          ```bash theme={null}
          curl "https://api.gcore.com/cloud/v1/lblisteners/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID/$LISTENER_ID" \
            -H "Authorization: APIKey $GCORE_API_KEY"
          ```

          ```json theme={null}
          {
            "id": "38db0600-8173-4386-8cf6-3ab143bbf1c8",
            "protocol": "TERMINATED_HTTPS",
            "secret_id": "04c518a0-738e-4fc6-b045-32a779ef83b3",
            "sni_secret_id": ["a2395dad-02a6-4313-bbc4-38f0f0954011"]
          }
          ```
        </Tab>
      </Tabs>

      To add more SNI certificates later, include all existing IDs plus the new ones in `sni_secret_id` — the field replaces the full list, not appends to it. The [update listener](/api-reference/cloud#tag/Load-Balancer-Listeners/operation/LoadBalancerListenerUpdateViewSet.patch) endpoint also accepts `secret_id` to replace the default certificate.
    </Accordion>

    ## Clean up

    Delete the listener before the secret. A secret that is in use by a listener cannot be deleted — the API returns a `ValidationError`.

    <Tabs>
      <Tab title="Python SDK">
        ```python theme={null}
        # Delete the listener first
        client.cloud.load_balancers.listeners.delete_and_poll(listener_id=listener.id)
        print("Listener deleted.")

        # Then delete the secrets
        task_list = client.cloud.secrets.delete(secret_id=secret.id)
        client.cloud.tasks.poll(task_list.tasks[0])
        print("Default certificate deleted.")

        task_list = client.cloud.secrets.delete(secret_id=sni_secret.id)
        client.cloud.tasks.poll(task_list.tasks[0])
        print("SNI certificate deleted.")
        ```
      </Tab>

      <Tab title="Go SDK">
        ```go theme={null}
        // Delete the listener first
        if err := client.Cloud.LoadBalancers.Listeners.DeleteAndPoll(ctx, listenerID,
            cloud.LoadBalancerListenerDeleteParams{}); err != nil {
            log.Fatal(err)
        }
        fmt.Println("Listener deleted.")

        // Then delete the secrets
        if err := client.Cloud.Secrets.DeleteAndPoll(ctx, secret.ID,
            cloud.SecretDeleteParams{}); err != nil {
            log.Fatal(err)
        }
        fmt.Println("Default certificate deleted.")

        if err := client.Cloud.Secrets.DeleteAndPoll(ctx, sniSecret.ID,
            cloud.SecretDeleteParams{}); err != nil {
            log.Fatal(err)
        }
        fmt.Println("SNI certificate deleted.")
        ```
      </Tab>

      <Tab title="curl">
        ```bash theme={null}
        # Delete the listener first
        curl -X DELETE "https://api.gcore.com/cloud/v1/lblisteners/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID/$LISTENER_ID" \
          -H "Authorization: APIKey $GCORE_API_KEY"
        ```

        Poll until the delete task finishes. Then delete the secrets:

        ```bash theme={null}
        curl -X DELETE "https://api.gcore.com/cloud/v1/secrets/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID/$SECRET_ID" \
          -H "Authorization: APIKey $GCORE_API_KEY"

        curl -X DELETE "https://api.gcore.com/cloud/v1/secrets/$GCORE_CLOUD_PROJECT_ID/$GCORE_CLOUD_REGION_ID/$SNI_SECRET_ID" \
          -H "Authorization: APIKey $GCORE_API_KEY"
        ```
      </Tab>
    </Tabs>
  </MethodSection>

  <MethodSection id="terraform" label="Terraform">
    <p>Configure TLS termination on a listener by attaching certificates via `sni_secret_id` on [`gcore_cloud_load_balancer_listener`](https://registry.terraform.io/providers/G-Core/gcore/latest/docs/resources/cloud_load_balancer_listener).</p>

    <Info>
      An [API token](/account-settings/api-tokens) is required, along with a [project ID](/api-reference/cloud/projects/list-projects) and [region ID](/api-reference/cloud/regions/list-regions).
    </Info>

    ## TERMINATED\_HTTPS listener

    <p>Specify the default certificate in `secret_id` — the fallback used when no SNI certificate matches the requested domain. Pass `""` to omit the default; connections without a matching SNI certificate will fail. Add one or more SNI certificates in `sni_secret_id`; the load balancer matches the client's requested hostname against the Subject Alternative Names of each SNI certificate.</p>

    ```hcl theme={null}
    resource "gcore_cloud_secret" "tls" {
      project_id = var.project_id
      region_id  = var.region_id
      name       = "example-com-cert"

      payload_wo_version = 1
      payload = {
        certificate_wo       = file("${path.module}/certificate.pem")
        private_key_wo       = file("${path.module}/private_key.pem")
        certificate_chain_wo = file("${path.module}/certificate_chain.pem")
      }
    }

    resource "gcore_cloud_load_balancer_listener" "https" {
      project_id       = var.project_id
      region_id        = var.region_id
      load_balancer_id = var.load_balancer_id
      name             = "https-listener"
      protocol         = "TERMINATED_HTTPS"
      protocol_port    = 443
      secret_id        = ""
      sni_secret_id    = [gcore_cloud_secret.tls.id]
    }

    output "listener_id" {
      value = gcore_cloud_load_balancer_listener.https.id
    }
    ```

    <p>Set `var.load_balancer_id` to the ID of an existing [load balancer](/cloud/networking/create-and-configure-a-load-balancer).</p>

    ## Additional SNI certificates

    <p>Pass all certificate IDs together — `sni_secret_id` replaces the full list on each apply:</p>

    ```hcl theme={null}
    resource "gcore_cloud_secret" "sni_extra" {
      project_id = var.project_id
      region_id  = var.region_id
      name       = "extra-domain-cert"

      payload_wo_version = 1
      payload = {
        certificate_wo       = file("${path.module}/extra-certificate.pem")
        private_key_wo       = file("${path.module}/extra-private_key.pem")
        certificate_chain_wo = file("${path.module}/extra-certificate_chain.pem")
      }
    }

    resource "gcore_cloud_load_balancer_listener" "https" {
      ...
      sni_secret_id = [
        gcore_cloud_secret.tls.id,
        gcore_cloud_secret.sni_extra.id,
      ]
    }
    ```

    ## Certificate rotation

    <p>To replace a certificate, increment `payload_wo_version` on the corresponding `gcore_cloud_secret` resource — Terraform destroys the old secret and creates a new one. The listener re-reads the new ID via the reference.</p>

    <Warning>
      Rotating a certificate destroys the existing secret and creates a replacement. The listener is updated automatically because `sni_secret_id` references `gcore_cloud_secret.tls.id` — Terraform updates the listener in the correct order.
    </Warning>

    ## Delete

    <p>Remove the listener block first, then the secret blocks, and run `terraform apply` — Terraform deletes the listener before the secrets to satisfy the API constraint that a secret in use cannot be deleted.</p>

    ```hcl theme={null}
    # Step 1: comment out or remove the listener block and apply
    # resource "gcore_cloud_load_balancer_listener" "https" { ... }

    # Step 2: after the listener is gone, comment out or remove the secret blocks and apply
    # resource "gcore_cloud_secret" "tls" { ... }
    # resource "gcore_cloud_secret" "sni_extra" { ... }
    ```

    ## Import

    <p>To bring an existing listener under Terraform management:</p>

    ```bash theme={null}
    terraform import gcore_cloud_load_balancer_listener.https '{PROJECT_ID}/{REGION_ID}/{LISTENER_ID}'
    ```
  </MethodSection>
</MethodSwitch>
