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

# SSL & TLS

> Configure SSL for connections to a TeeSQL cluster, including the RA-TLS handshake and per-client setup.

TeeSQL accepts TLS connections on port `5433` only. The sidecar terminates a mutual Remote Attestation TLS (RA-TLS) handshake — the server presents a self-signed X.509 certificate with a TDX attestation quote in a custom extension, and (in production) requires the client to present an equivalent attested certificate.

Plaintext Postgres on port `5432` is bound to localhost inside the CVM and is unreachable from outside.

## Prerequisites

* A TeeSQL cluster host and database
* For mutual RA-TLS (production): your application running inside an Intel TDX CVM with a dstack guest agent at `/var/run/dstack.sock`
* For local development: the [dstack simulator](https://github.com/Dstack-TEE/dstack/tree/main/sdk/simulator)

## Required SSL mode

The mode you set depends on which connection path you use. There is no single "TeeSQL `sslmode`" — the answer is determined by where the TLS handshake terminates.

| Path                                                               | `sslmode`     | Why                                                                                                           |
| ------------------------------------------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------- |
| RA-TLS client library (psycopg-ra-tls, prisma-ra-tls, sqlx-ra-tls) | `disable`     | The library runs a localhost forwarder that owns TLS. The driver speaks plain Postgres to `127.0.0.1:<port>`. |
| Raw `psql`/`libpq` direct to a CVM                                 | `verify-full` | The driver itself terminates TLS with the sidecar and must verify the server's RA-TLS chain.                  |
| Behind a dstack gateway with a public ACME cert                    | `require`     | The gateway presents a normal CA-signed cert; the gateway re-establishes RA-TLS internally.                   |
| Local dev against the dstack simulator                             | `require`     | The simulator's RA-TLS cert is self-signed and not chainable to a public CA, so `verify-full` fails.          |

<Warning>
  Setting `sslmode=require` or `sslmode=verify-full` on the **forwarder** DSN handed to a TeeSQL RA-TLS client library wires TLS twice and breaks the connection. The driver-side DSN must be `sslmode=disable`; the forwarder is the TLS endpoint.
</Warning>

## Configure SSL in your client

<Tabs>
  <Tab title="psql">
    Direct mutual RA-TLS to a CVM:

    ```bash theme={null}
    psql "host=your-cluster.teesql.com port=5433 user=postgres sslmode=verify-full \
      sslrootcert=ra-tls-ca.pem \
      sslcert=client-cert.pem \
      sslkey=client-key.pem \
      dbname=postgres"
    ```

    Local dev (simulator, server-only TLS):

    ```bash theme={null}
    psql "host=localhost port=5433 user=postgres sslmode=require dbname=postgres"
    ```

    Behind a dstack gateway (`sslmode=require` is enough — public ACME cert):

    ```bash theme={null}
    psql "postgres://postgres:your-32-byte-hex-secret@db.your-domain.com:5433/yourdb?sslmode=require"
    ```
  </Tab>

  <Tab title="psycopg (Python)">
    Use [psycopg-ra-tls](https://github.com/TeeSQL/psycopg-ra-tls). Pass the cluster DSN and a verifier; the library opens a localhost RA-TLS forwarder and rewrites the DSN to `sslmode=disable` internally.

    ```python theme={null}
    from psycopg_ratls import connect
    from ra_tls_verify import IntelApiVerifier

    conn = connect(
        "postgresql://teesql_readwrite:your-32-byte-hex-secret@your-cluster.teesql.com:5433/mydb",
        verifier=IntelApiVerifier(api_key="your-ita-key"),
    )
    ```
  </Tab>

  <Tab title="Prisma (TypeScript)">
    Use [prisma-ra-tls](https://github.com/TeeSQL/prisma-ra-tls). The adapter terminates RA-TLS before any query is issued.

    ```ts theme={null}
    import { PrismaClient } from "@prisma/client"
    import { withRaTls, IntelApiVerifier } from "prisma-ra-tls"

    const adapter = await withRaTls(process.env.DATABASE_URL!, {
      verifier: new IntelApiVerifier(),
      clientAttestation: true, // present a TDX-attested client cert
    })

    export const prisma = new PrismaClient({ adapter })
    ```
  </Tab>

  <Tab title="sqlx (Rust)">
    Use [sqlx-ra-tls](https://github.com/TeeSQL/sqlx-ra-tls). It runs its own forwarder so the first bytes on the wire are a TLS ClientHello (not a Postgres `SSLRequest`), which is required for SNI-based routing through a dstack gateway.

    ```rust theme={null}
    use std::sync::Arc;
    use sqlx::postgres::PgPoolOptions;
    use sqlx_ra_tls::{pg_connect_opts_ra_tls, DcapVerifier, RaTlsOptions};

    let verifier = Arc::new(DcapVerifier::new());
    let opts = pg_connect_opts_ra_tls(
        &std::env::var("DATABASE_URL")?,
        verifier,
        RaTlsOptions {
            allowed_mrtds: vec![std::env::var("EXPECTED_MRTD")?],
            ..Default::default()
        },
    ).await?;
    let pool = PgPoolOptions::new().connect_with(opts).await?;
    ```
  </Tab>
</Tabs>

## Where the CA certificate comes from

The trust anchor for a TeeSQL connection is **not** a public certificate authority. The server's RA-TLS certificate is issued by the **dstack Key Management Service** (KMS) bound to the cluster's attested identity. `ra-tls-parse::build_root_store` treats the last certificate in the presented chain as the trust anchor — that is the dstack KMS root.

Two ways to obtain the root for raw `libpq`/`psql` use:

1. **From the cluster operator.** During cluster provisioning, request the KMS root cert PEM and use it as `sslrootcert`.
2. **From the server's own chain.** The chain is presented during the TLS handshake; tooling that uses the RA-TLS client libraries does not need the root on disk because verification happens against the embedded TDX quote, not a CA chain.

For client certificates:

```rust theme={null}
let client = DstackClient::new()?;
let tls = client.get_tls_key(TlsKeyRequest {
    subject: "my-app".into(),
    alt_names: vec![],
    usage_ra_tls: true,
    usage_server_auth: false,
    usage_client_auth: true,
}).await?;
// tls.key and tls.certificate_chain are PEM
```

```python theme={null}
from dstack_sdk import DstackClient
client = DstackClient()
tls = client.get_tls_key(subject="my-app", usage_ra_tls=True, usage_client_auth=True)
```

The cert is short-lived and tied to your CVM's TDX quote.

## How RA-TLS differs from standard TLS

In standard TLS the server's certificate is signed by a CA you already trust — the chain is the proof. In RA-TLS the server's certificate is **self-signed**, but a TDX attestation is embedded in a custom X.509 extension (Phala RA-TLS OID `1.3.6.1.4.1.62397.1.8` for the current SCALE-encoded form, with `.1.1` retained as a legacy raw-quote fallback). The attestation proves:

* The server is running inside a genuine Intel TDX Trusted Domain
* The TD's measurements (MRTD, RTMR0–3) match the expected software stack
* The platform's Trusted Computing Base (firmware + microcode) is up to date

The TLS public key is bound to the quote via the `REPORTDATA` field, which prevents a man-in-the-middle from substituting a different certificate without invalidating the quote.

A standard TLS client that does not understand the OID will reject the cert as untrusted, which is why direct `libpq` use needs `sslrootcert` set to the dstack KMS root and why the RA-TLS client libraries verify the quote out-of-band against [Intel Trust Authority](https://portal.trustauthority.intel.com) or a local DCAP verifier.

<Note>
  RA-TLS replaces "trust this CA" with "verify this hardware attestation". The deeper protocol — quote layout, RTMR semantics, MRTD pinning — lives in [Remote attestation](/security/remote-attestation).
</Note>

## Verify

Confirm the cert presented on `:5433` is an RA-TLS cert with a TDX attestation extension:

```bash theme={null}
openssl s_client -connect your-cluster.teesql.com:5433 -starttls postgres -showcerts </dev/null 2>/dev/null \
  | openssl x509 -text -noout \
  | grep -A1 "1.3.6.1.4.1.62397"
```

You should see at least one Phala RA-TLS OID (`1.3.6.1.4.1.62397.1.8` for the current attestation, or `.1.1` for a legacy raw quote) listed under X.509v3 extensions. If neither is present, you are not talking to a TeeSQL sidecar.

## Related

* [Connection string](/connect/connection-string)
* [Quickstart](/quickstart)
* [Remote attestation](/security/remote-attestation)
* [Verify attestation](/security/verify-attestation)
