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

# PostgreSQL Connector

> Connect TextQL to your PostgreSQL database

<iframe className="w-full aspect-video rounded-xl" src="https://www.youtube.com/embed/f2xv9SFH8fg" title="How to Connect PostgreSQL to TextQL | AI Data Analysis Tutorial" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen />

## Overview

This guide walks you through connecting TextQL to your PostgreSQL database. You'll need connection credentials from your PostgreSQL account to complete the setup.

## Prerequisites

To connect TextQL with your PostgreSQL database, you will need:

* **Host URL** and **Port** (default: 5432)
* **Username** and **Password**
* **Database** name (default: postgres)
* **Schemas** (optional, if you want to specify specific schemas)

If your database is behind a private network and only accessible through a bastion host, you will also need SSH tunnel credentials. See [Connecting via SSH Tunnel](#connecting-via-ssh-tunnel) below.

## Finding Your Connection Details

### Connection String Format

Most PostgreSQL providers display the connection string in this format:

```
postgresql://username:password@host:port/database
```

### Example Connection String Breakdown

```
postgresql://username:pass123@db.example.com:5432/mydb
                ↑       ↑            ↑        ↑    ↑
           username   password     host     port  database
```

From this connection string, you can extract:

* **Host URL**: `db.example.com`
* **Port**: `5432`
* **Username**: `username`
* **Password**: `pass123`
* **Database**: `mydb`

## Creating the Connector in TextQL

### Step 1: Navigate to Connectors Page

1. Go to the [TextQL Connectors Page](https://app.textql.com/connectors)
2. Click **New Connector**

### Step 2: Select PostgreSQL

Select **Postgres** from the available connectors to open the configuration form.

### Step 3: Enter Connection Details

The form requires the following information:

| Field | Description | Example |
| - | - | - |
| **Connector Name** | A descriptive name to identify this connection | `My PostgreSQL Database` |
| **Host URL** | The endpoint URL of your PostgreSQL database | `db.example.com` |
| **Port** | The port number for your connection | `5432` |
| **Username** | Your PostgreSQL username | `postgres` |
| **Password** | Your database password | `••••••••` |
| **Database** | The name of the database to connect to | `postgres` |
| **Schemas** | (Optional) Specific schemas to include | `public, analytics` |

### Step 4: Test and Create

1. Click **Test Connection** to verify your credentials and network access
2. Once the test succeeds, click **Create Connector** to save the connection

## Authenticating with AWS Secrets Manager

Instead of storing a static password in TextQL, you can point the connector at an AWS Secrets Manager secret. TextQL fetches the credentials at connect time, so password rotations (including RDS-managed rotation) are picked up automatically.

### Setting Up Secrets Manager Authentication

1. In the PostgreSQL connector form, set **How should users authenticate?** to **AWS Secrets Manager**
2. Enter the **Secret ARN** of the secret holding your database credentials
3. (Optional) Enter an **IAM Role ARN** for TextQL to assume when reading the secret, plus the **External ID** its trust policy requires

<Note>
  Once the connector is saved, the **Secret ARN**, **IAM Role ARN** and **External ID** are hidden in the edit form and returned blank (marked redacted) in connector API responses. Leave them blank when editing to keep the stored values. Organization admins, or any role with the organization write permission, can reveal them from the edit form, and every reveal is recorded in the audit log.
</Note>

The secret value can be either:

* **RDS-style JSON** with `username` and `password` keys (the format AWS RDS uses for managed secrets), or
* A **plain-text password** (set the username on the connector form instead)

### Cross-Account Access with an IAM Role

When the secret lives in a different AWS account than your TextQL deployment, create an IAM role in the secret's account and enter its ARN in the **IAM Role ARN** field.

The role needs a permissions policy that allows reading the secret:

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "arn:aws:secretsmanager:us-east-1:111122223333:secret:my-db-credentials-AbCdEf"
    }
  ]
}
```

If the secret is encrypted with a customer-managed KMS key, also grant `kms:Decrypt` on that key.

The role's trust policy must allow TextQL's service role to assume it. It must grant **both** `sts:AssumeRole` and `sts:TagSession`, in **separate statements**:

```json theme={null}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::444455556666:role/your-textql-service-role"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": { "sts:ExternalId": "your-external-id" }
      }
    },
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::444455556666:role/your-textql-service-role"
      },
      "Action": "sts:TagSession"
    }
  ]
}
```

<Warning>
  `sts:TagSession` is required, not optional. TextQL deployments run on EKS Pod Identity, which attaches transitive session tags to the service's AWS credentials. STS forwards these tags automatically whenever the role is assumed, and rejects the request with `AccessDenied: ... is not authorized to perform: sts:TagSession` if the trust policy only grants `sts:AssumeRole`.

  Keep `sts:TagSession` in its own statement, as shown above. STS applies an `sts:ExternalId` condition only to the `sts:AssumeRole` action, so a single statement that grants both actions under that condition denies `sts:TagSession` and the role assumption fails.
</Warning>

<Tip>
  Using an **External ID** condition (as shown above) is recommended for cross-account roles to prevent the confused-deputy problem. Generate a unique value, add it to the trust policy condition, and enter the same value in the connector form.
</Tip>

Ask your TextQL contact for the service role ARN of your deployment to use as the `Principal`.

### Secrets Manager Troubleshooting

**"not authorized to perform: sts:TagSession"**

* The trust policy of the role in the **IAM Role ARN** field grants `sts:AssumeRole` but not `sts:TagSession`. Add a separate `sts:TagSession` statement as shown above.
* The trust policy grants both actions in a single statement with an `sts:ExternalId` condition. Split them into separate statements: the condition can never match for `sts:TagSession`, so the shared statement denies it.
* If the trust policy is already correct, the TextQL service role's own IAM policy may be missing `sts:TagSession` for your role: for cross-account roles, both accounts must allow it. Ask your TextQL contact to verify.

**"not authorized to perform: sts:AssumeRole"**

* The trust policy's `Principal` does not match the TextQL service role, or the **External ID** in the form does not match the trust policy condition.

**"not authorized to perform: secretsmanager:GetSecretValue"**

* The role's permissions policy does not cover the secret ARN, or the secret's resource policy denies access. If the secret uses a customer-managed KMS key, check `kms:Decrypt` as well.

**"secret ... contains no password"**

* The secret value is JSON but has no `password` key. Use RDS-style `{"username": ..., "password": ...}` JSON or a plain-text password.

## Connecting via SSH Tunnel

If your PostgreSQL database is in a private subnet and not directly reachable from the internet, you can connect through an SSH tunnel (also called a bastion host or jump server). TextQL will open an encrypted SSH connection to your bastion host and forward database traffic through it.

### When to Use an SSH Tunnel

* Your database is in a private VPC with no public endpoint
* Your security policy requires all external access to go through a bastion host
* You cannot or prefer not to whitelist TextQL's IP addresses directly on the database

### SSH Tunnel Prerequisites

In addition to your standard database credentials, you will need:

| Field | Description | Example |
| - | - | - |
| **SSH Host** | The hostname or IP of your bastion server | `bastion.example.com` |
| **SSH Port** | The SSH port on the bastion (default: 22) | `22` |
| **SSH Username** | The OS-level user on the bastion | `ec2-user` |
| **SSH Private Key** | A PEM-formatted private key authorized on the bastion | `-----BEGIN OPENSSH PRIVATE KEY-----` |
| **SSH Host Public Key** | (Optional) The bastion's public host key for verification | `ssh-ed25519 AAAA...` |

### Setting Up the SSH Tunnel

1. In the PostgreSQL connector form, check **Connect via SSH tunnel (bastion host)**
2. The SSH configuration fields will appear below the standard connection fields
3. Enter your bastion host, port, username, and private key
4. For **Host URL** and **Port** in the main connection fields, enter the database's **private** address (the address reachable from the bastion host, not from the public internet)

<Tip>
  The **SSH Host Public Key** field is optional but recommended. Providing it allows TextQL to verify the bastion host's identity and prevents man-in-the-middle attacks. You can find this value on the bastion server at `/etc/ssh/ssh_host_ed25519_key.pub` (or the equivalent for your key type).
</Tip>

<Warning>
  Your SSH private key is stored encrypted and is never exposed in the UI after saving. If you need to rotate the key, edit the connector and provide the new key.
</Warning>

### How It Works

When TextQL connects to your database through an SSH tunnel:

1. TextQL establishes an encrypted SSH connection to your bastion host
2. A secure tunnel is created from TextQL through the bastion to your database's private address
3. All database queries are forwarded through this tunnel
4. The tunnel is torn down after the session ends

```
TextQL  ──SSH──▶  Bastion Host  ──TCP──▶  PostgreSQL (private subnet)
```

### SSH Tunnel Troubleshooting

**"Failed to establish SSH tunnel"**

* Verify the bastion host is reachable from the internet on the specified SSH port
* Confirm the SSH username and private key are correct
* Ensure the private key is in PEM format (starts with `-----BEGIN`)
* Check that the bastion's security group allows inbound SSH from TextQL's IPs (`44.229.62.69` and `54.69.138.147`)

**"Connection test failed" (after tunnel succeeds)**

* The SSH tunnel connected, but the database is not reachable from the bastion. Verify:
  * The database host and port are correct (use the **private** address)
  * The bastion host has network access to the database
  * Database security groups allow inbound connections from the bastion

**"Host key verification failed"**

* The SSH Host Public Key you provided does not match the bastion server's actual host key. Retrieve the correct key from the bastion and update the connector.

## Troubleshooting

### Connection Fails

**Verify the following:**

* Connection credentials are correct
* Host URL and port are accurate
* Database name is spelled correctly
* PostgreSQL server is running and accessible

<Note>
  Having trouble connecting? See the [Network Configuration Guide](/core/datasources/databases/network-configuration) for firewall and IP whitelisting setup.
</Note>

### Authentication Errors

**Check:**

* Username and password are correct
* User has appropriate permissions
* Password doesn't contain special characters that need escaping

### Timeout Errors

**Possible causes:**

* Firewall blocking connection
* Incorrect host URL or port
* Database server not responding
* Network connectivity issues

## What's Next

Once your PostgreSQL connector is set up, you can:

* Ask Ana natural language questions about your data
* Generate SQL queries and visualizations
* Create reports and dashboards
* Share insights with your team


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.