# How to connect to a database on a private network

Source: https://docs.quake.ai/docs/databases/how-to/connect-to-a-private-database
Markdown: https://docs.quake.ai/docs/databases/how-to/connect-to-a-private-database.md
> Find a database instance's private address, confirm the security group allows your source, and open a client session from an application instance or through a bastion tunnel.

---

# How to connect to a database on a private network

Open a client session against a PostgreSQL or MySQL/MariaDB instance that has no floating IP. The steps cover both connection paths: an application instance already on the private network, and a workstation reaching the subnet through a bastion.

<PrerequisiteBlock methods={["cli"]}>

- A running database deployed from the [Self-Managed PostgreSQL](/resources/iac-templates/self-managed-postgres) or [MySQL/MariaDB Database](/resources/iac-templates/mysql-database) template
- The `db_name`, `db_user`, and `db_password` values you set when you applied the template
- For workstation access: a bastion, VPN, or jump instance with a route into the database subnet. See [SSH bastion access into a private subnet](/docs/network/how-to/ssh-bastion-access)

</PrerequisiteBlock>

Replace `DB_PRIVATE_IP` with the database instance's private address, `JUMP_HOST` with your bastion's reachable address, and `YOUR_PRIVATE_KEY_PATH` with the key matching the `key_name` on the deployment. The templates default to Ubuntu 24.04, so the SSH user is `ubuntu`.

## Step 1: Find the private address

List the instances in the project and read the address from the database row:

```bash
openstack server list -c Name -c Networks -c Status
```

The database instance shows one address on the private network and no floating IP:

```text
+----------+-------------------------------+--------+
| Name     | Networks                      | Status |
+----------+-------------------------------+--------+
| postgres | postgres-net=192.168.40.12    | ACTIVE |
| app-01   | postgres-net=192.168.40.20    | ACTIVE |
+----------+-------------------------------+--------+
```

## Step 2: Confirm the security group allows your source

Find the group attached to the database instance, then list its rules:

```bash
openstack server show postgres -c security_groups
openstack security group rule list SECURITY_GROUP_NAME
```

The rule you need shows the engine's port with your source range in the IP Range column: `5432` for PostgreSQL, `3306` for MySQL and MariaDB. If your source address falls outside every listed range, add a rule for it with [How to create security group rules](/docs/network/how-to/create-security-group-rules). Scope the new rule to the narrowest range that covers the client, such as the application subnet or a single instance address.

## Step 3: Connect from an application instance

An instance on the same private network connects directly. SSH to the application instance, install the client package, and open a session.

For PostgreSQL:

```bash
sudo apt update && sudo apt install -y postgresql-client
PGPASSWORD='YOUR_DB_PASSWORD' psql -h DB_PRIVATE_IP -U appuser -d appdb -c "SELECT version();"
```

For MySQL or MariaDB:

```bash
sudo apt update && sudo apt install -y mariadb-client
mysql -h DB_PRIVATE_IP -u appuser -p'YOUR_DB_PASSWORD' appdb -e "SELECT VERSION();"
```

A version string in the output confirms the network path, the security group rule, and the account all work.

## Step 4: Connect from your workstation through a bastion

A workstation has no route to the private subnet, so forward a local port through the bastion and point the client at `127.0.0.1`.

For PostgreSQL:

```bash
ssh -i YOUR_PRIVATE_KEY_PATH -N -L 5432:DB_PRIVATE_IP:5432 ubuntu@JUMP_HOST
```

For MySQL or MariaDB:

```bash
ssh -i YOUR_PRIVATE_KEY_PATH -N -L 3306:DB_PRIVATE_IP:3306 ubuntu@JUMP_HOST
```

Leave that session running and connect from a second terminal:

```bash
PGPASSWORD='YOUR_DB_PASSWORD' psql -h 127.0.0.1 -U appuser -d appdb
mysql -h 127.0.0.1 -u appuser -p'YOUR_DB_PASSWORD' appdb
```

The tunnel keeps the engine port off the public internet: the bastion terminates the SSH session, and the database still accepts connections only from inside the subnet.



To run administrative commands on the engine host instead of through a client, SSH to the database instance itself with `ssh -i YOUR_PRIVATE_KEY_PATH -J ubuntu@JUMP_HOST ubuntu@DB_PRIVATE_IP`.



## Step 5: Verify from the application

Point the application at the private address and confirm it connects with its own credentials rather than yours:

```text
postgresql://appuser:YOUR_DB_PASSWORD@192.168.40.12:5432/appdb
mysql://appuser:YOUR_DB_PASSWORD@192.168.50.12:3306/appdb
```

Store the password with the rest of the application's secrets; see [How to inject application secrets](/docs/security/how-to/inject-app-secrets).

## Troubleshoot a refused connection

| Symptom | Where to look |
| --- | --- |
| The client hangs, then times out | Security group: no rule allows the source range on the engine port |
| `Connection refused` immediately | The engine is not listening on that address (`listen_addresses` for PostgreSQL, `bind-address` for MySQL and MariaDB), or the service is down |
| `no pg_hba.conf entry for host` | PostgreSQL authentication: add a matching `pg_hba.conf` line and reload the server |
| `Access denied for user` | The MySQL account's host pattern does not cover the client address, or the password is wrong |
| The tunnel command exits at once | Local port already in use; choose another local port and point the client at it |

## Next steps

- [Database networking and access](/docs/databases/concepts/database-networking-and-access): the addressing and access model behind these steps
- [How to restore PostgreSQL from a self-managed-postgres backup](/docs/automation/how-to/restore-postgres-from-backup): a restore drill that uses the same private-subnet access
- [SSH bastion access into a private subnet](/docs/network/how-to/ssh-bastion-access): bastion setup and client configuration
