---
title: Zonecloud Controller Handbook
description: "Table of contents - [Creating a cluster](#toc_creating_a_cluster) - [Creating an agent](#toc_creating_an_agent) - [Creating a node](#toc_creating_a_node) - [Easy ZoneCloud configuration for agents/nodes](#toc_easy_zcloud_configuration) - [Creating a user](#toc_creating_a_user) - [Excluding a zone](#excluding_a_zone) - [Removing a zone exclusion](#removing_a_zone_exclusion) - [Changing timezone](#changing_timezone) - [Webhook notifications](#webhooks)…"
url: "https://zonecloud.io/documentation/zonecloud-controller-handbook/"
updated: "2025-09-17"
category: Controller (Web Interface)
---

# Zonecloud Controller Handbook

Table of contents

- [Creating a cluster](#toc_creating_a_cluster)
- [Creating an agent](#toc_creating_an_agent)
- [Creating a node](#toc_creating_a_node)
- [Easy ZoneCloud configuration for agents/nodes](#toc_easy_zcloud_configuration)
- [Creating a user](#toc_creating_a_user)
- [Excluding a zone](#excluding_a_zone)
- [Removing a zone exclusion](#removing_a_zone_exclusion)
- [Changing timezone](#changing_timezone)
- [Webhook notifications](#webhooks)
- [External API Access](#external_api_access)
  - [Token authorization](#token_authorization)
  - [Endpoints](#endpoints)
    - [POST /api/check_zone_active](#check_zone_active)
    - [POST /api/exclude-zone](#exclude-zone)
    - [POST /api/remove-exclude-zone](#remove-exclude-zone)
    - [GET /api/get-duplicate-zones](#get-duplicate-zones)
    - [GET /api/zones/{group?}](#get-zones)

## Creating a cluster

Navigate to **Clusters** and press the **“Create cluster”** button.

![](https://zonecloud.io/wp-content/uploads/2022/04/zc1.png)

Give your new cluster a name press the **“Create”** button.

![](https://zonecloud.io/wp-content/uploads/2022/04/create_cluster.png)

---

## Creating an agent

Navigate to **“Servers”** and press the **“Create Server”** button.

![](https://zonecloud.io/wp-content/uploads/2022/04/create_server.png)

Fill in the required fields and press **“Save”**.

Server name: could be the hostname of the server, unique for each agent
 Server token: will be used in the agent’s configuration file, press the **“GENERATE”** button to create a random one
 IP: The server’s IP
 Agent selection active
 Cluster: Select the appropriate cluster from the list

![](https://zonecloud.io/wp-content/uploads/2022/04/create_agent.png)

Navigating back to the **Servers** page, you should see the newly created server/agent.

![](https://zonecloud.io/wp-content/uploads/2022/04/server_list1.png)

---

## Creating a node

Navigate to **“Servers”** and press the **“Create Server”** button.

![](https://zonecloud.io/wp-content/uploads/2022/04/create_server.png)

Fill in the required fields and press **“Save”**.

Server name: could be the hostname of the server, unique for each agent
 Server token: will be used in the nodes’s configuration file, press the **“GENERATE”** button to create a random one
 IP: The server’s IP
 Node selection active
 Cluster: Select the appropriate cluster from the list

![](https://zonecloud.io/wp-content/uploads/2022/04/create_node.png)

Navigating back to the **Servers** page, you should see the newly created server/node.

![](https://zonecloud.io/wp-content/uploads/2022/04/server_list2.png)

---

## Easy ZoneCloud configuration for agents/nodes

Navigate to **“Servers”** and press the **“Configuration”** button for the agent/node you want to setup.
 You can copy-paste the configurations on your agent/node.

![](https://zonecloud.io/wp-content/uploads/2022/11/zonecloud_controller_configuration.png)

e.g.:

![](https://zonecloud.io/wp-content/uploads/2022/11/zonecloud_controller_configuration_node.png)

---

## Creating a user

Navigate to **Users** and press the **“CREATE USER”** button.
 Fill in the appropriate fields and press **“Create”**.

![](https://zonecloud.io/wp-content/uploads/2022/04/create_user.png)

---

## Excluding a zone

If for some reason you want a zone to not be served by an agent, all you have to do is navigate to **Zones** page and press the **“Exclude”** button.

![](https://zonecloud.io/wp-content/uploads/2022/04/exclude_1.png) ![](https://zonecloud.io/wp-content/uploads/2022/04/exclude_2.png)

---

## Removing a zone exclusion

Navigate to **Zones** page and locate the zone on the **“Excluded zones”** table.
 Press the **“Remove exclusion”** button.

![](https://zonecloud.io/wp-content/uploads/2022/04/remove_exclude_1.png) ![](https://zonecloud.io/wp-content/uploads/2022/04/remove_exclude_2.png)

---

## Changing timezone

The time zone can be set on the `.env` file:

```
APP_TIMEZONE='Europe/Athens'
```

---

## Webhook notifications support

![](https://zonecloud.io/wp-content/uploads/2022/11/Screenshot_20250917_123552.png)

Under the Settings page you can find "Webhook notifications settings".
 From there you can set a URL and enable webhook notifications for:

- Zone action logs (insert, delete, exclude, remove exclude)
- Agents or Nodes going offline

The controller sends a JSON payload for these notifications.
 The payloads have the following format:

For zone action logs:

```
{
"report": {
"message": "Zone: test.zonecloud.io Action: remove exclude Server: server1 Timestamp: 2025-09-17 11:51:37\r\nZone: test.zonecloud.io Action: exclude Server: server2 Timestamp: 2025-09-17 11:51:39\r\n",
"logs": [
{
"timestamp": "2025-09-17 11:51:37",
"action": "remove exclude",
"zone": "test.zonecloud.io",
"server_name": "server1"
},
{
"timestamp": "2025-09-17 11:51:39",
"action": "exclude",
"zone": "test.zonecloud.io",
"server_name": "server2"
}
]
}
}
```

For servers going offline:

```
{
"report": {
"message": "Server server1 (X.X.X.X) from cluster dev is offline since 2025-09-17 09:50:59.\r\nServer server2 (Y.Y.Y.Y) from cluster dev is offline since 2025-09-17 09:50:59.\r\n",
"errors": {
"offline_servers": [
{
"name": "server1",
"last_login": "2025-09-17 09:50:59",
"IP": "X.X.X.X",
"zones_count": 5,
"version": "2.9.5"
},
{
"name": "server2",
"last_login": "2025-09-17 09:50:59",
"IP": "Y.Y.Y.Y",
"zones_count": 2,
"version": "2.9.0"
}
]
}
}
}
```

### Verifying signature

You'll notice that each of these webhook notifications appends an HTTP header with the name 'signature' and a string value.
 The value is the hash of the payload and a predefined secret string using the sha256 algorithm.
 You can verify that the notification is really coming from you by simply running the same hash algorithm against the payload.
 The default secret is the string "zcloud". You can customize it in your **.env** file by adding a WEBHOOK_SECRET=MYSECRET line.

In PHP's terms this would be:

```
hash_hmac('sha256', $payload, 'MYSECRET');
```

---

## External API Access

### Token authorization

To allow an external application to interact with ZoneCloud Controller’s API, you need to create an access token.

You can do this from the **App API Access** page.
 From this page you can create new tokens or revoke existing ones.![](https://zonecloud.io/wp-content/uploads/2022/04/api_access.png)

### Endpoints

#### POST /api/check_zone_active

 

| **Parameter name** | **Value** |
| --- | --- |
| zone | Required The zone name to be checked if it is active |
| cluster | Optional The cluster name to search for the zone |
| extra_info | Optional Value = 1 Appends "active_in" and "excluded_in" info for the zone where applicable. |

 

| **Result** | **Output (JSON)** |
| --- | --- |
| Zone is active | ``` {"active":true} ``` |
| Zone is active (with extra_info=1) | ``` {"active":true,"active_in":["server1"]} ``` |
| Zone is not active | ``` {"active":false} ``` |
| Authorization header missing/invalid | ``` {"message":"Authorization token missing\/invalid."} ``` |
| Wrong cluster name | ``` {"active":false,"message":"No such cluster"} ``` |
| Missing zone parameter | ``` {"message":"No zone given as parameter."} ``` |

 

*curl examples*:

```
curl https://<controller_hostname>/api/check_zone_active -H "Authorization: dEYmHvuoSiwQJATTF2y8fiAfZ9N887b" -X POST --data "zone=test1.com" {"active":true} 
curl https://<controller_hostname>/api/check_zone_active -H "Authorization: dEYmHvuoSiwQJATTF2y8fiAfZ9N887b" -X POST --data "zone=test-inactive1.com" {"active":false} 
curl https://<controller_hostname>/api/check_zone_active -H "Authorization: dEYmHvuoSiwQJATTF2y8fiAfZ9N887b" -X POST --data "zone=test1.com&extra_info=1" {"active":true,"active_in":["server1"]}
```

 

#### POST /api/exclude-zone

Exclude a zone from an agent.

| **Parameter name** | **Value** |
| --- | --- |
| zone | Required The zone name to exclude |
| server_name | Required The agent's name to exclude the zone from |

| **Result** | **Output (JSON)** |
| --- | --- |
| Zone is excluded | ``` {"result":true,"message":"Zone excluded from server."} ``` |
| Server not found | ``` {"result":false,"message":"Can't find an agent with such name."} ``` |
| Zone not found in agent | ``` {"result":false,"message":"Can't find a zone with such name\/agent name."} ``` |
| Authorization header missing/invalid | ``` {"message":"Authorization token missing\/invalid."} ``` |
| Missing zone/server_name parameter | ``` {"result":false,"message":"Missing \"zone\" and\/or \"server_name\" parameter."} ```   |

*curl examples*:

 

```
curl https://<controller_hostname>/api/exclude-zone -H "Authorization: dEYmHvuoSiwQJATTF2y8fiAfZ9N887b" -X POST --data "zone=test1.com&server_name=demo1"
{"result":true,"message":"Zone excluded from server."}

curl https://<controller_hostname>/api/exclude-zone -H "Authorization: dEYmHvuoSiwQJATTF2y8fiAfZ9N887b" -X POST --data "zone=test1.com&server_name=non_existing"
{"result":false,"message":"Can't find an agent with such name."}

curl https://<controller_hostname>/api/exclude-zone -H "Authorization: dEYmHvuoSiwQJATTF2y8fiAfZ9N887b" -X POST --data "zone=not-existing-zone.com&server_name=demo1"
{"result":false,"message":"Can't find a zone with such name\/agent name."}
```

#### POST /api/remove-exclude-zone

Remove a zone exclusion from an agent.

| **Parameter name** | **Value** |
| --- | --- |
| zone | Required The zone name to remove exclusion |
| server_name | Required The agent's name to remove exclusion of zone from |

| **Result** | **Output (JSON)** |
| --- | --- |
| Zone exclusion removed | ``` {"result":true,"message":"Zone exclusion removed from server."} ``` |
| Server not found | ``` {"result":false,"message":"Can't find an agent with such name."} ``` |
| Zone not found in agent | ``` {"result":false,"message":"Can't find a zone with such name\/agent name."} ``` |
| Authorization header missing/invalid | ``` {"message":"Authorization token missing\/invalid."} ``` |
| Missing zone/server_name parameter | ``` {"result":false,"message":"Missing \"zone\" and\/or \"server_name\" parameter."} ```   |

*curl examples*:

```
curl https://<controller_hostname>/api/remove-exclude-zone -H "Authorization: dEYmHvuoSiwQJATTF2y8fiAfZ9N887b" -X POST --data "zone=test1.com&server_name=demo1"
{"result":true,"message":"Zone exclusion removed from server."}

curl https://<controller_hostname>/api/remove-exclude-zone -H "Authorization: dEYmHvuoSiwQJATTF2y8fiAfZ9N887b" -X POST --data "zone=test1.com&server_name=non_existing"
{"result":false,"message":"Can't find an agent with such name."}

curl https://<controller_hostname>/api/remove-exclude-zone -H "Authorization: dEYmHvuoSiwQJATTF2y8fiAfZ9N887b" -X POST --data "zone=not-existing-zone.com&server_name=demo1"
{"result":false,"message":"Can't find a zone with such name\/agent name."}
```

#### GET /api/get-duplicate-zones

Responds with duplicate zones along with a list of the servers that the zone is active in.

Takes no parameters.

*curl exapmles:*

```
curl -s https://<controller_hostname>/api/get-duplicate-zones -H "Authorization: dEYmHvuoSiwQJATTF2y8fiAfZ9N887b" | jq
[
{
"zone": "test0.com",
"server_id": 30,
"cluster_id": 6,
"count": 2,
"server_name": "demo1",
"duplicate_servers": [
{
"name": "demo1",
"id": 30
},
{
"name": "demo3",
"id": 32
}
],
"excluded_in": 0,
"excluded_server_count": 0,
"not_excluded_server_count": 2
}
]
```

#### GET /api/zones{/group?}

Responds with a list of zones along with some info for each zone.
 You can group the zones by server if you append /by-server in the url

*curl exapmles:*

```
curl -s https://<controller_hostname>/api/zones -H "Authorization: dEYmHvuoSiwQJATTF2y8fiAfZ9N887b" | jq
[
  {
    "zone": "domain1.com",
    "owner": "user1",
    "insert_date": "2022-03-30 10:24:20",
    "excluded": null,
    "server_name": "server1"
  },
  {
    "zone": "domain2.com",
    "owner": "user2",
    "insert_date": "2022-03-30 10:24:20",
    "excluded": null,
    "server_name": "server2"
  },
]
```

```
curl -s https://<controller_hostname>/api/zones/by-server -H "Authorization: dEYmHvuoSiwQJATTF2y8fiAfZ9N887b" | jq
{
  "server1": [
    {
      "zone": "domain1.com",
      "owner": "user1",
      "insert_date": "2022-03-30 10:24:20",
      "excluded": null
    }
   ],
   "server2": [
    {
      "zone": "domain2.com",
      "owner": "user2",
      "insert_date": "2022-03-30 10:24:20",
      "excluded": null
    }
   ]
}
```
