# TLS

> TLS on the Flight SQL edge and the native Quack listener: the default self-signed certificate, a CA-signed replacement, and client settings.

The Arrow FlightSQL edge (`quack-flightsql`, default port `31338`) runs with TLS enabled by default. This page explains the default self-signed certificate behavior, how to replace it with a CA-signed certificate, how to configure clients, and how to disable TLS for development. The native Quack front door (`quack-native`, default port `9494`) has its own switch, covered [at the end](#the-native-quack-front-door).

## Default behavior

TLS is on at startup (`PROXY_TLS_ENABLED=true`). The server reads the certificate chain and private key from:

| Setting | Default path | Override |
|---|---|---|
| Certificate chain | `certs/server-cert.pem` | `PROXY_TLS_CERT_CHAIN` |
| Private key | `certs/server-key.pem` | `PROXY_TLS_PRIVATE_KEY` |

Paths are resolved relative to the manager's working directory (the qod state dir, e.g. `~/.local/share/qod`, when launched via `qod start`).

### Automatic self-signed certificate generation

If either file is missing at boot, the server generates a self-signed certificate automatically using the JRE's own `keytool` (no external binary needed, so this works on stock Windows). The generated certificate has these properties:

- Algorithm: RSA 2048-bit, no passphrase
- Validity: 3650 days (approximately 10 years)
- Subject CN: `localhost`
- Subject Alternative Names: `DNS:localhost`, `IP:127.0.0.1`

On the rare JRE that ships without `keytool`, the server falls back to the system `openssl`. If neither is available (or generation fails), it throws a `RuntimeException` with the tool's output and does not start; supply your own certificate files at the configured paths in that case.

After a successful generation, the server logs a warning at `WARN` level containing the certificate path and its SHA-256 fingerprint in colon-separated uppercase hex format, for example:

```
WARN TLS: generated self-signed cert (CN=localhost) at certs/server-cert.pem / certs/server-key.pem. SHA-256 fingerprint: 4A:7F:... JDBC clients: jdbc:arrow-flight-sql://localhost:PORT?useEncryption=true&disableCertificateVerification=true (or import the cert into a trust store for verified TLS).
```

You can use this fingerprint to verify the certificate from a client or a monitoring check.

If the certificate and key files already exist at both configured paths, the server logs an informational message and reuses them without regenerating.

## Use a CA-signed certificate

For production, replace the self-signed certificate with one signed by a trusted CA before starting the server. Place the files at the configured paths (or point the environment variables at your preferred locations):

```
PROXY_TLS_CERT_CHAIN=/etc/tls/my-cert-chain.pem
PROXY_TLS_PRIVATE_KEY=/etc/tls/my-private-key.pem
```

The certificate file must contain a PEM-encoded certificate chain (leaf first, intermediates following). The key file must be a PEM-encoded PKCS#8 or traditional RSA private key without a passphrase (the server passes both directly to the Arrow Flight `useTls` API).

Restart the server after placing the files. The server logs `TLS: reusing existing cert at <path>` if the paths exist.

## Connect clients

### Import the certificate (recommended)

The most reliable approach for the self-signed default is to import the generated `certs/server-cert.pem` into the client trust store once. After that, clients verify the server identity and no extra connection flags are needed:

```
jdbc:arrow-flight-sql://localhost:31338?useEncryption=true&user=admin&password=admin&tenant=tpch&pool=sales
```

For Java-based clients, use `keytool` to import the PEM into a JKS truststore and point the JDBC URL at it with `trustStore=<path>&trustStorePassword=<password>`.

### Skip certificate verification (testing only)

For quick local testing against the self-signed certificate, pass `disableCertificateVerification=true` on the JDBC URL:

```
jdbc:arrow-flight-sql://localhost:31338?useEncryption=true&disableCertificateVerification=true&user=admin&password=admin&tenant=tpch&pool=sales
```

For ADBC or other CLI clients, pass the `--insecure` flag instead.

Do not use certificate verification bypass in production. It removes the server identity check and exposes the connection to man-in-the-middle attacks.

## Disable TLS (development only)

Set `PROXY_TLS_ENABLED=false` to run the FlightSQL edge on plain gRPC without any TLS wrapping. The JDBC URL then uses `useEncryption=false`:

```
jdbc:arrow-flight-sql://localhost:31338?useEncryption=false&user=admin&password=admin&tenant=tpch&pool=sales
```

Do not run without TLS in any environment where the network is not fully trusted, because credentials are transmitted in cleartext.

## The native Quack front door

The listener DuckDB clients `ATTACH` to (`quack-native`, default port `9494`) ships with TLS **off**, unlike the FlightSQL edge. The DuckDB `quack` client speaks plain HTTP to `localhost` / `127.0.0.1` / `::1` and TLS to every other host, and cannot be told to use TLS on a loopback host, so a TLS-on default would break every local `ATTACH 'quack:localhost:9494'`.

| Setting | Default | Override |
|---|---|---|
| TLS on/off | `false` | `QOD_QUACK_TLS_ENABLED` |
| Certificate chain | `certs/server-cert.pem` (the FlightSQL edge's) | `QOD_QUACK_TLS_CERT_CHAIN` |
| Private key (PKCS8) | `certs/server-key.pem` (the FlightSQL edge's) | `QOD_QUACK_TLS_PRIVATE_KEY` |

Before exposing the port beyond the host, either turn TLS on (remote DuckDB clients then default to TLS, and the client does not verify a self-signed certificate unless configured to) or terminate TLS in front of the port as for the REST API. A remote client reaching a plain-HTTP listener must add `DISABLE_SSL true` to its `ATTACH` options (`disable_ssl := true` on `quack_query`). A PKCS1 key (`-----BEGIN RSA PRIVATE KEY-----`) is refused at boot with a conversion hint; convert it with `openssl pkcs8 -topk8 -nocrypt`. See [DuckDB (native Quack)](/qod/connecting/duckdb).
