> For the complete documentation index, see [llms.txt](https://docs.umh.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.umh.app/usage/historian.md).

# Historian

A historian is the standard industrial term for the system that records process data over time, so operators can go back and ask what a machine was doing last Tuesday. AVEVA PI and GE Proficy fill that role in a classic plant.

Ours stores data from the Unified Namespace in TimescaleDB rather than a proprietary store, so you can read history back with ordinary SQL and build Grafana dashboards on it. The UNS keeps the live value of every tag; the Historian keeps the past.

{% hint style="info" %}
**Early access.** The Historian is not switched on by default. If you want to use it, get in touch with us on [Discord](https://discord.gg/F9mqkZnm8U) or through your usual UMH contact, and we will show you how to get started.
{% endhint %}

## How it fits together

```
Bridges / Stream Processors
        │
        ▼
  Unified Namespace  ──►  Historian bridge  ──►  TimescaleDB  ──►  Grafana
                          (one per contract)      umh schema
```

You configure the database connection **once per instance**, in the instance's Plugins tab. Every Historian bridge on that instance then reuses it, so no credentials are copied into bridge configs, and changing the connection updates the bridges that reference it.

Three steps get you from a running UNS to a dashboard:

1. Set up the connection (this page). Tell the instance where its database is.
2. [Save data to the Historian](/usage/historian/save-to-historian.md). Create one bridge per data contract you want to keep.
3. [Query the data](/usage/historian/querying.md). Copy queries out of the topic browser into Grafana or psql.

## Prerequisites

* **A TimescaleDB database**, reachable from the umh-core instance. If you don't have one, [Recommended UMH Stack](/production/deployment/docker-compose/additional-services/recommended-umh-stack.md) brings up umh-core, PgBouncer, TimescaleDB, and Grafana together. To add just the database to a running instance, see [TimescaleDB](/production/deployment/docker-compose/additional-services/timescaledb.md).
* **PostgreSQL 16 or newer**, with the `timescaledb` and `ltree` extensions available. Version 16 is the floor because older `ltree` labels reject hyphens, and location paths such as `line-1` contain them.
* **A login role**, created before the first bridge starts. The bridge logs in as this role and creates the `umh` schema it owns; it cannot create the role itself. A database-level grant is enough, so no privileges on `public` are needed:

  ```sql
  CREATE ROLE umh_owner WITH LOGIN PASSWORD 'change-me';
  GRANT CREATE, CONNECT ON DATABASE umh TO umh_owner;
  ```
* **Data in the UNS**, already flowing. The Historian archives what is in the namespace; it does not read from devices itself.

## Set up the connection

1. Open the instance, go to the **Plugins** tab, and add the **Historian** plugin.
2. Fill in the TimescaleDB connection:

   | Field        | Default     | Notes                                                          |
   | ------------ | ----------- | -------------------------------------------------------------- |
   | **Host**     | required    | Hostname or IP, resolved *from inside the umh-core container*. |
   | **Port**     | `5432`      | Point this at PgBouncer if you use one.                        |
   | **Database** | `umh`       | Must already exist.                                            |
   | **User**     | `umh_owner` | The login role from the prerequisites.                         |
   | **Password** | required    | Stored in `config.yaml`, hidden in the UI, redacted in logs.   |
   | **SSL Mode** | `require`   | `require` or `disable`.                                        |
3. Save. The instance opens a connection and reports the result on the plugin's overview card.

`require` encrypts the connection but does not verify the server certificate. `disable` turns TLS off entirely.

Full certificate verification (`verify-full`) cannot be configured from the Management Console yet; support is coming in a later release. A connection that already sets `sslmode: verify-full` in `config.yaml` keeps working, along with its `sslrootcert`, `sslcert`, and `sslkey` paths.

### Reading the connection status

The overview card is not shown in the plugin list. Go to **Instances** → your instance → **Plugins** → **Historian** and click **Open**. Saving a new connection also leaves you on the plugin list, so that click is needed either way.

The overview card shows the connection settings plus three live fields, refreshed once per second:

| Field         | Meaning                                                                                                                                           |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Reachable** | The endpoint answered. It stays `true` even when the server rejects the credentials. Only a network fault or timeout makes it `false`.            |
| **Auth**      | Whether the server accepted the login role, password, and database name. `unknown` when nothing answered, so authentication could not be checked. |
| **Latency**   | Round-trip time of the check query.                                                                                                               |

The check is a single `SELECT 1` over one pooled connection. It tells you the database is reachable and the credentials work; it says nothing about whether a particular bridge is writing. Per-bridge throughput, health, and errors stay in the **Data Flows** bridge list.

Connections are recycled every five minutes, so a password rotated on the server surfaces as an authentication failure within that window rather than being masked by a long-lived session.

### Editing and removing

Editing the connection updates every bridge that references it, so you don't touch the bridges. Leave the password field blank to keep the stored one; the Management Console never receives it back, so an empty value means "unchanged", not "clear".

Adding a Historian connection is create-only. If one already exists, edit it instead; a repeated add is rejected rather than silently overwriting your settings.

## Where it is stored

The connection lives in the instance's `config.yaml` as a single shared block:

```yaml
historian:
  timescale:
    host: timescaledb.example.com
    port: 5432
    database: umh
    username: umh_owner
    password: change-me
    sslmode: require
```

This block is the one place the credentials are written. Every Historian bridge on the instance reads them from here instead of carrying its own copy.

Bridge templates read it through the reserved `{{ .historian.timescale.* }}` scope: `host`, `port`, `database`, `username`, `password`, `sslmode`, `sslrootcert`, `sslcert`, and `sslkey`. See [Variables](/reference/variables.md).

A bridge counts as a Historian bridge when its write flow's destination protocol is `historian`. umh-core then targets the bridge's health check at the shared connection rather than at a host and port entered on the bridge, so the check follows the connection whenever you change it.

## Next steps

* [Save data to the Historian](/usage/historian/save-to-historian.md)
* [Query the Historian](/usage/historian/querying.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.umh.app/usage/historian.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
