> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://docs.ampdb.io/api/mcp` to find what you need.

# Controller

The controller is the service that manages Amp. You don't query data through it: you tell it what to extract and serve,
and it organizes the work.

* **It serves the [Admin API](/managing-amp/admin-api)**, on port `1610`. [`ampctl`](/clis/ampctl) and the SDKs use it
  to register and deploy [datasets](/get-started/concepts/datasets), and to manage [jobs](/managing-amp/jobs),
  [workers](/managing-amp/workers), [providers](/managing-amp/providers), and [config maps](/managing-amp/config-maps).
* **It schedules jobs.** A job is a unit of work a worker runs, like extracting a dataset's tables. The controller
  decides which worker runs each job and when, including periodic runs and retries, and records it in the
  [metadata database](/running-amp/metadata-database), which notifies the worker.
* **It tracks workers**, through the heartbeat each one writes to the metadata database every second.
* **It maintains the metadata database**, pruning the change log that query servers and workers use to
  [cache which files a table has](/running-amp/storage/metadata-caching#file-lists).

Solo mode runs a controller in its process. In [distributed mode](/running-amp/distributed-mode), you run it with
`ampd controller`.

## Prerequisites

* A [configuration file](/running-amp/configuration) with the metadata database's URL and your storage locations.
* A migrated metadata database, unless the configuration lets the controller migrate it
  ([`auto_migrate`](/running-amp/metadata-database#auto_migrate)).

## Start the controller

```bash
ampd --config config.toml controller
```

Check that it answers, and see the workers it knows about:

:::terminal
```bash
curl http://localhost:1610/workers
```

```json
{ "workers": [{ "node_id": "worker-01", "heartbeat_at": "2026-10-30T16:51:28.852457+00:00" }] }
```
:::

## Settings

The controller reads the [shared settings](/running-amp/configuration#per-service-settings) and the `[controller]`
section:

| Setting                                                                                                                          | Default        | Description                                                                                                        |
| -------------------------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------ |
| [`controller.admin_api_addr`](/running-amp/configuration-reference#controlleradmin_api_addr)                                     | `0.0.0.0:1610` | The address the Admin API listens on, like `"127.0.0.1:1610"` to accept local connections only.                    |
| [`controller.default_worker_id`](/running-amp/configuration-reference#controllerdefault_worker_id)                               | none           | The worker that runs jobs which don't name one. See [Job assignment](/running-amp/controller#job-assignment).                             |
| [`[controller.metadb]`](/running-amp/metadata-database#per-service-pools)                                                        |                | The controller's own connection pool settings, like a smaller `pool_size`.                                         |
| [`location_membership.log_prune_interval_secs`](/running-amp/configuration-reference#location_membershiplog_prune_interval_secs) | `3600`         | How often the controller prunes the [change log of table files](/running-amp/storage/metadata-caching#file-lists). |

## Job assignment

When you deploy a dataset or create a job, you can name the worker that runs it, or a group of workers with a prefix
pattern, like `worker-eu-*`. See [Workers](/managing-amp/workers). When you don't, the controller picks one:

1. The worker in `controller.default_worker_id`, when it's set. That worker must be active when the job is first
   scheduled, or the request fails.
2. Otherwise, a random active worker.

A worker is active while its heartbeat is less than 5 seconds old. Once a job has a worker, its retries and periodic
runs stay on that worker, even when others are available.

```toml [config.toml]
[controller]
default_worker_id = "worker-01"
```

The value must be one valid [worker ID](/running-amp/worker#node-ids), not a pattern, or the controller doesn't start.
Solo mode ignores this setting, since its only worker is `worker`.

## Health check

`GET /healthz` on the Admin API's port returns `200 OK` once the controller listens:

```bash
curl -i http://localhost:1610/healthz
```

## Security

The Admin API doesn't authenticate requests, and accepts them from any web origin (CORS), so anyone who can reach it
can manage Amp. Keep it on a private network, or behind a proxy that authenticates operators. When that proxy adds
`amp-capabilities` metadata to a request, the controller limits it to the namespaces the capabilities allow; see
[Admin API](/managing-amp/admin-api). [Distributed Mode](/running-amp/distributed-mode#secure-the-deployment) covers
securing the other services.

## Availability

Run one controller. While it's down, query servers keep answering queries and workers keep running their jobs, but the
Admin API is unavailable, and no job starts until it's back, periodic runs included. Run it under a process manager,
like systemd or Kubernetes, that restarts it when it stops.

## Troubleshooting

### `Migration consistency check failed`

The metadata database is behind the controller's version of Amp. Run
[`ampd migrate`](/running-amp/upgrading#migrate-the-metadata-database).

### `failed to bind to 0.0.0.0:1610`

Another program uses the port. Stop it, or set
[`controller.admin_api_addr`](/running-amp/configuration-reference#controlleradmin_api_addr) to a free one.

### `Invalid worker ID`

```
Exiting with error: Failed to load config | Caused by: Config parse error at /home/amp/config.toml -> Failed to load configuration file -> Invalid worker ID 'worker-*': invalid character '*' for key "default.default_worker_id.controller" in config.toml TOML file
```

`controller.default_worker_id` isn't a valid [worker ID](/running-amp/worker#node-ids). It names one worker, so it can't
be a pattern.
