# 08001 — sqlclient_unable_to_establish_sqlconnection

> PostgreSQL SQLSTATE 08001: sqlclient_unable_to_establish_sqlconnection, source-backed diagnosis and recovery guidance.
---

# 08001

## At a glance {#at-a-glance}

SQLSTATE `08001` is **sqlclient_unable_to_establish_sqlconnection** in Class `08`. `08001` is the server-side SQLSTATE for a client connection that could not be established. In the selected `dblink_connect` path the ERROR primary is `could not establish connection` and the refused-port reason is dynamic DETAIL; this differs from 08003 (an absent dblink handle) and startup failures such as 28000, 28P01, and 3D000.

<!-- BEGIN SQLSTATE FACTS: generated by scripts/generate.py; do not edit -->

| Field | Value |
| --- | --- |
| SQLSTATE | `08001` |
| Condition | `sqlclient_unable_to_establish_sqlconnection` |
| Status | `active` |
| Known present by | `7.4` |
| Locked snapshots | `9.0.23, 9.1.24, 9.2.24, 9.3.25, 9.4.26, 9.5.25, 9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3` |
| Macros | `ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION` |
| Aliases | `—` |

<!-- source facts: data/errcodes/08001.json -->
<!-- END SQLSTATE FACTS -->

## Meaning {#meaning}

The server-side `dblink_connect` branch emits this code after libpq cannot open the requested remote endpoint. Its fixed diagnostic is an ERROR with primary `could not establish connection`; the endpoint and operating-system reason are dynamic DETAIL text. The condition concerns establishing a named remote handle, so it is different from an absent handle (08003) and from startup rejection before a session exists (28000 or 3D000).

The source does not format the DETAIL itself: `dblink.c` passes the libpq error string through `errdetail_internal("%s", msg)`. That distinction matters when comparing versions or clients. The 18.6 refusal observed below includes the host, port, and `Connection refused` text, while the 10.21 refusal uses a different libpq wording; neither wording is a fixed 08001 template.

## Diagnosis {#diagnosis}

Record the target host, port, authentication parameters, and complete DETAIL. The selected 18.6/10.21 runs leave the local autocommit session `IDLE` after the refused attempt; a real dblink handle then connects to the runner-owned target, runs remote `SELECT 1`, and is explicitly disconnected. That proves local recovery and handle cleanup, not completion of any remote business transaction.

Read the failure stage before retrying. A refused TCP endpoint, DNS or TLS failure, and authentication rejection can all be returned by libpq through this dblink branch, so inspect the dynamic DETAIL rather than classifying from the code alone. If the call runs inside an explicit local transaction, the `ERROR` can leave that transaction unusable until `ROLLBACK` (or `ROLLBACK TO SAVEPOINT`); the autocommit `IDLE` result in the selected case does not imply that an explicit transaction is still usable. The dblink handle belongs to the backend that created it, so check and repair it on that same session or pool member.

## Response {#response}

Correct the endpoint or connection parameters, open a new handle, and verify a harmless remote probe before sending business work. If the failed request could have crossed a remote boundary, reconcile it before retrying; 08001 alone does not justify replaying a non-idempotent operation.

In autocommit, retrying the connection attempt after correcting the endpoint is a new statement and the owner session can remain usable. In an explicit transaction, first recover the local transaction, then establish and probe a fresh handle; a savepoint is useful only when the surrounding work is deliberately designed to continue. If the remote operation may have reached the target before the local error was reported, reconcile its result before replaying it.

## Observed diagnostics {#messages}

The fixed `dblink_connect` branch emits `ERROR` with primary `could not establish connection` and passes a dynamic libpq string as DETAIL (`errdetail_internal("%s", msg)`). In the selected 18.6 run that value was `connection to server at "127.0.0.1", port 1 failed: Connection refused` followed by libpq's hint; in the selected 10.21 run it began `could not connect to server: Connection refused`. Those are run-specific values, not a fixed SQLSTATE message template. Other producers may choose different text; a client-side exception without the server diagnostic is not evidence for this SQLSTATE.

The severity is fixed at `ERROR` for this dblink path. The selected local session stayed `IDLE` because the case used autocommit; do not transfer that status to a surrounding explicit transaction or to a remote transaction whose outcome was not observed.

## Representative case {#case}

<!-- BEGIN SQLSTATE SNIPPET: dblink_connect_failure -->

The SQL block creates `dblink`, attempts a refused runner-local port, then probes the owner session.

`runner_host`, `runner_port`, `runner_db`, and `runner_user` are runner placeholders, not literal values for a manual copy. Replace them with a reachable target and a login role that may connect to it; installing/using `dblink` and the remote login require the corresponding privileges. Keep the statements on the same owner backend and use the case's autocommit boundary if you want the selected recovery observation. The trigger is a server-side dblink attempt; it does not execute work on a remote transaction.

```sql
CREATE EXTENSION IF NOT EXISTS dblink;
SELECT dblink_connect('missing_remote', 'host=127.0.0.1 port=1 dbname=postgres connect_timeout=1');
SELECT dblink_connect('working_remote', 'host=runner_host port=runner_port dbname=runner_db user=runner_user connect_timeout=5');
SELECT * FROM dblink('working_remote', 'SELECT 1') AS result(value integer);
SELECT dblink_disconnect('working_remote');
SELECT 1;
```

<!-- END SQLSTATE SNIPPET -->

The selected 18.6 run reports SQLSTATE `08001`, primary `could not establish connection`, and leaves the owner session `IDLE` after the failed operation. The controlled repair opened the named handle with `OK`, returned remote `1`, and disconnected it with `OK`; the final owner probe returned `1` and `IDLE`. The 10.21 selected run passed the same assertions.

The downloadable case and evidence projections are [`08001 case JSON`](../data/cases/08001.json) and [`authored evidence`](../data/evidence/08001.json). The runner manifest is `verify/cases/08001/cases.json`; the page SQL is checked against its shared registry before publication.

## Versions {#versions}

The generated facts table records the locked catalogue snapshots and earliest observed definition. The selected natural runtime scope is PostgreSQL 18.6 and 10.21; this does not infer behavior for every intermediate release.

## Related {#related}

- [`08000` — connection_exception](../08000/)
- [`08003` — connection_does_not_exist](../08003/)
- [`28000` — invalid_authorization_specification](../28000/)
- [`3D000` — invalid_catalog_name](../3d000/)

## Sources {#sources}

- `src.dblink-connect.18.6` — `contrib/dblink/dblink.c` at `REL_18_6` commit `724edf9bde9d356724ad384a2e196edc3c9f80f7`; fixed blob SHA-256 `e4cfaec3a0a1e5d23fded584725e0f6cf7a17321ad99e5fe046e8b7f97416f15` ([source](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/contrib/dblink/dblink.c#L335-L338)).
- `src.dblink-connect.10.23` — `contrib/dblink/dblink.c` at `REL_10_23` commit `02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4`; fixed blob SHA-256 `2d542cc722ba361bd59990aaed7fe7c0f8e147155df5e5fcd56ee03b4dd27c61` ([source](https://github.com/postgres/postgres/blob/02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4/contrib/dblink/dblink.c#L299-L302)).
- `src.calls.REL_18_6` / `src.calls.REL_10_23` — fixed local call scans, SHA-256 `9ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf` / `00d16d3eb01b71ccf1b245c8f3102f9d0ec9f36fb02777b8dd1b99fcb263040c`; these scans preserve the resolved call context used by the claims.
